2018年7月

前言

之前在讲WebView缓存时有讲过一种DomStorage缓存方式。这篇文章就详细讲一下这个缓存机制,虽然这个跟web端的关系大一些,也就当作一次知识拓展吧!


webStorage

在html5中DomStorage称为webStorage。
其相关api有:

    setItem (key, value) ——  保存数据,以键值对的方式储存信息。

    getItem (key) ——  获取数据,将键值传入,即可获取到对应的value值。

    removeItem (key) ——  删除单个数据,根据键值移除对应的信息。

    clear () ——  删除所有的数据

    key (index) —— 获取某个索引的key

其类型可分为:sessionStorage和localStorage两种。


localStorage

localStorage的生命周期是永久性的。假若使用localStorage存储数据,即使关闭浏览器,也不会让数据消失,除非主动的去删除数据,使用的方法如上所示。localStorage有length属性,可以查看其有多少条记录的数据。使用的方法也很简单:

  • storage = window.localStorage; ——先获取localStorage的对象
  • storage.setItem("name", "Rick"); ——调用setItem方法,使用key-value的形式存储数据
  • storage.getItem("name"); ——调用getItem方法,获取对应key值的数据
  • storage.removeItem("name"); ——调用removeItem方法,移除对应key值的数据

以上就是localStorage的基本用法了。注意!以上的是js代码


sessionStorage

sessionStorage 的生命周期是在浏览器关闭前。也就是说,在整个浏览器未关闭前,其数据一直都是存在的。sessionStorage也有length属性,其基本的判断和使用方法和localStorage的使用是一致的。
主要的特点是:

  • 页面刷新不会消除数据
  • 只有在当前页面打开的链接,才可以访sessionStorage的数据
  • 使用window.open打开页面和改变localtion.href方式都可以获取到sessionStorage内部的数据

以上就是webStorage的基本介绍了,以下链接有比较详细的介绍:LocalStorage和sessionStorage之间的区别


Environment.getExternalStorageDirectory()究竟是哪里,以及它与getExternalFilesDir()的区别

上次讲WebView缓存的时候有用到Environment.getExternalStorageDirectory()来获取应用的外部存储路径。这里插一个题外话,其实不完全是WebView,所有关于文件读写的操作都需要获取到应用的内存地址。这个在做相机拍照、录像的时候也时常碰到的问题,就是到底Environment.getExternalStorageDirectory()是在哪里呢?
ExternalStorageDirectory:故名思义就是外部存储。说到外部存储我们的第一反应就是外置的SD卡。其实我刚开始做开发的时候也有过一些疑惑,因为现在大部分手机都不存在SD卡这个概念了,那会不会导致这个方法出现异常呢?其实是不会的。这里的外部存储是相对于手机本身的存储空间而言的。什么意思呢?其实大概就分为两种情况:

  • 第一种就是以前旧版本的手机可以插外置SD卡的,这一类手机的ExternalStorageDirectory一般会认为是这张SD卡的区域。
  • 第二种就是我们现在比较常见的无外置SD卡内置大容量的手机了。这部分的手机硬件一般会将手机容量分出一部分空间用于安装手机系统,而剩余的一般就是所谓的ExternalStorageDirectory(大概就是它们在这个内置内存中划分了很大一部分区间作为所谓的外部存储空间)。所以说即使一台256G的手机真正可以给用户自由分配的空间也必然会比256G少。

综合以上两种情况可以得知其实ExternalStorageDirectory只是一个相对的概念,而不是我们经常所说的外置SD卡。以下是Google官方文档的解释:
don't be confused by the word "external" here. This directory can better be thought as media/shared storage. It is a filesystem that can hold a relatively large amount of data and that is shared across all applications (does not enforce permissions). Traditionally this is an SD card, but it may also be implemented as built-in storage in a device that is distinct from the protected internal storage and can be mounted as a filesystem on a computer
其意思就是这个目录可以更好地被认为是媒体/共享存储。它是一个文件系统,可以保存相对大量的数据,并在所有应用程序之间共享(不强制执行权限)。传统上,这是一个SD卡,但它也可以被实现为与被保护的内部存储区不同的设备中的内置存储,并且可以作为文件系统安装在计算机上。
简单一点理解就是其实Android手机也可以作为U盘来存储数据嘛。所以它本身其实也可以看作是一张SD卡。


