提到消息机制大家都不陌生,因为在日常开发中少不了和它打交道,郭婶也有过相关的篇章来讲述:Android异步消息处理机制完全解析,带你从源码的角度彻底理解,看完不禁感叹写得很棒很适用。说实话,郭婶这篇文章看了不下 3 遍,甚至还打印出来认真仔细研究,每次都会随笔记点东西,但都是零零散散的,这次结合《Android 开发艺术探索》来记录自己的学习过程。
Android 规定访问 UI 只能在主线程中进行,如果在子线程中访问 UI 就会出现异常,这是为什么呢?
Android 系统为什么不允许子线程中访问 UI 呢?这是因为 Android 的 UI 控件是线程不安全的,如果在多线程中并发访问可能会导致 UI 控件处于不可预期的状态。
Android 消息机制的解析通过上面郭婶的文章就差不多了,但是这次我再来各个击破。
Handler 工作原理
Handler 提供了一系列的 send() 和 post() 方法,post() 系列的方法最终都会调用 send() 系列的方法来发送消息。在一系列的 send() 方法中除了 sendMessageAtFrontOfQueue() 方法之外,其它的发送消息方法都会辗转调用到 sendMessageAtTime() 方法,但是最终都会调用 Handler 本身的 enqueueMessage() 方法。下面看看这几个方法:
1 | // Handler.sendMessageAtTime() |
在 enqueueMessage() 方法中首先把当前的 handler 对象存储起来:msg.target = this
,然后再调用 MessageQueue 的 enqueueMessage() 方法将消息入队列,这就是通过 Handler 发送消息。
当调用 Looper.loop()
时就将消息出队列,最终消息由 Looper 交给 Handler 处理,即调用 Handler 的 dispatchMessage() 方法。dispatchMessage() 方法的实现如下:
1 | public void dispatchMessage(Message msg) { |
首先检查 Message 的 callback 即 msg.callback
是否为 null,不为 null 就通过 handleCallback() 方法来处理消息。msg.callback
是一个 Runnable 对象,实际上就是通过 Handler 的 post 方法所传递的 Runnable 参数。handleCallback() 方法的逻辑也很简单:
1 | private static void handleCallback(Message message) { |
所以通过 post() 方法来发送消息就可以写成下面这样:
1 | Handler handler = new Handler(); |
这样一来对 handleCallback() 方法的理解就更深刻了。回到 dispatchMessage() 方法继续判断 mCallback 是否为 null,如果不为 null 就调用 mCallback.handleMessage()
来处理消息,Callback 的定义如下:
1 | public interface Callback { |
Callback 是个接口,它的意义是可以用来创建一个 Handler 实例而不需要派生 Handler 的子类。来个例子最直观:
1 | class callback implements Handler.Callback { |
到这 mCallback.handleMessage()
也搞清楚了。这样 dispatchMessage() 到最后就调用 Handler 的 handlerMessage() 方法来处理消息。
至此通过 Handler 发送消息和处理消息算是过了一遍了。刚刚有说到 Handler 的 enqueueMessage() 方法最终是调用 MessageQueue 的 enqueueMessage() 方法来入队列的,接下来就看看消息是怎么入队列的。
MessageQueue 工作原理
MessageQueue 被称为消息队列,它主要包含两个操作:插入( enqueueMessage )和删除( next ),但是实际上 MessageQueue 内部是通过一个单链表的数据结构来维护消息的,因为单链表在插入和删除上比较有优势。
enqueueMessage 的作用是往消息队列中插入一条消息;而 next 的作用是从消息队列中取出一条消息并将其从消息队列中移除,因为 MessageQueue 的读取操作本身会伴随着删除操作。下面来看看 MessageQueue 的 enqueueMessage() 方法:
1 | boolean enqueueMessage(Message msg, long when) { |
enqueueMessage() 方法的主要操作就是单链表的插入操作,它并没有把所有消息都存储起来,而是用 mMessage 来表示当前待处理的消息,然后根据时间顺序调用 msg.next
来指定下一条消息。
在 Handler 部分有说到当调用 Looper.loop()
时会将消息出队列,实际上 loop() 方法里面是调用了 MessageQueue 的 next() 方法来将消息出队列,看看 next() 方法:
1 | Message next() { |
next() 方法里面有一个无限循环的方法,如果 MessageQueue 中没有消息,那么 next() 方法就会一直阻塞在这,直到有新的消息到来;如果 MessageQueue 中存在 mMessage (待处理消息),就将其出队列并将其从单链表中移除,然后把下一条消息赋值给 mMessage。
所以 MessageQueue 才是最终将消息入队列和出队列的,那就再来看看 Looper.loop()
的实现。
Looper 工作原理
Looper 在 Android 消息机制中扮演着消息循环的角色,具体来说就是会不停地从 MessageQueue 中查看是否有新消息,如果有新消息就立刻处理,否则就会一直阻塞在那里。下面是 loop() 方法:
1 | public static void loop() { |
首先判断 myLooper() 方法的返回值,如果返回值为 null 就抛出异常,意味着该线程还没有 Looper,否则就往下执行。
接着会无限循环的调用 queue.next()
,所以 MessageQueue 的 next() 方法就是在这被调用的。每当有一个消息出队列后就将其传递到 msg.target 的 dispatchMessage() 方法中, msg.target 已经说过了,它其实就是 Handler,这样通过 Handler 发送的消息又回到了它自己的 dispatchMessage() 方法中。
唯一跳出死循环的时机是 MessageQueue 的 next() 方法返回 null。其实 Looper 提供了 quit() 和 quitSafely() 两个方法来退出一个 Looper,这两个方法的区别是:quit() 方法会直接退出 Looper;而 quitSafely() 方法只是设定一个退出标记,当消息队列中已有的消息处理完后才安全退出。
当 Looper 的 quit() 方法或 quitSafely() 方法被调用时,这两个方法内部就会去调用 MessageQueue 的 quit() 方法来通知消息队列退出,此时 MessageQueue 的 next() 方法就会返回 null。因此建议在不需要 Looper 的时候通过 quit() 或 quitSafely() 方法终止 Looper。
刚刚说到 myLooper() 方法,现在看看 myLooper() 方法:
1 | public static Looper myLooper() { |
sThreadLocal.get()
是个什么?请继续往下看:
ThreadLocal 工作原理
用这么一句话来描述 ThreadLocal 再适合不过:ThreadLocal 是一个线程内部的数据存储类,同一个 ThreadLocal 对象在不同线程中拥有不同的值。
其实大家都知道,在子线程中创建 Handler 时必须先执行 Looper.prepare()
,因为在 Handler 的无参构造方法中会通过 (Looper)sThreadLocal.get()
检查是否存在 looper,如果不存在就会抛异常。调用 prepare() 方法正是因为该方法内部会通过 sThreadLocal.set(new Looper())
给当前线程设置 looper。
先来个小例子见识下 ThreadLocal 的厉害之处:
1 | public class ThreadLocalActivity extends AppCompatActivity { |
很简单的小例子,先创建出一个 ThreadLocal 对象:mThreadLocal,然后分别在三个线程中对它进行操作,按照刚刚那句话,打印的结果应该是:主线程的 mThreadLocal 是 true,子线程 1 的 mThreadLocal 是 false,子线程 2 由于没有赋值所以应该是 null 。实际的结果怎样呢?来看看:
结果当然在意料之中,因为在此之前已经先试验过了,这就是 ThreadLocal 的奇妙之处。知其然知其所以然,接下来看看它的内部实现,首先看看 set() 方法:
1 | public void set(T value) { |
这个方法很简洁,首先获取当前线程,然后通过 values() 方法获取当前线程的 ThreadLocal 数据。为什么这么说呢?先来看看 values() 方法:
1 | Values values(Thread current) { |
localValues 又是什么呢?其实它是 Thread 类内部的一个成员,localValues 内部有一个数组:private Object[] table
,这个 table 数组才是真正存储数据的结构,ThreadLocal 的值就存在这个 table 数组中。所以实际上通过 values() 获取到的是 localValues 对象,而 localValues 里面的 table 数组才真正拥有 ThreadLocal 的数据。
回到 set() 方法中,如果拿到的 values 的值为 null,那就对其进行初始化,初始化后再通过 put() 方法进行存储。继续看 put() 方法:
1 | void put(ThreadLocal<?> key, Object value) { |
这里只关注 put() 方法的存储规则,可以发现 ThreadLocal 的值存在 table 数组中 reference 的下一个位置。比如 reference 在 table 数组中的索引为 index,那么 ThreadLocal 的值就存在 table 数组中索引为 index + 1 的位置。
看完 set() 方法后再来看看 get() 方法:
1 | "unchecked") ( |
get() 方法同样先是获取当前 localValues 对象,如果该对象不为 null,那就取出它的 table 数组并且找到 reference 在 table 数组中的位置,然后把 table 数组中 reference下一个位置的数据返回,因为该数据就是当前线程中 ThreadLocal 的值;如果 localValues 对象为 null 则返回初始值,默认情况下为 null。
ThreadLocal 的 set() 和 get() 方法操作的对象都是当前线程的 table 数组,因此也印证了刚刚对 ThreadLocal 的描述。理解完 ThreadLocal,相信对上面 Looper 工作原理的理解又会再次加深。
到这里才几乎算是把 Android 的消息机制走了一遍,最后贴上时序图一张来理解这个过程(刚开始学习使用时序图):
通过这个时序图可以很清晰的知道 一个线程对应一个 Looper 对象,一个 Looper 对象对应一个 MessageQueue 对象。为什么使用异步消息处理的方式就可以对 UI 进行操作了呢?这是因为 Handler总是依附于创建时所在的线程。所以在子线程中进行耗时操作后通过 Handler 把消息发送出去,最后消息又到了主线程中 Handler 的 handlerMessage() 方法进行 UI 操作。