1 序
基于Android9.0分析更新
2 概述
什么是消息机制呢?简单来说,Android消息机制是一套以“消息”为中介来实现线程之间的任务切换或同一线程中任务的按需执行的机制,其中涉及到消息的发送、存储消息、消息循环以及消息的分发和处理。本文将通过分析源码来进一步了解消息机制的内部实现方式。
Android消息机制涉所及到的类:

3 消息机制分析
3.1 我们先创建一个简单的Handler使用代码片段用于发送和处理消息
1 | |
上述代码片段发生了什么:
- 创建了一个Handler并复写了
handleMessage方法来处理消息 - 调用了
Message.obtain()来创建一个消息 - 调用了
mHandler.sendMessage(msg)发送消息 handleMessage(Message msg)收到发送的消息并进行处理
我们会按照这个执行流程一步一步分析消息机制
3.2 创建Handler
构造方法:
1 | |
Handler 的构造方法需要传入两个参数,第一个参数是 Handler.Callback 接口的实现,第二个参数是标志传递的 Message 是否是异步。第一个参数的作用我们后面会说到,第二个参数我们使用时一般使用的是同步消息,看默认构造方法也能知道,异步消息系统会在某些时候使用。
3.3 创建Message
此时Handler对象已经创建,按照上面的步骤我们看看Message.obtain()方法中发生了什么
1 | |
通过上面代码可以看到,Message内部维护了一个链表方式实现的缓存池用于降低创建Message的开销,默认最大的缓存数量MAX_POOL_SIZE = 50为50个,这就是为什么我们使用Handler发送消息时建议使用Message.obtain()方法创建消息的原因了。
3.4 sendMessage发送消息
接着我们来看看sendMessage(Message msg)方法发生了什么:
1 | |
sendMessage(Message msg)方法内部调用的实际上是sendMessageDelayed,最终会走到sendMessageAtTime然后调用enqueueMessage方法将消息放入消息队列MessageQueue并将handler和发送出去的message对象绑定。
通过上面我们已经知道Handler是如何将消息发送到MessageQueue中了,那么MessageQueue中的消息是如何分发出去的呢? 答案就是Looper。
3.5 Looper
用于为线程运行消息循环的类。默认情况下,线程没有与之关联的消息循环;创建一个,调用 prepare()运行循环的线程,然后 loop()让它处理消息,直到循环停止。
大多数与消息循环的交互是通过 Handler类进行的。 这是一个Looper线程实现的典型例子,使用分离prepare()和loop()创建初始Handler与Looper进行通信。
1 | |
上述是官方文档给予Looper的描述,通过代码片段我们可以看到创建Handler前需要调用Looper.prepare()实例化Looper,之后需要调用Looper.loop() 开启Looper循环分发消息。但是我们平常使用Handler过程中是不是都没有做这些操作呀,这是为什么呢?
我们来看下ActivityThread的Main方法,这是应用程序的入口:
1 | |
通过上面我们知道,系统早已经给我们准备好了主线程的Looper,所以我们平常在主线程使用Handler时不需要调用Looper.prepare()和Looper.loop()方法,但是如果在子线程创建Handler并使用,上面步骤是必不可少的,我们需要自己调用Looper.prepare()和Looper.loop()方法。
那我们来看看Looper.prepare()和Looper.loop()中做了些什么:
1 | |
Looper.prepare()内部做了3件事情,创建Looper,创建MessageQueue,将Looper对象保存在当前线程的ThreadLocal中Looper.loop()内部获取当前线程对应的Looper对象,然后开启个死循环来调用queue.next()获取当前Looper的MessageQueue对象的消息,有新消息则调用handler.dispatchMessage(msg)方法来分发消息,没有就阻塞。
3.6 MessageQueue
在Looper.loop()方法内部获取消息时调用的是message.nex()
1 | |
nativePollOnce(ptr, nextPollTimeoutMillis)是调用 native 层的方法执行阻塞操作,其中 nextPollTimeoutMillis 表示阻塞超时时长:
nextPollTimeoutMillis = 0 则不阻塞
nextPollTimeoutMillis = -1 则一直阻塞,除非消息队列被唤醒
延时消息发送也是基于这个机制。
这儿说下为什么在主线程中死循环没有造成线程卡死呢?这就涉及到Linux pipe/epoll机制,简单说就是在主线程的MessageQueue没有消息时,便阻塞在loop的queue.next()中的nativePollOnce()方法里,此时主线程会释放CPU资源进入休眠状态,直到下个消息到达或者有事务发生,通过往pipe管道写端写入数据来唤醒主线程工作。这里采用的epoll机制,是一种IO多路复用机制,可以同时监控多个描述符,当某个描述符就绪(读或写就绪),则立刻通知相应程序进行读或写操作,本质同步I/O,即读写是阻塞的。 所以说,主线程大多数时候都是处于休眠状态,并不会消耗大量CPU资源。
3.7 Handler处理消息
1 | |
经过前面分析,最终是走到Looper.loop()方法中,调用handler.dispatchMessage(msg)方法来分发消息,这儿就能看到我们创建Handler对象时Handler(Callback callback, boolean async)第一个参数的作用了,dispatchMessage方法内部会判断,有msg的callback直接run,这儿的msg.callback实际上是一个Runnable,没有的话,判断实例化时是否设置了Handler的callback,有则先回调处理这个消息,没有设置或消息未处理过会继续调用默认的handleMessage(msg)方法处理。
4 总结
- 主线程调用
Lopper.prepareMainLooper()方法创建当前线程的Looper对象(主线程中这一步由 Android 系统在应用启动时完成),子线程则需要自己调用Looper.prepare()创建对象和Looper.loop()方法开启循环。 - 创建
Looper对象时会内部会创建一个消息队列MessageQueue Looper通过loop()方法获取到当前线程的Looper并启动循环,从MessageQueue不断提取Message,若MessageQueue没有消息,处于阻塞状态。- 使用当前线程创建的
Handler在其它线程通过sendMessage()发送Message到MessageQueue MessageQueue插入新Message并唤醒阻塞,重新检查MessageQueue获取新插入的MessageLooper获取到Message后,通过Message的target 即Handler调用dispatchMessage(Message msg)方法分发提取到的Message,然后回收Message并继续循环获取下一个MessageHandler使用handlerMessage(Message msg)方法处理MessageMessageQueue没有Message时,重新进入阻塞状态