注:以下内容基于Android API Version 27(Android 8.1)Linux Kernel 3.18.0
概述
Handler
、Looper
和MessageQueue
组成了Android的消息循环系统。消息循环系统是Android App的神经中枢,无论是与AMS/WMS
打交道,还是UI绘制,亦或是手机输入事件的派发都依赖于消息循环系统。
Android的消息循环运行于底层,我们在上层开发遇到的一系列的组件生命周期回调,比如Activity
的onCreate
和View
的onTouchEvent
,都是从主线程的消息循环(Looper.loop
经过层层调用过来的。我们在这些生命周期里填写代码最终形成了可以使用的App,而这一切都是被底层的消息循环驱动着,因此我们可以说Android App是一种基于消息驱动模型的应用程序。
MessageQueue
消息队列,用于存储和获取消息,包括Java层消息、Native层消息和各种文件描述符(FD)就绪消息。
Looper
消息循环,内部包含一个MessageQueue
对象,通过一个无限循环的loop方法,持续从MessageQueue
获取并处理消息。
Handler
Java层帮助类,内部包含一个Looper
对象,Handler
通过Looper
中的MessageQueue
往Java层的消息队列发送消息。Handler
同时提供了消息处理的回调函数,Handler
收到消息回调后再决定将消息派发到哪里去。
消息的发送与处理
消息循环起始于Looper
的loop
方法调用,此方法内部是一个无限的for
循环,线程一旦进入loop
就再也出不去了,除非主动退出。以后线程所做的事就三件:取消息、执行消息处理函数和等待,而线程的所有有意义的工作都发生在消息处理函数中。
Android的消息循环同时兼顾了Java层和Native层,Jave层和Native各自维护者自己的消息队列,Native层同时还监控着一批FD
,FD
就绪也是作为一种消息进行处理的。消息循环首先尝试消费Native层的消息(包括普通消息和FD
消息),消费完后再消费Java层的消息,如果Java层没有可供消费的消息了,线程就会在下一次消费Native时层阻塞在Native的epoll_wait
调用上,epoll_wait
用于等待其所监控的FD
就绪,如果Java层发送了新消息或者Native层发送了新消息,再或者有FD
就绪了,线程就会被唤醒,从而消息循环就会继续处理消息,直到Java、FD
和Native都没有消息了线程就又再次陷入阻塞。
Java层的消息在Java层处理,Native层的消息在Native层处理,Java层的消息和Native层的消息的唯一联系是他们在同一个循环线程中处理,线程阻塞是发生在Native层,线程唤起是发生在Java层和Native层,线程阻塞的时长和当前是否有消息或者下一个消息的处理时间有关。
Java层消息派发流程
Java应用程序通过Handler
将消息发送给MessageQueue
,消息按照时间从先到后进行排序放入链表中,如果消息要立即被处理,也就是说没有延迟的消息,此时如果线程处于阻塞状态,Java层通过jni
调用Native层的接口唤醒线程,线程唤醒后会从Nativie的Looer.pollOnce
返回到Java层的nativePollOnce
,然后继续处理Java层的消息,消息处理完后根据是否还有消息或者下一个消息等待处理的延迟时间来决定Native层下次是一直阻塞还是使用一个超时时间进行阻塞。大致流程就是:阻塞-发消息-唤醒线程-处理消息-阻塞。
Native层消息派发流程
Java层的MessageQueue
对象持有一个Native层的MessageQueue
对象,Native层的MessageQueue
持有一个Native层的Looper
对象,Java层的MessageQueue
调用Native的nativePollOnce
最终调到Native的Lopper.pollOnce
。Native的Lopper
创建时会将自己加入到Native的线程本地存储中去(TLS
),以便以后在线程执行的任意地方拿到Looper
对象向Native消息队列发送消息。
Native层的Looper
使用eventfd进行线程的唤醒,eventfd
是linux系统调用,通过eventfd
创建一个FD
,然后将这个FD
和其他真正要监控的FD
加入到同一个epoll
监控列表中,当需要唤起线程时向这个FD
写入一个字符,这样epoll
检测到有FD
就绪就会从阻塞中唤醒。当Java层需要唤醒Native层的epoll
阻塞时只需要调用Native层的方法向event FD写入一个字符,这样epoll_wait
就返回了。
Native层的Lopper.pollOnce
从epoll_wait
返回后首先处理Native消息队列中的消息,Native消息队列的消息是通过Native Lopper.sendMessage
函数添加的,每一个消息都绑定了一个MessageHandler
对象,处理消息就是调用MessageHandler
的handleMessage
函数将消息对象本身传给发送消息者。处理完普通消息后,Native Looper
接着处理FD
就绪的消息,每一个FD
消息在添加的时候(Looper.addFD
)会关联一个LooperCallback
对象,Native Looper
就是通过回调FD
所绑定的LopperCallback
来由使用方自己处理FD
就绪的消息的。
处理完所有的这两种消息后Native Looper
就返回了,接着线程就走到了Java层的消息处理逻辑中去了。
线程局部存储
我们之所以在创建了Looper
的线程中的任意位置可以访问这个Looper
原因是Looper
对象是借助ThreadLocal
存储的。ThreadLocal
即线程局部存储。Looper
内部声明了一个静态的ThreadLocal
对象,TheadLocal
内部是以线程对象为key,线程局部数据为value存储在一个类map的结构中的。只要线程调用Looper.prepare
将当前创建的Looper
对象存在ThreadLocal
中,以后在线程执行的任意位置都可以调用Looper.myLooper
将存储的Looper
对象拿出来,并且每个线程对应各自的Looper
对象。
关于线程局部存储可以参阅:Android 线程局部存储ThreadLocal原理总结
Android消息循环在以下重要场景中都有应用
- 子线程做完任务后通过消息循环转到主线程去刷新UI。
AMS
和WMS
对App进程的回调从binder线程转到App主线程。vsync
信号的处理。ViewRootImpl
刷新布局基于vsync
信号,而vsync
信号是通过读取socketpair的FD
获得的,将socketpair
的FD
加入到Native的消息轮询中可以直接使vsync
的回调发生在主线程,从而避免了一次线程切换。- 设备输入事件(Input)同样是通过将socketpair的
FD
加入到Native的消息轮询中从而实现了消息直接于主线程处理。
参考
blog.csdn.net/luoshengyan…
androidxref.com/8.1.0_r33/x…
androidxref.com/8.1.0_r33/x…
linux.die.net/man/2/event…
linux.die.net/man/2/socke…
chao-tic.github.io/blog/2018/1…