Android 应用程序启动时,系统会创建一个主线程,负责与UI组件(widget、view)进行交互,比如控制UI 界面显示、更新等;分发事件给UI界面处理,比如按健事件、触摸事件、基绘图事件等,因此,Android 主线程也称为 UI线程。
由此可知,UI 线程只能处理一些简单的、短暂的操作,如果要执行繁重的任务或者耗时长的操作,比如访问网络、数据库、下载等,这种单线程模型会导致线程运行性能大大降低其至阻塞UI线程,如果被阻塞超过5秒,系统会提示应用程序无响应,也就是ANR,这会直接导致退出整个应用程序或者短暂杀死应用程序。
除此之外,单线程模型的UI主线程也是不安全的,会造成不可确定的结果。线程不安全可以简单理解为∶多线程访问资源时,有可能出现多个线程先后更改数据造成数据不一致。比如,A工作线程(也称为子线程)访问某个公共UI资源,B工作线程在某个时候也访问了该公共资源,当B线程正访问时,公共资源的属性已经被A改变了,这样B得到的结果不是所需要的,造成了数据不一致的混乱情况。
线程安全简单理解为∶当一个线程访问功能资源时对该资源进行了保护,比如加了锁机制,当前线程在没有访问结束释放锁之前,其他线程只能等待直到释放锁才能访问。这样的线程就是安全的。
基于以上原因,Android 的单线程模型必须遵守两个规则∶
(1)不要阻塞 UI线程。
(2)不要在UI线程之外访问UI组件,即不能在子线程访问UI组件,只能在UI线程访间。因此,Android系统将大部分耗时、繁重任务交给子线程完成,不会在主线程中完成,解决了第一个难题;同时,Android 只允许主线程更新 UI 界面,子线程处理后的结果无法和主线程交互,即无法直接访问主线程,这就要用到Handler机制来解决此问题。
Handler 机制核心类介绍
在 Handler 机制中的所有故事都是围绕Handler、Looper、Message 这3个类展开的。下面我们分别介绍一下这3个类。
1、Message
消息对象,顾名思义就是记录消息信息的类。Message 类有儿个比较重要的字段,如下表1-1所示。
表1-1Message类的常用字段
字段名 | 作用 |
arg1 | 使用这个字段来传递整数类型的值,与ag2相同 |
arg2 | 使用这个字段来传递整数类型的值,与ag1相同 |
obj | 这个字段是Object 类型,可以通过这个字段传递某个复杂的消息内容到接收者中 |
what | 这个字段是消息的标志,在消息处理中,可以根据这个字段区分消息,类似于在处理 button事件时通过 switch(v.getId())判断是点击了哪个按钮 |
在使用 Message 时,可以通过 new Message()创建一个Message 实例,但是Android 官更推荐我们通过Messge.obtain()或者Hander.obtainMesage()获取Message 对象。这并不一定是直接创建一个新的实例,而是先从消息池中看有没有可用的Message 实例,存在则直接取出并返回这个实例。反之如果消息池中没有可用的 Message 实例,则根据给定的参数新建一个新Message 对象。一般情况下,Android系统默认情况下在消息池中实例化10 个Message对象。
当Message实例被创建之后,使用 setDate()或者arg参数为 Message 携带一些数据,并通过Handler 对象发送到MessageQueue 中。
2、Looper
Looper是 MessageQueue 的管理者。
MessageQueue 是一个消息队列,用来存放 Message 对象的数据结构,按照"先进先出"的原则存放消息。存放并非实际意义的保存,而是将 Message 对象以链表的方式串联起来的。MessageQueue 对象不需要自己创建,而是由Looper 对象对其进行管理,一个线程最多只可以拥有一个MessageQueue。可以通过Looper.myQueue()获取当前线程中的 MessageQueue。
在一个线程中,如果存在 Looper 对象,则必定存在 MessageQueue 对象,并且只存在一个Looper 对象和一个MessageQueue 对象。在 Android系统中,除了主线程有默认的Looper 对象,其他线程默认是没有Looper 对象的。如果想让新创建的线程拥有 Looper 对象,首先应调用Looper.prepare()方法,然后调用Looper.loop()方法。
另外,如果想要获取一个已经存在的Looper对象,可以通过Looper.myLooper()获取,此外还可以通过Looper.getMainLooper()获取当前应用系统中主线程的Looper 对象。在这个地方有一点需要注意,假如 Looper 对象位于应用程序主线程中,那么 Looper.myLooper()和Looper.getMainLooper()获取的是同一个对象。
3、Handler
消息的处理者。一般情况下,会在子线程中通过 Handler 对象把 Message 对象发送到MessageQueue 中,然后在主线程中用该对象的 handleMessage(Message msg)方法接收Message 对象,再对UI进行操作。
Handler类的方法很多,常用的几种如下表所示。
方法 | 作用 |
public final boolean sendEmptyMessage(int what) | 用于发送一个只包含 what 值的 Message |
public final boolean sendMessage(Message msg) | 用于发送一个 Message |
public final boolean sendMessageDelayed(Message msg,long nms) | 用于延迟发送一个 Message,延迟时长为nms(毫秒) |
public final boolean hasMessages(int what) | 用于判断消息队列中是否已经含有此what值的Message |
public final boolean post(Runnable r) | 用于提交一个任务,并立即执行 |
public final boolean sendMessageDelayed(Runabler, long nms) | 用于提交一个延迟执行的任务,延迟时长 |
通过上面的学习,读者可能已经明白了Handler 机制是如何发送消息到 MessageQueue 中的,但是对于handleMessage(Message msg)方法为什么能够接收到 Message 还抱有疑问。其实这里面的重点在于Looper.loop()方法。此方法很重要,源码如下:
通过源码分析,可以知道这是通过一个死循环不断地调用 MessageQueue 的 next()方法,这个next()方法就是消息队列的出队方法。每当有一个消息出队,就将它传递到msg.target的dispatchMessage(Message msg)方法中。dispatchMessage(Message msg)的源码如下∶
这样一来,handleMessage(Message msg)方法才可以获取到之前发送的消息。
另外,上一篇博客的实例中用到了 runOnUiThread(Thread thread)方法,当时说这其实使用的也Handler机制。打开runOnUiThread(Thread thread)方法,源码如下;
public final void runOnUiThread(Runnable action)
{
if(Thread.currentThread() != mUiThread) {
mHandler.post(action);
}else{
action.run();
}
}
通过分析发现,runOnUiThread(Thread thread)方法的逻辑很简单:如果当前的线程不等于UI线程(主线程),就去调用 Handler 的 post()方法,否则直接调用Runnable对象的run()方法。
源码下载地址:链接:https://pan.baidu.com/s/1ExoSxaQRdjmwUUnDuSbUIQ 提取码:43ka