在 Android 系统源码中,多处使用了同步屏障。例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 void scheduleTraversals () { if (!mTraversalScheduled) { mTraversalScheduled = true ; mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier(); mChoreographer.postCallback( Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null ); } }
这是 Android 屏幕刷新过程中的一个操作,在调用 postCallback(int, Runnable, Object) 方法监听下一帧信号之前,首先向当前线程的消息队列发起了同步屏障。
接下来我们通过对源码的解读,分析出同步屏障的作用到底是什么。
1. Message 的种类 Message 中有一个 flag 变量,用于标记 Message 的类型。
1.1 Message::setAsynchronous(boolean) 1 2 3 4 5 6 7 8 9 public void setAsynchronous (boolean async) { if (async) { flags |= FLAG_ASYNCHRONOUS; } else { flags &= ~FLAG_ASYNCHRONOUS; } }
调用 setAsynchronous(boolean) 方法,可以将 Message 设置为同步消息 或者异步消息 。
1.2 Message::isAsynchronous() 1 2 3 4 public boolean isAsynchronous () { return (flags & FLAG_ASYNCHRONOUS) != 0 ; }
通过 isAsynchronous() 方法,可以判断 Message 是同步消息 还是异步消息 。
由此可知,Message 可以分为两类:
2. 发起同步屏障 在屏幕刷新的例子中,同步屏障是通过调用 MessageQueue::postSyncBarrier() 方法发起的,实际上是间接调用了 MessageQueue::postSyncBarrier(long) 方法。
1 2 3 public int postSyncBarrier () { return postSyncBarrier(SystemClock.uptimeMillis()); }
这里传入的 SystemClock.uptimeMillis() 参数,表示了从系统启动到当前时刻经过的时间。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 private int postSyncBarrier (long when) { synchronized (this ) { final int token = mNextBarrierToken++; 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 ) { msg.next = p; prev.next = msg; } else { msg.next = p; mMessages = msg; } return token; } }
2.1 获取 Message 实例 1 2 3 4 final Message msg = Message.obtain();msg.markInUse(); msg.when = when; msg.arg1 = token;
Message 实例是通过 Message.obtain() 方法获取的,来看看这个方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 public static Message obtain () { synchronized (sPoolSync) { if (sPool != null ) { Message m = sPool; sPool = m.next; m.next = null ; m.flags = 0 ; sPoolSize--; return m; } } return new Message (); }
从这个方法可以看出来,Message 实例是从缓存池中获取的,当缓存池没有可用的 Message 时,则新建一个 Message 对象。
在获取到 Message 实例以后,将其标记为使用中,并设置 Message 的 when 和 arg1 属性。
我们知道,Message 中有一个重要的属性 target,其类型为 Handler。当消息被消费时,用于处理该消息的 Handler 就是 message.target 指向的 Handler。
但是,这里创建出的 Message 实例没有设置 target,也就是 message.target = null。
2.2 小结 通过上面的分析,我们知道发起同步屏障其实就是将一个不带有 target 属性的 Message (接下来我将称之为同步屏障消息) 按照执行时间的先后顺序插入到消息队列中。
现在消息队列中已经存在这样一个独特的消息,接着就需要分析 Android 是如何处理这种消息的。
3. 处理同步屏障消息 在 Android Handler 机制中,Looper 负责将 Message 分发给目标 Handler,依靠的是 Looper::loop() 方法中不断执行的 for 循环 ,首先来分析这个方法是如何获取 Message。
3.1 Looper::loop() 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public static void loop () { final Looper me = myLooper(); final MessageQueue queue = me.mQueue; for (;;) { Message msg = queue.next(); } }
从代码可以看出,Looper 其实是从 MessageQueue 不断取出 Message,然后再对 Message 进行处理的。
接着来分析 MessageQueue::next() 方法,了解 Message 是怎么获取的。
3.2 MessageQueue::next() 为了分析处理同步屏障消息这种场景,现在假设所有的按时间顺序排在同步屏障消息之前的 Message 都已经被消费,换句话说,现在位于消息队列头部 的 Message 就是同步屏障消息。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 Message next () { for (;;) { synchronized (this ) { final long now = SystemClock.uptimeMillis(); Message prevMsg = null ; Message msg = mMessages; if (msg != null && msg.target == null ) { do { prevMsg = msg; msg = msg.next; } while (msg != null && !msg.isAsynchronous()); } if (msg != null ) { 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 ; } } } }
3.3 小结 通过上面的分析,可以得出一些结论:
同步屏障的目的是:阻塞消息队列中同步消息的消费,只处理异步消息 。
在发起同步屏障之后,由于在消息队列中的同步屏障消息不会被自动消费 ,必须手动移除同步屏障。
在没有同步屏障的场景下,同步消息和异步消息在从消息队列获取消息的过程 中没有区别。
4. 移除同步屏障 经过上面的分析,现在我们知道,在发起同步屏障之后,由于在消息队列中的同步屏障消息不会被自动消费 ,Android 提供了 MessageQueue::removeSyncBarrier(int) 方法让我们手动移除同步屏障,来看看这个方法做了什么。
4.1 MessageQueue::removeSyncBarrier(int) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 public void removeSyncBarrier (int token) { synchronized (this ) { Message prev = null ; Message p = mMessages; while (p != null && (p.target != null || p.arg1 != token)) { prev = p; p = p.next; } if (p == null ) { throw new IllegalStateException ("The specified message queue synchronization " + " barrier token has not been posted or has already been removed." ); } final boolean needWake; if (prev != null ) { prev.next = p.next; needWake = false ; } else { mMessages = p.next; needWake = mMessages == null || mMessages.target != null ; } p.recycleUnchecked(); } }
4.2 小结 从代码中可以看出,想要移除同步屏障,需要保存发起同步屏障 postSyncBarrier() 方法的返回值 token,根据 token 对应地移除同步屏障。