getExternalStorageDirectory()与getExternalFilesDir()

  • 上面说到了ExternalStorageDirectory是一个在所有应用之间共享的文件系统,那么他的路径就必然是相对于其他应用独立的。也就是说在其路径下保存的文件可以对其他应用共享。

我们可以获取ExternalStorageDirectory的路径

File sdCard = Environment.getExternalStorageDirectory();
Log.i(TAG,sdCard);

打印出来的路径为

/mnt/sdcard
  • 再来说说getExternalFilesDir()。之前说过ExternalStorageDirectory是相对应用独立的文件路径,有独立就会私用的概念,也就是应用专属的文件。其路径一般会在应用的包目录下:

    /Android/data/< package name >/files/

我们可以通过getExternalFilesDir(null)查找到上面提到对应应用的files目录。
如果需要查找files下对应的目录,可以将所查找的目录名作为字符串传入方法中:
如我需要查找files下的Cache目录,getExternalFilesDir("Cache")

前言

上一篇文章写到了WebView的常规设置,今天具体来介绍一下WebView中java与js代码的交互问题。
本次参考了两篇文章的内容:

1.WebView详解

2.Android:你要的WebView与 JS 交互方式 都在这里了


Android通过WebView调用 JS 代码

首先来说说通过WebView调用js代码的问题。在WebView中,提供了两个方法来调用js中的代码分别是

1. 通过WebView的loadUrl() 
2. 通过WebView的evaluateJavascript()

这里说明一下,第二个方法是在Android4.4之后加入的,其具有可以获取js方法返回值的功能。下面来说说具体的使用。
首先我们可以定义一段js代码,代码截取上述的“WebView详解”。这里分别定义了两个函数,一个有返回值,一个无返回值。

<script>
 // 有参无返回值
 function funcParam(param){
   alert(param+"");
 }

 // 有参有返回值
 function newFunc(param1,param2,param3){
   alert((param1 + param2) + " " + param3);
   return param3;
 }
</script>

然后我们就可以在java代码中通过webView.loadUrl("Javascript:js方法名()");或webView.evaluateJavascript("Javascript:js方法名()",ValueCallback<T> callback);这两种形式去调用js中的方法。
这里因为设计到两种版本的方法调用,在参考文章中也提到了一种封装方式,这里也顺带提一下:

public static final String JS_FUNC_PREFIX = "javascript:";
public void invokeJs(String jsFunc, final ValueCallback<String> callback) {
  if (!jsFunc.startsWith(JS_FUNC_PREFIX)) {
     jsFunc = JS_FUNC_PREFIX + jsFunc;
  }
 // api 19
 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
    mWebView.evaluateJavascript(jsFunc, callback);
 } else {
    mWebView.loadUrl(jsFunc);
 }
}

其实现在市场上的Android手机普遍都是5.0版本起步,个人认为这里的适配可以省略。当然也需要根据实际需求为准。

然后在说一下ValueCallback<T>这个接口,它是一个Android Webkit中用于js代码返回值回调的接口,这里的泛型T就是js代码的返回值类型,需要按照实际需求来设置。

然后就是js函数的问题了,这里因为我们的参数是String类型的,在传参方面其实就是直接把它的参数也拼接成字符串传递。所以我们可以使用如下方法进行字符串的拼接。

public static String generateJsFunc(String funcName, Object... params) {
StringBuilder sb = new StringBuilder(funcName).append("(");
for (int i = 0; i < params.length; i++) {
  if (params[i] != null) {
     if (params[i] instanceof String) {
        sb.append("'").append(params[i]).append("'");
     } else {
        sb.append(params[i]);
     }
     if (i != params.length - 1) {
        sb.append(",");
     }
  }
}
sb.append(")");
return sb.toString();}

在调用loadUrl或evaluateJavascript方法之前就可使用上述的方法进行字符串拼接参数。


通过Js调用Android代码

这里可以通过3种方式进行调用

  1. 通过WebView的addJavascriptInterface()进行对象映射
  2. 通过 WebViewClient 的shouldOverrideUrlLoading ()方法回调拦截 url
  3. 通过 WebChromeClient 的onJsAlert()、onJsConfirm()、onJsPrompt()方法回调拦截JS对话框alert()、confirm()、prompt() 消息

    • addJavascriptInterface()进行对象映射
public class JsBridge {

    @JavascriptInterface 
    public void toast(String msg) {
      ToastUtils.show(msg);
    }

    @JavascriptInterface
    public void log(String msg) {
      Log.e(TAG, msg);
    }

  }

