Android 消息循环(Handler/Looper/MessageQueue)原理总结

注:如下内容基于Android API Version 27(Android 8.1)Linux Kernel 3.18.0linux

概述

HandlerLooperMessageQueue组成了Android的消息循环系统。消息循环系统是Android App的神经中枢,不管是与AMS/WMS打交道,仍是UI绘制,亦或是手机输入事件的派发都依赖于消息循环系统。android

Android的消息循环运行于底层,咱们在上层开发遇到的一系列的组件生命周期回调,好比ActivityonCreateViewonTouchEvent,都是从主线程的消息循环(Looper.loop通过层层调用过来的。咱们在这些生命周期里填写代码最终造成了可使用的App,而这一切都是被底层的消息循环驱动着,所以咱们能够说Android App是一种基于消息驱动模型的应用程序。git

MessageQueue

消息队列,用于存储和获取消息,包括Java层消息、Native层消息和各类文件描述符(FD)就绪消息。github

Looper

消息循环,内部包含一个MessageQueue对象,经过一个无限循环的loop方法,持续从MessageQueue获取并处理消息。socket

Handler

Java层帮助类,内部包含一个Looper对象,Handler经过Looper中的MessageQueue往Java层的消息队列发送消息。Handler同时提供了消息处理的回调函数,Handler收到消息回调后再决定将消息派发到哪里去。函数

消息的发送与处理

消息循环起始于Looperloop方法调用,此方法内部是一个无限的for循环,线程一旦进入loop就再也出不去了,除非主动退出。之后线程所作的事就三件:取消息、执行消息处理函数和等待,而线程的全部有意义的工做都发生在消息处理函数中。oop

Android的消息循环同时兼顾了Java层和Native层,Jave层和Native各自维护者本身的消息队列,Native层同时还监控着一批FDFD就绪也是做为一种消息进行处理的。消息循环首先尝试消费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层下次是一直阻塞仍是使用一个超时时间进行阻塞。大体流程就是:阻塞-发消息-唤醒线程-处理消息-阻塞。post

Native层消息派发流程

Java层的MessageQueue对象持有一个Native层的MessageQueue对象,Native层的MessageQueue持有一个Native层的Looper对象,Java层的MessageQueue调用Native的nativePollOnce最终调到Native的Lopper.pollOnce。Native的Lopper建立时会将本身加入到Native的线程本地存储中去(TLS),以便之后在线程执行的任意地方拿到Looper对象向Native消息队列发送消息。.net

Native层的Looper使用eventfd进行线程的唤醒,eventfd是linux系统调用,经过eventfd建立一个FD,而后将这个FD和其余真正要监控的FD加入到同一个epoll监控列表中,当须要唤起线程时向这个FD写入一个字符,这样epoll检测到有FD就绪就会从阻塞中唤醒。当Java层须要唤醒Native层的epoll阻塞时只须要调用Native层的方法向event FD写入一个字符,这样epoll_wait就返回了。

Native层的Lopper.pollOnceepoll_wait返回后首先处理Native消息队列中的消息,Native消息队列的消息是经过Native Lopper.sendMessage函数添加的,每个消息都绑定了一个MessageHandler对象,处理消息就是调用MessageHandlerhandleMessage函数将消息对象自己传给发送消息者。处理完普通消息后,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。
  • AMSWMS对App进程的回调从binder线程转到App主线程。
  • vsync信号的处理。ViewRootImpl刷新布局基于vsync信号,而vsync信号是经过读取socketpairFD得到的,将socketpairFD加入到Native的消息轮询中能够直接使vsync的回调发生在主线程,从而避免了一次线程切换。
  • 设备输入事件(Input)一样是经过将socketpairFD加入到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…

相关文章
相关标签/搜索