Read The Fucking Source Code
消息机制模型:
- Message:消息分为硬件产生的消息(如按钮、触摸)和软件生成的消息;
- MessageQueue:消息队列的主要功能向消息池投递消息(MessageQueue.enqueueMessage)和取走消息池的消息(MessageQueue.next);
- Handler:消息辅助类,主要功能向消息池发送各种消息事件(Handler.sendMessage)和处理相应消息事件(Handler.handleMessage);
- Looper:不断循环执行(Looper.loop),按分发机制将消息分发给目标处理者。
源码阅读心得:
消息机制的分层与解耦:
- 消息机制分为Java层和Native层,Java端创建了Native端,Native端与Java端组成了一个完成的整体。
- Java层和Native层的有且只有MessageQueue通过JNI建立关联,各自实现自己的逻辑,功能相似,彼此独立。
Java层总结:
- Message的消息池:尽可能用Handler的obtainMessage()方法,而不是去创建Message,因为这样会取Message消息池的缓存Message,提高效率。
- MessageQueue:在Java层面,MessageQueue才是核心,里面包含了很多native方法,它是连接native的唯一入口。
- MessageQueue.enqueueMessage():是将消息按照处理时序进行添加。当然添加消息还有postSyncBarrier(这是hide方法)
- Looper.loop():无限循环(无限循环的意义在于消息队列的信息获取),取出MessageQueue的Message。
- MessageQueue.next():无限循环(无限循环的意义在于线程等待退出,重新进行消息队列取值计算),next()是MessageQueue的处理核心,可能会有阻塞操作,等待nativePollOnce()结束或者消息队列被唤醒,也会处理SyncBarrier逻辑。当消息取值失败,那么会进入Idle状态,处理Idle任务集合(只执行一次),所以queueIdle()的方法还是返回false即可,或者主动remove idle也行。
- 异步消息是为了同步屏障(SyncBarrier)而设计的,如果不设置SyncBarrier,那么异步消息是没有作用的,而SyncBarrier是hide方法,只提供给系统来使用,比如UI刷新。设置SyncBarrier(好比给消息设置优先级一样),首先处理异步消息,然后取消SyncBarrier,再依次处理消息队列。(系统级别的消息:因为像系统级别的消息,会封装成异步消息,插入异步消息的时候,会向队头插入一个屏障消息,当发现到有这个屏障消息的时候,就不会处理后面的普通消息了,就会在队列后面找异步消息,优先处理异步消息,所以主线程插入异步消息的时候,总是会优先处理这个消息,所以我们主线程所做的一些系统级别的优先级比较高的消息,并不会阻塞)
- Java层也可以主动唤醒线程(当然需要调用Native的方法),可以进行唤醒的地方有:MessageQueue的quit()/removeSyncBarrier()/enqueueMessage()
- quit()与quitSafely()的区别,仅仅在于是否移除当前正在处理的消息。移除当前正在处理的消息可能会出现不安全的行为。
Native层总结:
- Native层提供epoll机制(是一种 IO 多路复用机制,可以同时监控多个文件描述符,当某个文件描述符就绪,则立刻通知相应程序进行读写操作拿到最新的消息,进而唤醒等待的线程)来保证消息队列处理的低功耗。Java层通过Looper的loop(),在MessageQueue中next()中,最终直通Native层的epoll_wait()方法,线程挂起。(就拿主线程来说,如果主线程没有视图刷新请求进行epoll唤醒,那么epoll_wait()会等到timeout结束才会再次唤醒。否则,线程处于空闲等待也就是挂起状态。所以主线程大多数的时候都是出于休眠状态,并不会消耗大量的 CPU 资源,这就是为什么主线程不会因为Loop.loop()里的死循环卡死)
- Native层处理Native Request和Native Message,而Java层处理Java message。通过Native层的Looper.pollInner()方法可以看出,消息处理流程是先处理Native Message,再处理Native Request,最后处理Java Message。理解了该流程,也就明白有时上层消息很少,但响应时间却较长的真正原因。
Handler的通俗解释
- Android中主线程是不能进行耗时操作的,子线程是不能进行更新UI的。所以就有了handler,它的作用就是实现线程之间的通信。
- 安卓是由 事件驱动 的,对事件进行处理的就是 looper(每一个点击触摸或者Activity每一个生命周期),Handler 的创建依赖 该线程 的Looper(Handler利用哪个线程的Looper创建的实例,它就和相应的线程绑定到一起,处理该线程上的消息),这就决定了Looper查询出的msg分发到Handler的handleMessage()方法是运行在 该线程。
- 主线程Looper从消息队列读取消息,当读完所有消息时,主线程阻塞。子线程往消息队列发送消息,并且往管道文件写数据,主线程即被唤醒,从管道文件读取数据,主线程被唤醒只是为了读取消息,当消息读取完毕,再次睡眠。因此loop的循环并不会对CPU性能有过多的消耗。
Handler、Thread和HandlerThread的差别
- Handler是线程的消息通讯的桥梁,主要用来发送消息及处理消息
- Thread普通线程,如果需要有自己的消息队列,需要调用Looper.prepare()创建Looper实例,调用loop()去循环消息。
- HandlerThread是一个带有Looper的线程,在HandleThread的run()方法中调用了Looper.prepare()创建了Looper实例,并调用Looper.loop()开启了Loop循环,循环从消息队列中获取消息并交由Handler处理。利用该线程的Looper创建Handler实例,此Handler的handleMessage()方法是运行在子线程中的。
- HandlerThread适合处理本地IO读写操作(读写数据库或文件),因为本地IO操作耗时不长,对于单线程+异步队列不会产生较大阻塞,而网络操作相对比较耗时,容易阻塞后面的请求,因此HandlerThread不适合加入网络操作。
问题思考
Android如何保证在请求绘制流程过程中,不会因为其他消息处理事件的阻塞,导致Vsync刷新信号的处理延时?
- 这其实就是Handler如何做到线程优先级的问题。我们来跟进一下绘制流程的处理。
- 页面请求绘制流程刷新,会调用View的 requestLayout / invalidate 方法。
- 最终会调入到ViewRootImpl的 scheduleTraversals 方法。
- 在 scheduleTraversals 方法中,首先在Handler消息队列中插入同步屏障。
- 然后在 scheduleTraversals方法中向Choreographer(Vsync处理引擎)注册Vsync刷新信号回调。
- 在Choreographer中,如果有跨线程通信,那么都会用异步消息,并且放在消息队列头部,消息不会堵塞,优先处理。
- DisplayEventReceiver接收到native侧的调用 dispatchVsync(),然后通过跨线程,将onVsync()转到doFrame()。
- doFrame()已经在主线程中了,通过回调 scheduleTraversals 在 Choreographer 注册的回调Runnable。
- 返回ViewRootImpl中注册了Vsync刷新信号回调的Runnable中,会走到 doTraversal 。
- 在doTraversal中,移除同步屏障,然后执行真正的绘制分发流程 performTraversals(绘制流程分发总方法)。
- 等待绘制流程处理结束,才会进行下一个消息的轮询,这样就保证了绘制流程的高优先级线程处理。
- 设置同步屏障是hide方法,只给系统来用,所以设计初衷就是保证系统的某些操作要高优先级。
- 线程的高优先级需要两个必不可少的条件 = 同步屏障 + 异步消息。