如上述代码所示,我们可以通过创建一个自定义类型,在公共方法上加入@JavascriptInterface注解声明其为一个可以被js调用的方法。
然后我们可以通过webView.addJavascriptInterface(obj, name); 这个方法来与js建立连接。这里解释一下,obj就是刚刚所创建的类的对象,name是用于提供给js调用的key值。
当然前提是需要设置webView.getSettings().setJavaScriptEnabled(true);

webView.addJavascriptInterface(new JsBridge(), "android");

在 js 中调用 java 方法时,只需要使用 window 对象:

window.android.log('test js invoke java method');

android就是我们刚刚传递的name参数值,而log就是JsBridge类中的其中一个公共方法。

  • shouldOverrideUrlLoading ()方法回调拦截 url
    在上一篇文章中我们提到了WebViewClient中的shouldOverrideUrlLoading方法,这个其实也是一个用于js和Android交互的工具。下面介绍的是如何通过uri的scheme来调起其他应用,例如支付宝。

如果web中需要通过链接的方式去调起手机上某应用的话就可以使用这个方法。
首先定义一个检测scheme的方法:

 public static final String ALIPAY_SCHEME = "alipays";
 public static boolean shouldOverrideIntentUrl(Context context, String url) {
 Uri uri = Uri.parse(url);
 if (uri != null
     && uri.getScheme() != null
     && uri.getScheme().equals(ALIPAY_SCHEME)) {
  try {
     Intent intent = Intent.parseUri(url, Intent.URI_INTENT_SCHEME);
     intent.addCategory(Intent.CATEGORY_BROWSABLE);
     intent.setComponent(null);
     intent.setSelector(null);
     context.startActivity(intent);
  } catch (Exception e) {
     e.printStackTrace();
     if (e instanceof ActivityNotFoundException) {
        ToastUtil.show("未检测到支付宝客户端");
     }
     return true;
  }
  return true;
}
 return false;
}

该方法先将url转成uri然后获取其scheme判断其是否为支付宝支付标志,若是则使用intent调起支付宝应用。
然后在shouldOverrideUrlLoading进行判断

webView.setWebViewClient(new WebViewClient() {
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
   // 如果不使用 intent 覆盖加载这个链接,就走原来
   if (!shouldOverrideIntentUrl(getContext(), url)) {
      view.loadUrl(url, Api.makeHttpHeaders(getContext()));
   }
   return true;
 }
});

还有一种情况是点击连接之后下载文件,例如下载一个apk,这种链接的url一般会是http://xxx.apk这种形式的。这时候我们就需要在shouldOverrideUrlLoading判断是否含有这种格式,然后作一个拦截。拦截后可以通过调用webView的DownloadListener做下载处理。这里使用的是使用直接打开浏览器的方式。

webView.setDownloadListener(new DownloadListener() {
  @Override
  public void onDownloadStart(String url, String userAgent,
                       String contentDisposition,
                       String mimetype,
                       long contentLength) {
     //去下载
     Intent intent = new Intent(Intent.ACTION_VIEW);
     String downLoadUrl = url;
     if (!downLoadUrl.contains("http://")) {
        downLoadUrl = "http://" + downLoadUrl;
     }
     intent.setData(Uri.parse(downLoadUrl));
     intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
     webView.getContext().startActivity(intent);
  }
});
  • WebChromeClient
    然后就到了第3种方式了。这里内置了三种方法分别是onJsAlert()、onJsConfirm()、onJsPrompt()方法。分别对应的是js中的alert()、confirm()、prompt() 弹窗方法。下图是对应这三个方法在js的作用。

944365-1385f748618af886.png

这里首先我们需要设置WebChromeClient,这里用onJsPrompt为例,然后就是重写onJsPrompt方法

webView.setWebChromeClient(new WebChromeClient(){
        @Override
        public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, 
        JsPromptResult result) {
             Uri uri = Uri.parse(url);
             if ( uri.getScheme().equals("js")) {
                 if (uri.getAuthority().equals("webview")) {
                     Set<String> collection = uri.getQueryParameterNames();
                     result.confirm("js调用了Android的方法成功啦");
                 }
                 return true;
             }
             return super.onJsPrompt(view, url, message, defaultValue, result);
        }
    });
  1. 参数message:代表promt()的内容,而result代表输入框的返回值。
  2. 这里我们假定url = "js://webview?arg1=111&arg2=222",顺便提一下,scheme(协议格式), authority(协议名)。
  3. 然后通过url转换成uri与上面第2种方式是一样的,分析其scheme、authority的值进行拦截。然后如果都符合的话就进行拦截后的处理。
  4. 还有一点需要注意的是我们可以通过url拼接的方式如上述url所示,将一些参数传递给Android。在java中可以通过uri.getQueryParameterNames();获取这些参数的key值,uri.getQueryParameter(String key)通过key值获取对应的value值。
  5. 最后我们可以调用JsPromptResult的confirm将结果回调到弹窗上显示。

