前言
Handler 消息处理机制在 Android 开发中起着举足轻重的作用,有必要好好理解下其原理,先前我写了一篇文章,感觉疏漏了好多东西,下面先从一个简单的例子出发。
日常使用
假设有这么一个需要,请求网络然后将图片展示出来,我们知道网络请求是不允许在主线程执行的,而 UI 是不能在子线程(具体是不允许在非创建 UI 的原始线程)更新的,因此我们需要在子线程请求网络获得了数据以后再切换回主线程更新 UI ,这个例子中 Handler 就是起着切换线程的作用,下面的代码演示了这个例子。
class MainActivity : AppCompatActivity() { |
咦!说好的Handler去哪了?其实这里的 runOnUIThread 方法内部实现其实就是利用了 Handler,我们来看看它的源码。
public final void runOnUiThread(Runnable action) { |
该方法首先判断了当前线程是否是主线程,如果不是主线程就调用 mHandler.post()
,如果当前线程就是主线程就直接运行,下面我们来分析看看 Handler 的原理。
Handler 原理
要想分析 Handler 的原理,我们先从 Handler 的创建过程开始分析。
Handler 的创建
Activity 的这个 mHandler 是怎么来的呢?原来 mHandler 是 Activity 的成员变量,在 Activity 实例创建的时候就创建了。
final Handler mHandler = new Handler(); |
接着看看 Handler 的构造方法
public Handler() { |
首先如果 FIND_POTENTIAL_LEAKS 为 true 那么判断该 Handler 派生类是否是非静态内部类,如果是的话就打印出日志提示可能导致内存泄露,然后调用了 Looper.myLooper
获取到当前线程的 Looper 对象,如果当前线程没有 Looper 就会抛出异常,最后将 Looper 中的 MessageQueue 对象赋值给 mQueue
,callback 赋值给 mCallback
,aync 赋值给 mAsynchronous
,我们来看看 Looper.myLooper
做了些什么。
public static Looper myLooper() { |
从 ThreadLocal 里面取 Looper ,那么是在哪里把 Looper 设置到 ThreadLocal 里面去的呢?其实 Looper 提供了 prepare
方法来创建当前线程的 Looper,我们来看看代码。
public static void prepare() { |
只有在当前线程拿不到 Looper 的时候才会去创建 Looper 对象并将其设置到 ThreadLocal 中去,不然就抛出异常说一个线程只能拥有一个 Looper,继续看看 Looper 的构造方法。
// 这里的 quitAllowed 是 true |
又创建了一个 MessageQueue 对象,继续看看它的构造方法。
// 这里的 quitAllowed 是 true |
调用了一个 Native 方法就结束了,其实 mPtr 是 NativeMessageQueue 与 MessageQueue 之间的桥梁,暂时不看 native 层代码。
源码看到这里就会产生一个疑问,既然创建 Handler 的时候判断了当前线程的 Looper 是否为 null,为 null 就会抛出异常,那么 Activity 的 Handler 是怎么创建成功的呢?其实在 Activity 实例创建前主线程就已经有 Looper 对象了,这个得从 ActivityThread 开始说起。ActivityThread 是一个应用程序的入口里面有一个 main 方法,我们来看看。
public static void main(String[] args) { |
Looper.loop()
后面会讲到先忽略,main
方法内部调用了 Looper.prepareMainLooper()
这个方法跟上面讲到的 Looper.prepare()
有什么异同点呢?我们来看看它的源码。
public static void prepareMainLooper() { |
prepare
方法前面已经分析过了但是主线程是不允许退出的,所以传入了 false,后面判断了如果 sMainLooper 不为空那么就抛出异常,至此主线程的 Looper 创建成功这也就解释了为什么 Activity 中可以直接创建 Handler,接着我们分析那个 post
方法干了些什么事情。
Handler 的消息发送
Handler提供了很多方法用于发送消息,比如以下几种:
- sendEmptyMessage(int what) 发送一个空消息,what 用于判断消息类别。
- sendEmptyMessageDelayed(int what, long delayMillis) 发送一个空消息,延迟 delayMillis 毫秒执行,what 用于判断消息类别。
- sendEmptyMessageAtTime(int what, long uptimeMillis) 发送一个空消息,在 uptimeMillis 的时候执行,what 用于判断消息类别。
- sendMessageDelayed(Message msg, long delayMillis) 发送一个消息,延迟 delayMillis 毫秒执行
- sendMessageAtTime(Message msg, long uptimeMillis) 发送一个消息,在 uptimeMillis 的时候执行
- sendMessageAtFrontOfQueue(Message msg) 发送一个消息,该消息会排在消息队列的队首
- executeOrSendMessage(Message msg) 如果 Handler 中的 Looper 与当前线程的 Looper 一致就直接分发消息,不然就发送一个消息。
继续着看 post
方法的实现
public final boolean post(Runnable r) { |
其实post
方法内部也就是发送了一个消息
private static Message getPostMessage(Runnable r) { |
最终调用到了 sendMessageAtTime
,其实几乎所有发送消息的方法最终都会调用到该方法,继续看 enqueueMessage
的实现。
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) { |
这里将本 Handler 的实例赋值给了 msg.target
,这个很重要以后会用到,然后判断下当前 Handler 是否是异步的,是的话就将消息设置成异步,我们这里不是异步的,接着继续看 enqueueMessage
。
boolean enqueueMessage(Message msg, long when) { |
该方法首先判断了 msg.target
是否为空,这个我们刚才看到已经设置了,然后判断 msg
是否正在被使用,然后再判断消息队列是否已经退出了,如果已经退出了就将 msg
回收并抛出个异常,下面那个同步代码块其实处理的逻辑就是将 msg
放入到消息队列中去,插入过程分为以下两步,至于 needWake
是用于判断是否要唤醒处于nativePollOnce
而阻塞的 Message.next
方法:
- 如果满足
p == null || when == 0 || when < p.when
其实也就是如果消息队列的头指针为空,或者当前消息的执行时间为0,或者当前消息的执行时间先与消息队列队首的执行时间,那么将当前msg
当做头指针。 - 如果不满足第一种情况就根据当前
msg.when
决定插入的位置。
现在已经将消息放到的消息队列中,但是什么时候这个消息才能得到执行呢?这就要看看前面跳过的 ActivityThread 的 main
方法中的 Looper.loop()
。
public static void loop() { |
这个方法代码有点长,主要流程如下
- 首先判断一下当前线程是否包含 Looper 不包含就抛出异常。
- 调用 MessageQueue 的
next
方法获取 Message,如果返回了null,标志了 MessageQueue 已经退出了,所以 Looper 也要退出。 - 获取 Looper 中的
mLogging
用于打印日志,我们可以通过setMessageLogging
进行设置,设置后每次收到消息和消息处理完毕都会有日志我们可以根据这些日志分析 ANR 是由于处理哪个消息超时造成的。 - 设置慢分发时间和慢交付时间,可以通过 adb 进行设置,慢分发时间表示如果这个消息的实际执行时间比其设置的
slowDeliveryThresholdMs
要长就会打印警告日志,慢交付时间表示这个消息从消息队列取出时间比其设置的when
超过slowDispatchThresholdMs
就会打印警告日志。 - 记录开始分发的时间。
- 调用
msg.target.dispatchMessage
进行分发消息,其中msg.target
就是一个 Handler 实例,上文说到过的。 - 记录结束分发的时间。
- 根据实际情况打印日志。
- 回收
msg
。
loop
方法调用 queue.next
取出消息,我们来看看该方法的实现
Message next() { |
- 调用
nativePollOnce
,阻塞等待下一个可执行消息,参数为等待时间 -1 表示永久。 - 判断第一个消息的
target
是否为空,如果不为空表示是一个普通的消息,如果为空则表示是一个同步屏障消息(在屏幕刷新的时候会发送),遍历消息队列找到第一个异步消息赋值给msg
。 - 判断
msg
是否为空,如果为空那么进行无超时的等待,直到被唤醒。 - 判断
msg
是否到了执行时间,如果不到就执行阻塞等待msg.when - now
,如果已经到了就将该消息返回 - 如果消息队列已经退出就返回 null。
总结下 Looper.loop 的原理,其实就是分为以下步骤:
Looper.loop 死循环执行 MessageQueue.next 。
MessageQueue.next 首先 blocked 为 false 、nextPollTimeoutMillis 为 0,于是立即脱离阻塞,获取 Message 链表中的第一个消息,这里分情况讨论。
- 获取不到 Message ,那么 blocked 为 true ,nextPollTimeoutMills 为 -1,无限制的阻塞在 nativePollOnce,等待 MessageQueue.enqueueMessage 将它唤醒。
- 获取到 Message ,比较当前时间与 Message.when 如果发现还没到执行时间,那么 blocked 为 true,nextPollTimeoutMillis 为 当前时间 - Message.when,在 nativePollOnce 那里等待一会儿。
- 获取到 Message,比较当前时间与 Message.when 如果发现到了执行时间,那么直接将该 Message 返回给 Looper.next ,其执行 Message.target.dispatchMessage。执行完毕后再次回到第 2 步。
MessageQueue.enqueueMessage
Message 需要插入到链首,是否唤醒由
blocked
字段决定,如果当前其为 false,那么表示其还没阻塞,它自己会从队列中取,不需要唤醒,如果其为 true ,表示其由于 链表无元素或者链表首部元素执行时间未到,因为当前 Message 是插入首部的,于是得唤醒,判断当前 Message 是否可以执行。Message 需要插入到链表中间,如果不考虑同步屏障,那么是不需要唤醒的。
拿到了消息以后就调用了 handler.dispatchMessage
我们来看看其实现
public void dispatchMessage(Message msg) { |
首先判断是否该 Message 是否设置了 callBack,设置了就直接运行,然后判断 Handler 是否设置了 callBack,设置了就调用 callback.handleMessage
如果返回 false,继续调用 handleMessage
。
IdleHandler 用法
先说说作用,其可以在 Looper 所在线程空闲时执行某些操作来提高性能,不过单次执行不要太耗时,不然后续事件可能被延迟了。
public void addIdleHandler(@NonNull IdleHandler handler) { |
从上述代码可以看到,如当前 MessageQueue 为空 或者 MessageQueue 的首个 Message 的执行事件还没到,本来这两种情况都需要 nativePollOnce 阻塞等待的,如果有 IdleHandler 那么会依次其 queuIdle 方法,如果返回 false,那么会自动移除掉,注意:执行完一次 IdleHandler 后,需要等到下次再调用 MessageQueue.next 才有可能再次执行。
一般也就是用于 Activity 启动优化将 onCreate,onStart,onResume 中耗时较短但非必要的代码可以放到 IdleHandler 中执行,减少启动时间。
总结
- Handler 的作用就是把想要执行的操作从当前线程切换到 Handler 中 Looper 所在的线程进行执行。
- 每次通过 Handler 发送消息其实就是把消息插入到了消息队列中,然后根据情况判断是否要唤醒处于调用
nativePollOnce
阻塞状态的线程。