消息循环机制
我们都知道,Android应用程序是通过消息来驱动的,整个机制是围绕着消息的产生以及处理而展开的。消息机制的三大要点:消息队列、消息循环(分发)、消息发送与处理。
1. 消息队列
Android应用程序线程的消息队列是使用一个MessageQueue对象来描述的,它可以通过调用Looper类的静态成员函数prepareMainLooper
或者prepare
来创建,其中,前者用来为应用程序的主线程创建消息队列;后者用来为应用程序的其它子线程创建消息队列。
- 创建消息队列
prepareMainLooper
和prepare
的实现:
|
|
不管是在主线程中prepare还是在其它线程中,最终调用的方法都是prepare(boolean quitAllowed)
方法,进一步来看下具体实现。
|
|
程序最后一行中,为当前的线程创建唯一的loop对象。
loop的构造方法如下:
|
|
程序到了上面后开始创建消息队列,MessageQueue的构造方法如下:
|
|
可以发现java层的MessageQueue是由JNI层的nativeInit
方法实现的。
到了JNI层IDE上面就看不到具体实现了,这里推荐一个在线的源码阅读地址:点击查看
在/frameworks/base/core/jni/android_os_MessageQueue.cpp
找到android_os_MessageQueue.cpp
文件,这里我们先看nativeInit
方法的实现
|
|
在C++层,实现Java层的MessageQueue新建了NativeMessageQueue与java层的相关联,并将生成的nativeMessageQueue的内存地址返回到java层。
在NativeMessageQueue的构造方法中,新建了JNI层的loop对象:
|
|
找到路径/system/core/libutils/Looper.cpp
,我们来看Looper的具体实现。
|
|
上述代码中创建的管道非常的重要,它又两个文件描述符:mWakeReadPipeFd
(管道读端文件描述符)和mWakeWritePipeFd
(管道写端描述符)。首先,当一个线程没有新的消息处理时,它就会睡眠在这个管道的读端文件描述符上,直到有新的消息需要处理为止;其次,当其它线程向这个线程的消息队列发送一个消息之后,其它线程就会通过这个管道的写端文件描述符往这个管道写入数据,,从而将这个线程唤醒,以便它可以对刚才发送到它的消息队列中的消息进行处理。
epoll是Linux内核为处理大批量文件描述符而作了改进的poll,是Linux下多路复用IO接口select/poll的增强版本,它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率。另一点原因就是获取事件的时候,它无须遍历整个被侦听的描述符集,只要遍历那些被内核IO事件异步唤醒而加入Ready队列的描述符集合就行了。epoll除了提供select/poll那种IO事件的水平触发(Level Triggered)外,还提供了边缘触发(Edge Triggered),这就使得用户空间程序有可能缓存IO状态,减少epoll_wait/epoll_pwait的调用,提高应用程序效率。后面在回答为什么死循环不会导致app卡死也是利用了这机制的这个特点。
2. 消息循环过程
在looper中建立完毕消息队列后,就会进入循环了,我们这里来看下looper的静态方法Looper.loop()
,为了不影响阅读我把理解都加到代码的注释中,之外就不做过多解释。
|
|
我们都知道消息队列是遵循先进先出的,那么为什么会这样呢?我们开看下Message的结构:
|
|
不难发现,Message是链表结构。回归正题,我们接着看MessageQueue如何取Message的
|
|
JNI层是如何检测是否有新的消息的呢?我们来看下nativePollOnce(ptr, nextPollTimeoutMillis);
还是在路径/frameworks/base/core/jni/android_os_MessageQueue.cpp
|
|
继续找到路径/system/core/libutils/Looper.cpp
:
|
|
3. 消息发送与处理
在前面插播了Message的结构体介绍,那么作为链表的表头它是在什么时候给赋值的呢?下面我们来一起看下Handler是如何做到消息发送以及处理的。
首先是Handler的构造函数:
|
|
一个Handler对应一个消息队列和一个消息循环。Handler发送消息最终都会走到sendMessageAtTime
方法
|
|
一波三折,最后走到的是MessageQueue的enqueueMessage(Message msg, long when)
方法
|
|
下面我们来看如何唤醒线程的,还是找到MessagerQueue.cpp:
|
|
上面通过向管道写入数据来唤醒线程,那么之后又是怎么处理消息的呢?
再来看Looper的loop()
方法:
|
|
ok,闭上眼睛,深呼吸。Handler的消息循环机制基本上跟着代码撸完了一遍,下面通过一些问题来深化对其的理解。
深化理解
- q:什么是消息循环机制?
Android应用程序是通过消息来驱动的,整个机制是围绕着消息的产生以及处理而展开的。消息机制的三大要点:消息队列、消息循环(分发)、消息发送与处理。
- q: loop死循环为什么不会导致应用卡死?
消息循环数据通信采用的是epoll机制,它能显著的提高CPU的利用率,另外Android应用程序的主线程在进入消息循环前,会在内部创建一个Linux管道(Pipe),这个管道的作用是使得ANdroid应用主线程在消息队列唯恐时可以进入空闲等待状态,并且使得当应用程序的消息队列有消息需要处理是唤醒应用程序的主线程。也就是说在无消息时,循环处于睡眠状态,并不会出现卡死情况。
- q:Handler为什么能够处理不同线程的消息?
可以看Handler的构造方法,传入不同的looper就会处理不同的线程。这里一个线程只有一个消息队列和一个消息循环,而Handler与线程是一对多的关系。
- 为什么直接在子线程中初始化Handler会报错?
我们在主线程直接初始化的Handler是不会报错的,因为创建主线程的时候,已经先初始化了主线程的loop对象,而子线程中我们如果不初始化loop对象就会报错。可以看下面ActivityThread的Main方法:
|
|
本文会持续更新,欢迎各位同学拍砖。
参考文章:
- 《Andorid系统源代码情景分析》
- Android消息处理零散分析