注:

  • WebView 默认是无法弹出 alert() 的,需要设置 WebChromeClient。
  • 需要设置webSettings.setJavaScriptCanOpenWindowsAutomatically(true);来允许js弹窗

前言

最近想补一波关于WebView的基础,以下是我所参考的推文地址:请输入链接描述
今天先来讲讲WebView的基础设置及其缓存设置。


WebSettings

WebSettings可以用于配置WebView的相关设置。一般使用webView.getSettings()方法来获取一个WebSettings对象进而进行相关的WebView设置,下面就先上一堆代码慢慢讲解一些常用的设置。

WebSettings settings=webView.getSettings();

首先我们需要获取WebSettings对象

settings.setJavaScriptEnabled(true);

设置为true后就是允许与js交互,这里顺便提一下:

对于Android调用JS代码的方法有2种:

  1. 通过WebView的loadUrl()
  2. 通过WebView的evaluateJavascript()

对于JS调用Android代码的方法有3种:

1. 通过WebView的addJavascriptInterface()进行对象映射
2. 通过 WebViewClient 的shouldOverrideUrlLoading ()方法回调拦截 url
3. 通过 WebChromeClient 的onJsAlert()、onJsConfirm()、onJsPrompt()方法回调拦截JS对话框alert()、confirm()、prompt() 消息

对于这些交互的方法会在后面的文章中详细说明。

settings.setJavaScriptCanOpenWindowsAutomatically(true);

设置WebView是否可以由 JavaScript 自动打开窗口,默认为 false,通常与 JavaScript 的 window.open() 配合使用。

settings.setDefaultTextEncodingName("UTF-8");

设置符号编码方式,这里设置支持中文的编码方式UTF-8

settings.setUseWideViewPort(true);
settings.setLoadWithOverviewMode(true);

设置使用大视图时,适应屏幕

settings.setSupportMultipleWindows(true);

设置支持多窗口。

settings.setSupportZoom(true);

设置支持缩放

settings.setAllowFileAccess(true);

设置支持访问文件。

settings.setNeedInitialFocus(true);

当WebView调用requestFocus时为WebView设置节点,这里可以先设置支持获取手势焦webView.requestFocusFromTouch();

settings.setLoadsImagesAutomatically(true);

支持自动加载图片

settings.setLayoutAlgorithm(WebSettings.LayoutAlgorithm.TEXT_AUTOSIZING);

指定WebView的页面布局显示形式,调用该方法会引起页面重绘。
NORMAL,SINGLE_COLUMN (过时), NARROW_COLUMNS (过时) ,TEXT_AUTOSIZING

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
        settings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
}

这里需要判断一下系统版本,从Lollipop(5.0)开始WebView默认不允许混合模式,https当中不能加载http资源,需要设置开启。


WebViewClient

上述设置之后可以通过webView.loadUrl("https://www.baidu.com/");这样就可以通过WebView访问百度页面了。loadUrl方法可以通过传递一个Url来访问web端。

当我们访问到百度的页面时,可能会出现点击某个链接的时候会自动跳转至系统默认的浏览器。这个明显不符合应用的要求。这是我们需要配置WebViewClient来实现WebView的重定向加载。
我们可以通过设置WebView的WebViewClient来实现页面的重定向。

    webView.setWebViewClient(new WebViewClient() {
        @Override
        public boolean shouldOverrideUrlLoading(WebView view, String url) {
            if (url.toString().contains("sina.cn")){
                view.loadUrl("https://www.bilibili.com/");
                return true;
            }
            return false;
        }

        @Override
        public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request)
        {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                if (request.getUrl().toString().contains("sina.cn")){
                    view.loadUrl("https://www.bilibili.com/");
                    return true;
                }
            }
            return false;
        }

    });

