Handler中的消息屏障

2021-01-14 10:25:34 浏览数 (1)

Handler中的消息队列如上图所示,是一个单链表,各个消息按照执行时间先后排列,消息类型分为三种:普通消息(normal)、屏障消息(barrier)、异步消息(async)。

我们通常使用的都是普通消息,屏障消息的作用是为了阻塞它后面的普通消息的执行,异步消息的执行不受屏障消息的阻塞。

上面这个方法postSyncBarrier就是用来在Handler的MessageQueue中添加一个屏障消息的,关于屏障消息我们需注意以下几点:

1. 屏障消息没有target的,我们知道普通消息是会有一个target作为消息分发的对象,一个线程中可以有多个Handler,多个Handler是共享一个MessageQueue的,多个Handler向同一个MessageQueue中添加消息在dispatch(消息分发)的时候,就会将消息分发到该消息的target中,而屏障消息是没有target的,所以它是不需要进行分发的,在后续的loop方法中处理消息分发的时候会根据target是否为空,判断当前消息是否为消息屏障;

2. 屏障消息也会带有一个时间when字段,在插入MessageQueue的时候,也是会按照when的先后进行排序的,在MessageQueue中,屏障消息只会影响它后面的消息,对于屏障消息前面的消息是没有影响的;

3. 一个MessageQueue中是可以插入多个屏障消息的;

4. 屏障消息的插入是不会唤醒线程的,我们知道当我们通过Handler向MessageQueue中插入一条普通消息的同时,要是当前线程处于休眠状态,为了唤醒线程处理消息,native层会向eventfd字段中写入数据,从而唤醒线程的(休眠的线程会监听eventfd),屏障消息因为本身就是为了阻塞普通消息的分发,所以在MessageQueue中插入一个屏障消息是不会唤醒线程的;

5. 屏障消息插入队列的时候,Handler会返回一个token,每个屏障消息的token都是通过上面代码中的mNextBarrierToken 来获得的,这个token的作用是为了后续将该屏障消息从MessageQueue中移除的时候使用的,这个token会被记录到消息的arg1的属性变量上面;

6. 屏障消息的插入方法postSyncBarrier方式是private,我们无法通过Handler对象来调用该方法,只能通过反射机制来调用该方法。

另外需要注意我们想MessageQueue中添加普通消息最终是通过上面的enqueueMessage方法插入到MessageQueue中的,这个方法会判断插入的消息的target是否为空,为空会抛出异常,所以虽然Handler在判断一个消息是否为屏障消息的时候是通过消息的target是否为空来作为判断标准的,但是我们却不能通过enqueueMessage方法,传一个target为空的消息以此来向MessageQueue中插入消息屏障。

上面的方法是用来移除MessageQueue中的消息屏障的,需要注意的是,在移除消息屏障之后,会在满足一定条件的时候唤醒线程(nativeWake)。这个条件就是:当前线程是因为该消息屏障的阻塞而进入休眠的,那么当移除这个消息屏障的时候,就需要唤醒线程。

上图是线程启动之后进入消息监听,要是没有消息线程会被阻塞在queue.next()的地方进行休眠,直到MessageQueue中有可用消息的时候,线程才会被再次唤醒,消息屏障的处理也是在queue.next()方法中:

上面是queue.next()方法,该方法会调用native方法nativePollOnce方法在等到nextPollTimeoutMillis时间之后唤醒当前线程读取MessageQueue中的消息进行消息处理,处理消息的场景:

要是MessageQueue中的第一条消息是消息屏障,则会想会判断队列中是否有异步消息(async),要是有就会判断首次遍历到的这个异步消息是否到了要被处理的时间,要是到了会立即处理,要是没有到就会计算得到一个超时时间nextPollTimeoutMillis,并传递给nativePollOnce方法,同时线程进入休眠;如果没有异步消息,线程就会进入无限休眠,直到线程被再次唤醒,或者该消息屏障被移除,同时也会伴随着线程的唤醒。

上面的方法是我们通过enqueueMessage方法向MessageQueue中插入消息,假设一个线程处于休眠状态,此时我们插入消息,如果该消息被最终插入到了MessageQueue的头部,就需要唤醒线程处理消息;如果插入在了一条消息屏障的后面,就要分情况:

(1)当前消息是同步消息,则不会唤醒线程;

(2)当前消息是异步消息,而且该异步消息是消息屏障之后的第一条异步消息,那么此时会唤醒线程进行消息处理;

(3)当前线程是异步消息,但是该异步消息的前面还有未被处理的异步消息,那么此时肯定还轮不到该异步消息的处理,这种情况也不需要唤醒线程。

几个关于消息屏障和IdleHandler的问题:

(1)MessageQueue为空的时候,向MessageQueue中插入一个消息屏障,会触发IdleHandler吗?

不会。IdleHandler触发的条件是MessageQueue首次为空的时候会触发IdleHandler,触发之后线程进入休眠状态,也就是IdleHandler的触发是在线程即将进入休眠状态前触发的。一个线程的MessageQueue为空,说明线程正处于休眠状态,而插入消息屏障是不会唤醒线程的,所以并不会触发IdleHandler。

(2)如果删除了屏障,消息队列为空了,会触发IdleHandler吗?

不会。IdleHandler的调用不会连续,也就是两个IdleHandler的调用之间必定会有一次普通消息或异步消息的处理(即使这次消息处理的时候发现该消息尚未到处理的时间,导致线程进行进入休眠,也会触发IdleHandler)。当Handler分发完一次消息之后,发现MessageQueue中不再有消息的时候就会触发IdleHandler,触发完毕之后Handler还会再次检查一次MessageQueue,避免在调用IdleHandler的过程中MessageQueue中有新消息插入,再次检查的时候要是还是没有消息,线程就会进入休眠状态,此时我们移除消息屏障,线程会被再次唤醒,唤醒之后的线程发现MessageQueue中并没有消息可处理,就会再次进入休眠状态,因为距离上次触发IdleHandler到本次唤醒之间并没有处理任何普通消息/异步消息(屏障消息是不需要处理分发的),所以并不会触发IdleHandler。

上面的代码是通过反射机制向MessageQueue中插入一个消息屏障。同样的postSyncBarrier方法也是有两个,一个是不带参数的会在MessageQueue的头部插入消息屏障,另外一个是带参数的,可以置顶消息屏障插入的delayTime。

上面的方法是移除MessageQueue中的消息屏障,也是需要通过反射机制进行的。需要向removeSyncBarrier方法中传递将要移除的屏障消息的token。

上面是向MessageQueue中插入一个异步消息,异步消息的插入和普通消息的插入类似,唯独不同的是,异步消息需要调用消息的setAsynchronous方法,并传入true表示当前消息是异步消息。

【总结】

消息屏障的作用就是阻塞MessageQueue中消息屏障之后的普通消息(也称为同步消息)不要处理,主要是为异步消息开一个快速通道,让异步消息优先处理,注入UI渲染绘制,输入事件的分发等等。

0 人点赞