Handler的同步屏障机制

172 阅读5分钟

Handler的Message种类分为三种:

  • 普通消息
  • 异步消息
  • 屏障消息

异步消息

通常我们使用Handler向消息队列中添加的Message都是同步的,如果我们想要添加一个异步的Message,有以下两种方式:

  1. Handler的构造方法有个async参数,默认的构造方法此参数是false,只要我们在构造handler对象的时候,把该参数设置为true就可以了。async设置为true后,对全局的mAysnchronous设置为true.然后在enqueueMessage()方法里,调用msg.setAsynchronous(true),将message设置为异步的。
  2. 在创建Message对象时,直接调用Message的setAsynchronous()方法。

在一般情况下,异步消息和同步消息没有什么区别,但是一旦开启了同步屏障以后就有区别了。

同步屏障

一般来说,MessageQueue里面的所有Message是按照时间从前往后有序排列的。同步屏障消息就是在消息队列中插入一个屏障,在屏障之后的所有普通消息都会被挡着,不能被处理。不过异步消息却例外,屏障不会挡住异步消息,因此可以认为,屏障消息就是为了确保异步消息的优先级,设置了屏障后,只能处理其后的异步消息,同步消息会被挡住,除非撤销屏障。同步屏障是通过MessageQueue的postSyncBarrier方法开启的。

  1. 获取屏障的唯一标识,标识从0开始,自加1.
  2. 从Message消息对象池中获取一个msg,设置msg为正在使用状态,并且重置msg的when和arg1,arg1的值设置为token值。但是这里并没有给target赋值。所以msg的target是否为空是判断这个msg是否是屏障消息的标志。
  3. 创建变量pre和p,为下一步做准备。其中p被赋值为mMessages,mMessages指向消息队列中的第一个元素,所以此时p指向消息队列中的第一个元素。
  4. 通过对队列中的第一个Message的when和屏障的when进行比较,决定屏障消息在整个消息队列中的位置,因为消息队列中的消息都是按时间排序的。
  5. prev!=null,代表不是消息的头部,把msg插入到消息队列中。
  6. prev==null,代表是消息队列的头部,把msg插入消息的头部。

我们通常通过Handler发送消息handler.sendMessage(),最终都会调用Handler.java中的enqueueMessage()方法。enqueueMessage()方法里为msg设置了target字段。而上面的postSyncBarrier(),也是从Message消息对象池中获取一个msg,插入到消息队列中,唯一的不同是没有设置target字段。所以从代码层面上讲,屏障消息就是一个target为空的Message。

屏障消息的工作原理

通过postSyncBarrier方法屏障就被插入到消息队列中了,那么屏障是如何挡住普通消息只允许异步消息通过呢?

我们知道Handler的消息处理是在Looper.loop()从消息队列中获取消息,并交给Handler处理的,其中时通过MessageQueue next方法来获取消息的。msg.target==null 时说明此时的msg是屏障消息,此时会进入到循环,遍历移动msg的位置,直到移动到msg是异步message则退出循环,也就是说,循环的代码会过滤掉所有的同步消息,直到取出异步消息为止。

当设置了同步屏障之后,next函数将会忽略所有的同步消息,返回异步消息。换句话说就是:

  • 设置了同步屏障之后,Handler只会处理异步消息。
  • 同步屏障为Handler消息机制增加了一种简单的优先级机制,异步消息的优先级要高于同步消息。

移除同步屏障

同步屏障的移除是在MessageQueue.java的removeSyncBarrier()方法。removeSyncBarrier方法需要传入一个参数token,这个参数可以从postSyncBarrier添加屏障方法的返回值中获取到。

删除屏障消息的方法很简单,就是不断遍历消息队列,直到找到屏障消息,退出循环的条件有两个,一是p.target==null,说明是屏障消息,二是p.arg1==token,也说明p是屏障消息,因为在屏障消息入队的时候,设置过msg.arg1=token。找到屏障消息后,把它从消息队列中删除并回收。

屏障消息用在哪里

系统把插入屏障和构造异步Handler这些东西标记为@UnsupportedAppUsage,意思就是这些API是系统自己用的,不想让开发者调用。那系统是什么时候用的呢?

异步消息需要同步屏障的辅助,但同步屏障我们无法手动添加,因此了解系统何时添加和删除同步屏障是必要的。只有这样才能更好地运行异步消息这个功能,知道为什么要用和如何用。了解同步屏障需要简单了解屏幕刷新机制的内容。

手机屏幕刷新有不同的类型,60HZ、120HZ等。屏幕会在每次刷新的时候发出一个Vsync信号,通知CPU进行绘制计算。具体到我们代码中,可以认为是执行onMeasure、onLayout、onDraw这些方法。

View绘制的起点是ViewRootImpl的requestLayout()开始的,这个方法会去执行上面三大绘制任务:测量、布局、绘制。调用requestLayout()方法之后,并不会马上开始进行绘制任务,而是会给主线程设置一个同步屏障,并设置Vsnyc信号监听。当Vsync信号的到来,会发送一个异步消息到主线程Handler,执行我们上一步设置的绘制监听任务,并移除同步屏障。

在等待Vsync信号的时候主线程什么事都没干,这样的好处是保证在Vsync信号到来时,绘制任务可以被及时执行,不会造成界面卡顿。这样的话,我们发送的普通消息可能会被延迟处理,在Vsync信号到了之后,移除屏障,才得以处理普通消息。

改善这个问题的办法是使用异步消息,发送异步消息之后,即使是在等待Vsync期间也可以执行我们的任务,让我们设置的任务可以更快得执行且减少主线程的Looper压力。