注:shouldOverrideUrlLoading(WebView view, String url)方法为5.0以前使用的,在5.0以后使用shouldOverrideUrlLoading(WebView view, WebResourceRequest request)代替。
返回值说明:

  • 返回false,意味着请求过程里,不管有多少次的跳转请求(即新的请求地址),均交给webView自己处理,这也是此方法的默认处理
  • 返回true,说明你自己想根据url,做新的跳转,比如在判断url符合条件的情况下,我想让webView加载https://www.bilibili.com/
    只有设置了WebViewClient才能避免点击新的链接跳转至系统浏览器中的情况!

关于缓存机制

WebView现在主要的缓存机制分别是AppCache和DomStorage(dom缓存)。在早些版本还有数据库SQL缓存等,但现在基本上随着html5发布也弃用了。

  1. AppCache:
    AppCache使我们能够有选择的缓冲web浏览器中所有的东西,从页面、图片到脚本、css等等。尤其在涉及到应用于网站的多个页面上的CSS和JavaScript文件的时候非常有用。其大小目前通常是5M。在Android上需要手动开启(setAppCacheEnabled),并设置路径(setAppCachePath)。Android中Webkit使用一个db文件来保存AppCache数据(my_path/ApplicationCache.db)
  2. DomStorage
    如果需要存储一些简单的用key/value对即可解决的数据,DOM Storage是非常完美的方案。根据作用范围的不同,有Session Storage和Local Storage两种,分别用于会话级别的存储(页面关闭即消失)和本地化存储(除非主动删除,否则数据永远不会过期)。在Android中可以手动开启DOM Storage(setDomStorageEnabled),设置存储路径(setDatabasePath)。

注:这个主要是web的用于设置缓存的机制。
在Android中清除缓存时,如果需要清除Local Storage的话,仅仅删除Local Storage的本地存储文件是不够的,内存里面有缓存数据。如果再次进入页面,Local Storage中的缓存数据同样存在。需要杀死程序运行的当前进程再重新启动才可以。

AppCache设置方式:

  • 获取WebViewSettings对象:WebSettings settings = webView.getSettings();
  • 创建一个缓存保存路径:

    String cachePath = new File(Environment.getExternalStorageDirectory(), "webCache").getAbsolutePath();

  • 设置允许AppCache和缓存路径:

    settings.setAppCacheEnabled(true);
    settings.setAppCachePath(cachePath);

  • 可以设置缓存的最大内存大小:(现已被弃用,转为管理自动分配)

    settings.setAppCacheMaxSize(1000 * 1024);

DomStorage设置方式:

  • 设置允许开启dom缓存:

    settings.setDomStorageEnabled(true);

  • 设置缓存路径(现已被弃用)

    settings.setDatabasePath(cachePath);

再来说说缓存的模式,我们可以通过settings.setCacheMode()方法设置缓存模式,该方法接收的参数如以下所示:

  • LOAD_DEFAULT 默认的缓存使用模式。在进行页面前进或后退的操作时,如果缓存可用并未过期就优先加载缓存,否则从网络上加载数据。这样可以减少页面的网络请求次数。
  • LOAD_CACHE_ELSE_NETWORK 只要缓存可用就加载缓存,哪怕它们已经过期失效。如果缓存不可用就从网络上加载数据。
  • LOAD_NO_CACHE 不加载缓存,只从网络加载数据。
  • LOAD_CACHE_ONLY 不从网络加载数据,只从缓存加载数据。

前言

最近补了一波Handler的知识,之前写项目的时候也有用到Handler,就是一直没有完全理解其中的机制,现在大概来说说吧。


什么是Handler

首先在Android当中是存在UI线程的,而UI线程其实就是主线程。在java中,如果处理一些比较耗时的操作,譬如网络请求等待响应这一类操作,线程是会出现阻塞来等待请求返回的。而假如我们在UI线程当中来等待请求返回,也就是说UI线程出现阻塞。对于一个前端页面来说有可能就出现了卡顿现象。做前端的都知道,卡顿是很难受的。所以一般这种后台处理数据的逻辑会采取不影响UI层面的情况下执行,所以就采有了异步。Handler跟多线程,消息队列联系很紧密,其主要的功能就是发送和处理Message,也可以分发Runnable对象。 每个Handler实例,都会绑定到创建他的线程中(默认是位于主线程,但是我们可以通过Looper对象来指定到其他线程)。


