前言
由于Handler消息机制的同步屏障问题使用的场景不多,即使不了解可能也不会影响正常开发,因此往往容易被忽视,笔者也是自认比较熟悉Handler的消息机制,在之前同步屏障也算盲区,要想更全面的掌握Handler消息机制,同步屏障就不应该缺席。本文将从同步屏障的插入、删除、使用场景等角度分析同步屏障的工作原理
消息的分类
Message分为3中:普通消息(同步消息)、屏障消息(同步屏障)和异步消息。我们通常使用的都是普通消息,而屏障消息就是在消息队列中插入一个屏障,在屏障之后的所有普通消息都会被挡着,不能被处理。不过异步消息却例外,屏障不会挡住异步消息,因此可以这样认为:屏障消息就是为了确保异步消息的优先级,设置了屏障后,只能处理其后的异步消息,同步消息会被挡住,除非撤销屏障。同步屏障后面会重点介绍,这里先了解异步消息。
在Android系统中存在一个VSync消息,它主要负责每16ms更新一次屏幕展示,如果用户同步消息在16ms内没有执行完成,那么VSync消息的更新操作就无法执行在用户看来就出现了掉帧或卡顿的情况,为此Android开发要求每个消息的执行需要限制在16ms之内完成。但是消息队列中可能会包含多个同步消息,假如当前主线程消息队列有10个同步消息,每个同步消息要执行10ms,总共也就需要执行100ms,这段时间内就会有近7帧无法正常刷新展示,应用执行过程中遇到这种情况还是很普遍的。
Android系统设计时自然也会考虑到这种情况,同步消息会导致延迟主要原因在于排队等候,如果消息发送后不必排队等待直接就执行就能够解决消息延迟问题。Android系统中的异步消息就是专门解决消息处理延迟的问题,它需要配合同步屏障(SyncBarrier)一起工作,在发送异步消息的时候向消息队列插入同步屏障对象,消息队列会返回同步屏障的token,此时消息队列中的同步消息都会被暂停处理,优先执行异步消息处理,等异步消息处理完成再通过消息队列移除token对应的同步屏障,消息队列继续之前暂停的同步消息处理。
异步消息& 同步屏障 使用场景
ViewRootImpl接收屏幕垂直同步信息事件用于驱动UI测绘
ActivityThread接收AMS的事件驱动生命周期
InputMethodManager分发软键盘输入事件
PhoneWindowManager分发电话页面各种事件
屏障消息
发送同步屏障
同步屏障是通过MessageQueue的postSyncBarrier方法插入到消息队列的。屏障消息(同步屏障有以下特征)
-
①没有给target赋值,即不用handler分发处理,后续也会根据target是否为空来判断消息是否为消息屏障
-
②消息队列中可以插入多个消息屏障
-
③消息屏障也是可以有时间戳的,插入的时候也是按时间排序的,它只会影响它后面的消息,前面的不受影响
-
④消息队列中插入消息屏障,是不会唤醒线程的(插入同步或异步消息会唤醒消息队列)
-
⑤插入消息屏障后,会返回一个token,是消息屏障的序列号,后续撤除消息屏障时,需要根据token查找对 应的消息屏障
-
⑥发送屏障消息的API被隐藏,需要通过反射调用postSyncBarrier方法
MessageQueue#postSyncBarrier
class MessageQueue{
public int postSycnBarrier() {
//currentTimeMills()系统当前时间,即日期时间,可以被系统设置修改,时间值会发生跳变
//uptimeMills() 自开机后,经过的时间,不包括深度休眠的时间
//sendMessageDelay,postDelay也都使用了这个时间戳
//意思是指,发送了这条消息,在这期间如果设备进入休眠状态(如息屏后长时间不操作手机)那么消息是不会被执行的,
//设备唤醒后到了时间才会执行
}
private int postSyncBarrier(long when) {
synchronized (this) {
final int token = mNextBarrierToken++;
//屏障消息和普通消息的区别是屏障消息没有tartget
//也就不会被分发处理(执行)
//可以理解为是一个标志位flag
final Message msg = Message.obtain();
msg.markInUse();
msg.when = when;
msg.arg1 = token;
Message prev = null;
Message p = mMessages;
//根据时间顺序将屏障插入到消息链表中适当的位置
if (when != 0) {
while (p != null && p.when <= when) {
prev = p;
p = p.next;
}
}
if (prev != null) {
// invariant: p == prev.next
msg.next = p;
prev.next = msg;
} else {
msg.next = p;
mMessages = msg;
}
//返回一个序号,通过这个序号可以撤销屏障
return token;
}
}
}
移除同步屏障
- 根据添加屏障时对应的token,在消息队列中找到对应的屏障删除
- 添加屏障的时候不会唤醒线程,删除屏障时,满足一定条件就唤醒线程
- 如果屏障在消息队列头部,触发时间到了,不管后面有没有消息,撤除屏障都会唤醒线程。但是如果后面紧跟着