Android 消息机制 Handler

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如何做到线程优先级的问题。我们来跟进一下绘制流程的处理。
    1. 页面请求绘制流程刷新,会调用View的 requestLayout / invalidate 方法。
    1. 最终会调入到ViewRootImpl的 scheduleTraversals 方法。
    1. 在 scheduleTraversals 方法中,首先在Handler消息队列中插入同步屏障。
    1. 然后在 scheduleTraversals方法中向Choreographer(Vsync处理引擎)注册Vsync刷新信号回调。
    1. 在Choreographer中,如果有跨线程通信,那么都会用异步消息,并且放在消息队列头部,消息不会堵塞,优先处理。
    1. DisplayEventReceiver接收到native侧的调用 dispatchVsync(),然后通过跨线程,将onVsync()转到doFrame()。
    1. doFrame()已经在主线程中了,通过回调 scheduleTraversals 在 Choreographer 注册的回调Runnable。
    1. 返回ViewRootImpl中注册了Vsync刷新信号回调的Runnable中,会走到 doTraversal 。
    1. 在doTraversal中,移除同步屏障,然后执行真正的绘制分发流程 performTraversals(绘制流程分发总方法)。
    1. 等待绘制流程处理结束,才会进行下一个消息的轮询,这样就保证了绘制流程的高优先级线程处理。
    1. 设置同步屏障是hide方法,只给系统来用,所以设计初衷就是保证系统的某些操作要高优先级。
    1. 线程的高优先级需要两个必不可少的条件 = 同步屏障 + 异步消息。

小编的扩展链接

《Android 基础组件 全家桶》

你可能感兴趣的:(Android 消息机制 Handler)