以下是我从网上看到的Handler作用

  • 执行计划任务,比如在预定的实现某些任务,模拟定时器等等。
  • 线程之间的互相通信,在app启动的时候,会创建一个主线程(即UI线程),主线程创建了之后还会创建一个消息队列来处理各种消息,当我们创建了子线程时,可以在子线程中拿到主线程创建的Handler对象,当我们需要通信时就可以,通过该对象像主线程的消息队列发送消息,主线程接收到消息之后就会处理。
  • 确保操作始终在某个特定的线程中运行。比如说当我们访问数据库并加载数据时,除了初始化要加载外,每当我们收到数据改变的消息通知时也需要重新加载,为了保证数据的有效性,减少不必要的查询操作。

消息循环机制

其实在主线程中是存在一个消息循环机制的。和Handler相关的其实还有两个角色。一个叫Looper、一个叫MessageQueue。这两个是什么东西?下面慢慢讲。

  • 首先先讲一个网购的故事。小羊从淘宝上买一个手机壳,然后在确认付款之后就会到达待收货阶段。这时候,店家就会根据小羊提供的地址将快递单填好然后交给快递员小哥。快递员小哥就会按照填写的地址,把这个快递送到小羊的手上。这就是整个淘宝购物最简单的流程了。

好了,说完一个小羊购物的故事后就到了和Handler关联的时间了。这里先分一下角色,上述的Handler就是小羊,小羊购买的货物是一条message店家在这里办理一个Looper的角色,而快递员小哥就是MessageQueue了。那么究竟这些是什么跟什么呢?下面讲解一下:

  • Looper:Android的线程分为有消息循环的线程和没有消息循环的线程,有消息循环的线程一般都会有一个Looper。我们的主线程(UI线程)就是一个消息循环的线程。Looper的线程,不管是UI线程还是子线程,只要你有Looper,我就可以往你的消息队列里面添加东西,并做相应的处理。一句话每一个消息循环线程都可以产生一个Looper对象,用于管理线程里的MessageQueue。
  • Handler:你可以**构造Handler对象来和Looper沟通以便Push新的消息到MessageQueue
    里或者接收Looper(从MessageQueue中取出)发送过来的消息**
  • MessageQueue: 用于存放线程放入的消息

注:主线程默认含有Looper、MessageQueue
现在翻译一下上述的故事就是:

  1. 首先,Handler(小羊)会向Looper(店家)提交一条message(里面包含要买的货物)。
  2. Looper(店家)收到请求后就向MessageQueue(快递员)增加这条信息。(相当于生成了一条订单)。
  3. 当MessageQueue里轮到该条message时,就会响应Handler的handlerMessage,让其处理这条消息。(相当于快递员将货物送到小羊手上,小羊要自己处理这个货物)

构建Handler对象

  • 默认的构造方法Handler()把当前Handler与当前线程和当前线程的消息队列绑定,这也是为什么默认的Handler一定运行在主线程中
  • 可以触发回调Handler(Handler.Callback callback)
  • Handler(Looper looper) 把Handler绑定到Looper所属的线程和消息队列中
  • Handler(Looper looper, Handler.Callback callback)

Handler的常用方法

以下方法名摘录自网上

  • 获取Looper对象: Looper getLooper()
  • 处理系统的消息:void dispatchMessage(Message msg) 一般不宜修改
  • 处理消息:void handleMessage(Message msg) 必须实现
  • 从消息队列中得到新的Message对象:Message obtainMessage()及重载
  • 发送Runnable对象:boolean post(Runnable r) 及重载形式
  • 发送消息:boolean sendEmptyMessage()及重载
  • 发送自定义Message:boolean sendMessage(Message msg)
  • removeCallbacks(Runnable r) 、removeCallbacksAndMessages(Object token)
    、removeMessages(int what) 等移除系列,移除了之后就不会去执行handleMessage(Message msg)

注:如果想将已发出的消息移除,可以使用removeCallbacks(Runnable r) 、removeCallbacksAndMessages(Object token)
、removeMessages(int what),具体视情况而定。但是需要注意的是,removeCallbacks(Runnable r)的该Runnable对象如果已经提交给handler进行处理当中的话,是无法实现移除的。


Handler在其他线程的使用

值得一提的是,我们无法在其他线程中直接创建Handler,因为其他线程默认不自动创建Looper和MessageQueue。
我们可以通过创建一个继承HandlerThread类的子线程,并且在HandlerThread.onLooperPrepared()方法中来进行Handler的相关操作。HandlerThread是自带Looper、MessageQueue的。


