Android Handler 机制(四):屏障消息(同步屏障)
一、Handler Message 种类
Handler的Message种类分为3种:
- 普通消息
- 屏障消息
- 异步消息
其中普通消息又称为同步消息,屏障消息又称为同步屏障。
我们通常使用的都是普通消息,而屏障消息就是在消息队列中插入一个屏障,在屏障之后的所有普通消息都会被挡着,不能被处理。不过异步消息却例外,屏障不会挡住异步消息,因此可以这样认为:屏障消息就是为了确保异步消息的优先级,设置了屏障后,只能处理其后的异步消息,同步消息会被挡住,除非撤销屏障。
二、屏障消息如何插入消息队列
同步屏障是通过MessageQueue的postSyncBarrier方法插入到消息队列的。
MessageQueue#postSyncBarrier方法的源码如下:
private int postSyncBarrier(long when) { synchronized (this) { final int token = mNextBarrierToken++; //1、屏障消息和普通消息的区别是屏障消息没有tartget。 final Message msg = Message.obtain(); msg.markInUse(); msg.when = when; msg.arg1 = token; Message prev = null; Message p = mMessages; //2、根据时间顺序将屏障插入到消息链表中适当的位置 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; } //3、返回一个序号,通过这个序号可以撤销屏障 return token; } }
postSyncBarrier方法就是用来插入一个屏障到消息队列的,可以看到它很简单,从这个方法我们可以知道如下:
- 屏障消息和普通消息的区别在于屏障没有tartget,普通消息有target是因为它需要将消息分发给对应的target,而屏障不需要被分发,它就是用来挡住普通消息来保证异步消息优先处理的。
- 屏障和普通消息一样可以根据时间来插入到消息队列中的适当位置,并且只会挡住它后面的同步消息的分发。
- postSyncBarrier返回一个int类型的数值,通过这个数值可以撤销屏障。
- postSyncBarrier方法是私有的,如果我们想调用它就得使用反射。
- 插入普通消息会唤醒消息队列,但是插入屏障不会。
三、屏障消息的工作原理
通过postSyncBarrier方法屏障就被插入到消息队列中了,那么屏障是如何挡住普通消息只允许异步消息通过的呢?
我们知道MessageQueue是通过next方法来获取消息的。
Message next() { //1、如果有消息被插入到消息队列或者超时时间到,就被唤醒,否则阻塞在这。 nativePollOnce(ptr, nextPollTimeoutMillis); synchronized (this) { Message prevMsg = null; Message msg = mMessages; if (msg != null && msg.target == null) {//2、遇到屏障 msg.target == null do { prevMsg = msg; msg = msg.next; } while (msg != null && !msg.isAsynchronous());//3、遍历消息链表找到最近的一条异步消息 } if (msg != null) { //4、如果找到异步消息 if (now < msg.when) {//异步消息还没到处理时间,就在等会(超时时间) nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE); } else { //异步消息到了处理时间,就从链表移除,返回它。 mBlocked = false; if (prevMsg != null) { prevMsg.next = msg.next; } else { mMessages = msg.next; } msg.next = null; if (DEBUG) Log.v(TAG, "Returning message: " + msg); msg.markInUse(); return msg; } } else { // 如果没有异步消息就一直休眠,等待被唤醒。 nextPollTimeoutMillis = -1; } //... } }
在注释2如果碰到屏障就遍历整个消息链表找到最近的一条异步消息,在遍历的过程中只有异步消息才会被处理执行到 if (msg != null){}中的代码。
屏障消息就是通过这种方式就挡住了所有的普通消息。