概述
从开发的角度来说,Handler 是 Android 消息机制的上层接口,这使得在开发过程中只需要和 Handler 交互即可。Handler 的使用过程很简单,通过它可以轻松地将一个任务切换到 Handler 所在的线程中去执行。很多人认为 Handler 的作用是更新 UI,这的确没错,但是更新 UI 仅仅是 Handler 的一个特殊的使用场景。具体来说是这样的:有时候需要在子线程中进行耗时的 I/O 操作,可能是读取文件或者访问网络等,当耗时操作完成以后可能需要在 UI 上做一些改变,由于 Android 开发规范的限制,我们并不能在子线程中访问 UI 控件,否则就会触发程序异常,这个时候通过 Handler 就可以将更新 UI 的操作切换到主线程中执行。因此,本质上来说,Handler 并不是专门用于更新 UI 的,它只是常被开发者用来更新 UI。
Android 的消息机制主要是指 Handler 的运行机制,Handler 的运行需要底层的 MessageQueue 和 Looper 的支撑。MessageQueue 的中文翻译是消息队列,顾名思义,它的内部存储了一组消息,以队列的形式对外提供插入和删除的工作。虽然叫消息队列,但是它的内部存储结构并不是真正的队列,而是采用单链表的数据结构来存储消息列表。Looper 的中文翻译为循环,在这里可以理解为消息循环。由于 MessageQueue 只是一个消息的存储单元,它不能去处理消息,而 Looper 就填补了这个功能,它负责的就是创建一个 MessageQueue,每个线程只能有一个 Looper,Looper 会以无限循环的形式从 MessageQueue 中读取消息,如果有新消息的话就把消息发送给 Handler 进行处理,而消息的创建者和处理者就是一个或多个 Handler,否则就一直等待着。Looper 中还有一个特殊的概念,那就是 ThreadLocal,ThreadLocal 并不是线程,它的作用是可以在每个线程中存储数据。Handler 创建的时候会采用当前线程的 Looper 来构造消息循环系统,那么 Handler 内部如何获取到当前线程的 Looper 呢?这就要使用 ThreadLocal 了,ThreadLocal 可以在不同的线程中互不干扰地存储并提供数据,通过 ThreadLocal 可以轻松获取每个线程的 Looper。当然需要注意的是,线程是默认没有 Looper 的,如果需要使用 Handler 就必须为线程创建 Looper。我们经常提到的主线程,也叫 UI 线程,它就是 ActivityThread,ActivityThread 被创建时就会初始化 Looper,这也是在主线程中默认可以使用 Handler 的原因。
Looper的两个重要方法
Looper 在 Android 的消息机制中扮演着消息循环的角色,具体来说就是它会不停地从 MessageQueue 中查看是否有新消息,如果有新消息就会立刻处理,否则就一直阻塞在那里。
Looper.prepare()
首先来看一下如何创建 Handler 对象。我们尝试在程序中创建两个 Handler 对象,一个在主线程中创建,一个在子线程中创建,代码如下所示:
1 | public class MainActivity extends Activity { |
运行程序会发现在子线程中创建的 Handler 是会导致程序崩溃的,提示的错误信息为 Can’t create handler inside thread that has not called Looper.prepare()。说不能在没有调用 Looper.prepare() 的线程中创建 Handler,那我们尝试在子线程中先调用一下 Looper.prepare(),代码如下所示:
1 | new Thread(new Runnable() { |
这样就不会崩溃了,看下 Handler 的源码,Handler 的无参构造函数如下所示:
1 | public Handler() { |
可以看到调用了 Looper.myLooper() 方法获取了一个 Looper 对象,如果 Looper 对象为空,则会抛出一个运行时异常,提示的错误正是 Can’t create handler inside thread that has not called Looper.prepare()。那什么时候 Looper 对象才可能为空呢?看看 Looper.myLooper() 中的代码,如下所示:
1 | public static final Looper myLooper() { |
这个方法非常简单,就是从 sThreadLocal 对象中取出 Looper。如果 sThreadLocal 中有 Looper 存在就返回 Looper,如果没有 Looper 存在自然就返回空了。
对于 Looper 主要是 prepare() 和 loop() 两个方法。在 Looper.prepare() 方法中给 sThreadLocal 设置 Looper,看下它的源码:
1 | public static final void prepare() { |
可以看到,首先判断 sThreadLocal 中是否已经存在 Looper 了,如果还没有则创建一个新的 Looper 设置进去。sThreadLocal 是一个 ThreadLocal 对象,可以在一个线程中存储变量。在第5行,将一个 Looper 的实例放入了 ThreadLocal,并且2-4行判断了 sThreadLocal 是否为 null,否则抛出异常。也就完全解释了为什么我们要先调用 Looper.prepare() 方法,才能创建 Handler 对象。同时也可以看出 Looper.prepare() 方法不能被调用两次,同时也保证了一个线程中只有一个 Looper 实例。
主线程中的 Handler 也没有调用 Looper.prepare() 方法,为什么没有崩溃?这是由于在程序启动的时候,系统已经帮我们自动调用了 Looper.prepare() 方法。查看 ActivityThread 中的 main() 方法,代码如下所示:
1 | public static void main(String[] args) { |
可以看到,在第7行调用了 Looper.prepareMainLooper() 方法,而这个方法又会再去调用 Looper.prepare() 方法,代码如下所示:
1 | public static final void prepareMainLooper() { |
因此应用程序的主线程中会始终存在一个 Looper 对象,从而不需要再手动去调用 Looper.prepare() 方法了。
1 | 在主线程中可以直接创建 Handler 对象,而在子线程中需要先调用 Looper.prepare() 才能创建 Handler 对象。 |
由上面可知 Looper 除了 prepare 方法外,还提供了 prepareMainLooper 方法,这个方法主要是给主线程也就是 ActivityThread 创建 Looper 使用的,其本质也是通过 prepare 方法来实现的。由于主线程的 Looper 比较特殊,所以 Looper 提供了一个 getMainLooper 方法,通过它可以在任何地方获取到主线程的 Looper。Looper 也是可以退出的,Looper 提供了 quit 和 quitSafely 来退出一个 Looper,二者的区别是:quit 会直接退出 Looper,而 quitSafely 只是设定一个退出标记,然后把消息队列中的已有消息处理完毕后才安全地退出。Looper 退出后,通过 Handler 发送的消息会失败,这个时候 Handler 的 send 方法会返回 false。在子线程中,如果手动为其创建了 Looper,那么在所有的事情完成以后应该调用 quit 方法来终止消息循环,否则这个子线程就会一直处于等待的状态,而如果退出Looper以后,这个线程就会立刻终止,因此建议不需要的时候终止 Looper。
Looper.loop()
只有调用了 loop 后,消息循环系统才会真正地起作用,它的实现如下所示。下面先看 Looper 的构造方法:
1 | private Looper(boolean quitAllowed) { |
在构造方法中,创建了一个 MessageQueue,一个 Looper 也对应一个 MessageQueue。然后看 loop() 方法:
1 | public static void loop() { |
第2行:
1 | public static Looper myLooper() { |
方法直接返回了 sThreadLocal 存储的 Looper 实例,如果 me 为 null 则抛出异常,也就是说 loop 方法必须在 prepare 方法之后运行。
第6行:拿到该 looper 实例中的 mQueue(消息队列)。
13到45行:就进入了我们所说的无限循环。
14行:取出一条消息,这个 next() 方法就是获取消息的方法,它的简单逻辑就是如果当前 MessageQueue 中存在 mMessages (即待处理消息),就取出这个消息,然后让下一条消息成为 mMessages,如果没有消息则阻塞,一直等到有新的消息进来。
27行:调用 msg.target.dispatchMessage(msg) 把消息交给 msg 的 target 的 dispatchMessage 方法去处理。Msg 的 target 其实就是 handler 对象,下面会进行分析。
44行:释放消息占据的资源。
Looper 主要作用:
- 与当前线程绑定,保证一个线程只会有一个 Looper 实例,同时一个 Looper 实例也只有一个 MessageQueue。
- loop() 方法,不断从 MessageQueue 中去取消息,交给消息的 target 属性的 dispatchMessage 去处理。
现在异步消息处理线程已经有了消息队列(MessageQueue),也可以在无限循环体中取出消息,现在缺的就是发送消息的对象了,于是乎Handler登场了。
Handler 消息发送与 MessageQueue
Handler 的工作主要包含消息的发送和接收过程。消息的发送可以通过 post 的一系列方法以及 send 的一系列方法来实现,post 的一系列方法最终是通过 send 的一系列方法来实现的。在 Looper.prepare() 一节中已经介绍了 Handler 的创建,使用 Handler 之前,我们都是初始化一个实例,比如用于更新 UI 线程,会在声明的时候直接初始化,或者在 onCreate 中初始化 Handler 实例。发送消息的流程已经非常熟悉:new 出一个 Message 对象,然后可以使用 setData() 方法或 arg 参数等方式为消息携带一些数据,再借助 Handler 将消息发送出去就可以了,示例代码如下:
1 | new Thread(new Runnable() { |
这里 Handler 到底把 Message 发送到哪里去了呢?为什么之后又可以在 Handler 的 handleMessage() 方法中重新得到这条 Message 呢?Handler 中提供了很多个发送消息的方法,其中除了 sendMessageAtFrontOfQueue() 方法之外,其它的发送消息方法最终都会辗转调用到 sendMessageAtTime() 方法中。我们再看 Handler 的构造方法,看其如何与 MessageQueue 联系上的,它在子线程中发送的消息(一般发送消息都在非 UI 线程)怎么发送到 MessageQueue 中的。
1 | public Handler(Callback callback, boolean async) { |
11行:通过Looper.myLooper()获取了当前线程保存的 Looper 实例,然后在16行又获取了这个 Looper 实例中保存的 MessageQueue(消息队列),这样就保证了 handler 的实例与 Looper 实例中 MessageQueue 关联上了。
另外,Handler 还有一个特殊的构造方法,那就是通过一个特定的 Looper 来构造 Handler,它的实现如下所示。通过这个构造方法可以实现一些特殊的功能。
1 | public Handler(Looper looper) { |
然后看最常用的 sendMessage 方法:
1 | public final boolean sendMessage(Message msg) { |
1 | public final boolean sendEmptyMessageDelayed(int what, long delayMillis) { |
1 | public final boolean sendMessageDelayed(Message msg, long delayMillis) { |
1 | public boolean sendMessageAtTime(Message msg, long uptimeMillis) { |
sendMessageAtTime() 方法接收两个参数,其中 msg 参数就是我们发送的 Message 对象,而 uptimeMillis 参数则表示发送消息的时间,它的值等于自系统开机到当前时间的毫秒数再加上延迟时间,如果你调用的不是 sendMessageDelayed() 方法,延迟时间就为0。enqueueMessage()方法毫无疑问就是入队的方法了,我们来看下这个方法的源码:
1 | private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) { |
enqueueMessage 中首先为 msg.target 赋值为 this,(如果还记得 Looper 的 loop 方法会取出每个 msg 然后交给 msg.target.dispatchMessage(msg) 去处理消息),也就是把当前的 handler 作为 msg 的 target 属性。最终会调用 queue 的 enqueueMessage 的方法,也就是说 handler 发出的消息,最终会保存到消息队列中去。
queue.enqueueMessage 方法源码如下:
1 | final boolean enqueueMessage(Message msg, long when) { |
消息队列在 Android 中指的就是 MessageQueue,MessageQueue 主要包含两个操作:插入和读取。读取操作本身会伴随着删除操作,插入和读取对应的方法分别为 enqueueMessage 和 next,其中 enqueueMessage 的作用是往消息队列中插入一条消息,而 next 的作用是从消息队列中取出一条消息并将其从消息队列中移除。尽管 MessageQueue 叫消息队列,但是它的内部实现并不是用的队列,实际上它是通过一个单链表的数据结构来维护消息列表,单链表在插入和删除上比较有优势。
MessageQueue 并没有使用一个集合把所有的消息都保存起来,它只使用了一个 mMessages 对象表示当前待处理的消息。然后观察上面的代码的16~31行就可以看出,所谓的入队其实就是将所有的消息按时间来进行排序,这个时间就是刚才介绍的 uptimeMillis 参数。具体的操作方法就根据时间的顺序调用 msg.next,从而为每一个消息指定它的下一个消息是什么。当然如果是通过 sendMessageAtFrontOfQueue() 方法来发送消息的,它也会调用 enqueueMessage() 来让消息入队,只不过时间为0,这时会把 mMessages 赋值为新入队的这条消息,然后将这条消息的 next 指定为刚才的 mMessages,这样也就完成了添加消息到链表头部的操作。
Handler 发送消息的过程仅仅是向消息队列中插入了一条消息。Looper 会调用 prepare() 和 loop() 方法,在当前执行的线程中保存一个 Looper 实例,这个实例会保存一个 MessageQueue 对象,然后当前线程进入一个无限循环中去,不断从 MessageQueue 中读取 Handler 发来的消息(MessageQueue 的 next 方法返回这条消息给 Looper)。最终消息由 Looper 交由 Handler 处理,即 Handler 的 dispatchMessage 方法会被调用,这时 Handler 就进入了处理消息的阶段。下面我们去看一看这个方法:
1 | public void dispatchMessage(Message msg) { |
首先检查 Message 的 callback 是否为 null,不为 null 就通过 handleCallback 来处理消息。Message 的 callback 是一个 Runnable 对象,实际上就是 Handler 的 post 方法所传递的 Runnable 参数。
然后在第5行进行判断,如果 mCallback 不为 null,则调用 mCallback 的 handleMessage() 方法来处理消息。Callback 是个接口,它的定义如下:
1 | /** |
通过 Callback 可以采用如下方式来创建 Handler 对象:
1 | Handler handler = new Handler(callback); |
那么 Callback 的意义是什么呢?源码里面的注释已经做了说明:可以用来创建一个 Handler 的实例但并不需要派生 Handler 的子类。在日常开发中,创建 Handler 最常见的方式就是派生一个 Handler 的子类并重写其 handleMessage 方法来处理具体的消息,而 Callback 给我们提供了另外一种使用 Handler 的方式,当我们不想派生子类时,就可以通过 Callback 来实现。
最后,调用 Handler 的 handleMessage() 方法,并将消息对象作为参数传递过去。handleMessage 方法源码如下:
1 | /** |
可以看到这是一个空方法,因为消息的最终回调是由我们控制的,我们在创建 handler 的时候都是复写 handleMessage 方法,然后根据 msg.what 进行消息处理。例如:
1 | private Handler mHandler = new Handler() { |
Handler 处理消息的过程可以归纳为如下流程图:
流程总结
一个最标准的异步消息处理线程的写法应该是这样:
1 | class LooperThread extends Thread { |
到此,这个流程已经解释完毕,总结一下:
- 首先 Looper.prepare() 在本线程中保存一个 Looper 实例,然后该实例中保存一个 MessageQueue 对象;因为 Looper.prepare() 在一个线程中只能调用一次,所以 MessageQueue 在一个线程中只会存在一个。
- Looper.loop() 会让当前线程进入一个无限循环,不断从 MessageQueue 的实例中读取消息,然后回调 msg.target.dispatchMessage(msg) 方法。
- Handler 的构造方法,会首先得到当前线程中保存的 Looper 实例,进而与 Looper 实例中的 MessageQueue 相关联。
- Handler 的 sendMessage 方法,会给 msg 的 target 赋值为 handler 自身,然后加入 MessageQueue 中。
- 在构造 Handler 实例时,我们会重写 handleMessage 方法,也就是 msg.target.dispatchMessage(msg) 最终调用的方法。
整个异步消息处理流程的示意图如下两张图所示:
在子线程中进行 UI 操作的其它方法
除了发送消息之外,我们还有以下几种方法可以在子线程中进行UI操作:
- Handler 的 post() 方法
- View 的 post() 方法
- Activity 的 runOnUiThread() 方法
先来看下 Handler 中的 post() 方法,有时候为了方便,我们会直接写如下代码:
1 | mHandler.post(new Runnable() { |
然后 run 方法中可以写更新 UI 的代码,其实这个 Runnable 并没有创建线程,而是发送了一条消息,下面看源码:
1 | public final boolean post(Runnable r) { |
这里调用 sendMessageDelayed() 方法去发送一条消息,并且还使用了 getPostMessage() 方法将 Runnable 对象转换成了一条消息,看下这个方法的源码:
1 | private final Message getPostMessage(Runnable r) { |
可以看到,在 getPostMessage 中,得到了一个 Message 对象,然后将我们创建的 Runable 对象作为 callback 属性,赋值给了此 message。
另外,产生一个 Message 对象,可以 new,也可以使用 Message.obtain() 方法;两者都可以,但是更建议使用 obtain 方法,因为 Message 内部维护了一个 Message 池用于 Message 的复用,避免使用 new 重新分配内存。
Handler 的 post() 方法最终和 handler.sendMessage 一样,调用了 sendMessageAtTime,然后调用了 enqueueMessage 方法,给 msg.target 赋值为 handler,最终加入 MessagQueue。
getPostMessage 方法中将消息的 callback 字段的值指定为传入的 Runnable 对象,在 Handler 的 dispatchMessage() 方法中有做一个检查,如果 Message 的 callback 等于 null 才会去调用 handleMessage() 方法,否则就调用 handleCallback() 方法,也就是我们的Runnable对象:
1 | public void dispatchMessage(Message msg) { |
看下 handleCallback() 方法中的代码:
1 | private final void handleCallback(Message message) { |
就是直接调用了一开始传入的 Runnable 对象的 run() 方法。因此在子线程中通过 Handler 的 post() 方法进行 UI 操作完整写法如下:
1 | public class MainActivity extends Activity { |
虽然写法上相差很多,但是原理是完全一样的,我们在 Runnable 对象的 run() 方法里更新 UI,效果完全等同于在 handleMessage() 方法中更新 UI。
然后再来看一下 View 中的 post() 方法,代码如下所示:
1 | public boolean post(Runnable action) { |
原来就是调用了 Handler 中的 post() 方法。
最后再来看一下 Activity 中的 runOnUiThread() 方法,代码如下所示:
1 | public final void runOnUiThread(Runnable action) { |
如果当前的线程不等于 UI 线程(主线程),就去调用 Handler的 post() 方法,否则就直接调用 Runnable 对象的 run() 方法。
通过以上所有源码的分析,我们已经发现了,不管是使用哪种方法在子线程中更新 UI,其实背后的原理都是相同的,必须都要借助异步消息处理的机制来实现。
参考资料:
guolin Android异步消息处理机制完全解析,带你从源码的角度彻底理解
鸿洋_ Android 异步消息处理机制 让你深入理解 Looper、Handler、Message三者关系
《Android 开发艺术探索》任玉刚 第10章 Android的消息机制