使用Looper对象创建Handler

Looper.getMainLooper()方法可以获取到主线程的Looper对象。
在其他线程中也可以通过this.getLooper();获取。
创建Handler对象handler=new Handler(Looper.getMainLooper())


handleMessage

这里说一下handleMessage这个方法:
当消息需要处理时就会响应handleMessage

handler=new Handler(){

@Override
public void handleMessage(Message msg) {
    super.handleMessage(msg);
    
   }

};

方法的模板就是上述的结构了。然后上述的Message对象就会携带一个what的整型的基本数据类型。通过这个what的值就可分辨出是你所传递的哪条消息。


handleMessage的处理唯一性

这个处理唯一性其实我自己起的名字。意思就是在这个方法还没完全执行完的时候,这个message对象是不会更改的。譬如

handler=new Handler(){

@Override
public void handleMessage(Message msg) {
    super.handleMessage(msg);
    Log.i("handler",msg.what);
    Thread.sleap(1000);
    Log.i("handler",msg.what);
   }
};

以上首先在接收到message的时候先在log中输出一个what,然后再让他睡眠1s,也就是线程阻塞。然后再打印这个what。如果是运行后你会发现这还是同一条消息。


跨线程通信

跨线程通信的意思就是我们可以使用handler来进行线程间的交互。
首先有主线程和线程A,线程A是子线程

  • 首先我们在线程A中创建一个主线程的Handler,handler=new Handler(Looper.getMainLooper())
  • 然后我们就可以通过这个handler对象来向主线程发送消息,然后在主线程做处理。

最后

最后还要提一个点就是handler对象和MessageQueue的地址是有唯一性的。也就是说其中其实是有target这个概念的,不可以妄想使用两个不同的handler对象来共同处理一个message。以下是错误想法

  • 新建一条message,然后使用handlerA将message送进入消息队列。
  • 使用handlerB进行这条message的处理。

以上这种做法是完全不可行的,即使这条message是相同的,但是不同的handler只能处理自己发送的消息。

前言

上一篇文章介绍了如何添加按钮的点击阴影效果,这篇主要是定制按钮的圆角问题。其实也不难只是做多了一个属性控制而已。


添加一个enum类型的属性

<declare-styleable name="MaterialButton">
    <attr name="rectRadius" format="dimension"></attr>
    <attr name="backgroundColor" format="color"></attr>
    <attr name="radiusType" format="enum">
        <enum name="none" value="1"/>
        <enum name="top_radius" value="2"/>
        <enum name="bottom_radius" value="3"/>
        <enum name="all" value="4"/>
    </attr>
</declare-styleable>

这个是这个自定义按钮的自定义属性。前两个分别是圆角半径、背景颜色。第三个就是设置圆角的类型。这个enum类型的属性其实有个很常见的例子,就是LinearLayout的orientation属性就是enum类型的。
这里说明一下:
none:无圆角
top_radius:左上和右上有圆角
bottom_radius:左下和右下有圆角
all:四角都为圆角
这里暂定这四种情况,以后有需要可以再作修改。


在代码中解析该属性

在代码中定义一个int型变量

private int radiusType;          

//Type
private static final int NONE=1;
private static final int TOP_RADIUS=2;
private static final int BOTTOM_RADIUS=3;
private static final int ALL=4;

四种情况的属性常量

radiusType=a.getInt(R.styleable.MaterialButton_radiusType,ALL);

解析属性,默认为ALL(四角都为圆角)

这时就可以在之前我们设置圆角float型数组的时候作一个选择判断了。

switch (radiusType){
        case NONE:
            radius = new float[] { 0, 0, 0, 0, 0, 0, 0, 0};
            break;
        case TOP_RADIUS:
            radius = new float[] { rectRadius, rectRadius,rectRadius, rectRadius, 0, 0, 0, 0 };
            break;
        case BOTTOM_RADIUS:
            radius = new float[] { 0, 0, 0, 0, rectRadius, rectRadius, rectRadius, rectRadius };
            break;
        case ALL:
            radius = new float[] { rectRadius, rectRadius,rectRadius, rectRadius, rectRadius, rectRadius, rectRadius, rectRadius };
            break;
    }

之后的操作和之前文章里的代码是一样的。


效果

最后是TOP_RADIUS和BOTTOM_RADIUS的效果:
微信图片编辑_20180705222107.jpg

微信图片编辑_20180705222203.jpg