本文重点:
ViewRootImpl
中的例子讲解同步屏障;IdleHandler
的用法与原理,并以ActivityThread
举例;代码以 Android API 28 (Android 9)为准,如有不当,希望指正😀
什么是同步屏障?开启同步屏障的第一步需要发送一个特殊消息作为屏障消息,当消息队列检测到了这种消息后,就会从这个消息开始,遍历后续的消息,只处理其中被标记为“异步”的消息,忽略同步消息(所以叫“同步屏障”),相当于给一部分消息开设了“VIP”优先通道。当使用完同步屏障后我们还注意移除屏障。
我们来看ViewRootImpl
中是如何使用同步屏障的:
void scheduleTraversals() { if (!mTraversalScheduled) { mTraversalScheduled = true; // 1、发送屏障消息,记录屏障消息的token:mTraversalBarrier mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier(); // 2、发送Vsync信号的过程,这其中会发送一些异步消息 mChoreographer.postCallback( Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); if (!mUnbufferedInputDispatch) { scheduleConsumeBatchedInput(); } notifyRendererOfFramePending(); pokeDrawLockIfNeeded(); } } void doTraversal() { if (mTraversalScheduled) { mTraversalScheduled = false; // 3、移除屏障消息,使用屏障消息的token移除 mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier); // 绘制界面 performTraversals(); if (mProfile) { mProfile = false; } } } 复制代码
如果看了我前面的文章 源码阅读#我们所写的View是如何被添加与显示的呢? 就会知道触发postCallback
方法后大致有以下几步:
Handler
发送消息,所有逻辑分支最终都会使用native
方法nativeScheduleVsync
发送VSync
垂直同步信号;VSync
信号由SurfaceFlinger
实现并定时发送;FrameDisplayEventReceiver
的onVsync
方法;Choreographer
的doFrame
;ViewRootImpl
的doTraversal
,doTraversal
又调用到performTraversals
,其中同过Surface
向GPU
发送绘制数据。这里的同步屏障实际上是保障了在发送VSync
信号之前,postCallback
中的Handler
消息能被优先处理(postCallback
里发送的消息都被标记为了异步消息),以保证界面能迅速更新。
那么同步屏障如何使用,如何运行的呢,主要分为以下四步:
Message
中target
为空,target
的类型是Handler
)asynchronous
的消息)token
来删除消息)括号中的说明如果不理解,我们就来详细看看整个过程吧。
我们来看看MessageQueue
中的postSyncBarrier
方法:
public int postSyncBarrier() { // uptimeMillis会返回从系统启动开始到现在的时间(不包括深度睡眠的时间):milliseconds of non-sleep uptime since boot return postSyncBarrier(SystemClock.uptimeMillis()); } private int postSyncBarrier(long when) { // Enqueue a new sync barrier token. // We dont need to wake the queue because the purpose of a barrier is to stall it. // 上面的意思是放一个新的同步屏障消息到队列中,它就会一直在那挡着(直到你移除它) synchronized (this) { // 屏障消息的token,作为唯一标识,用于移除屏障消息 final int token = mNextBarrierToken++; // 循环利用Message对象 final Message msg = Message.obtain(); // 标记为正在使用,记录时间与token msg.markInUse(); msg.when = when; msg.arg1 = token; // 下面代码的目的就是把屏障消息按时间排序插入到消息队列中, // 前面的是早于自己的消息,后面的是晚于自己的消息 // 1、找到两个相邻的消息,使得 prev.when < msg.when < p.when Message prev = null; Message p = mMessages; if (when != 0) { while (p != null && p.when <= when) { prev = p; p = p.next; } } // 2、插入屏障消息到prev与p之间 if (prev != null) { // invariant: p == prev.next msg.next = p; prev.next = msg; } else { msg.next = p; mMessages = msg; } return token; } } 复制代码
该方法会返回一个token
,在移除屏障消息的时候使用。
这里插一句,感觉很多人其实不理解token
到底是什么意思,我们在用户登录的时候也会用到token
,其实token
的意义就是一个唯一标识,token
意思是“已经发生了”,就是给一个已发生的事物一个唯一标识。
回到正题,postSyncBarrier
的作用就是在消息队列中插入一个屏障消息,插入到什么位置呢,按消息的先来后到,排到对应的位置(消息都有记录when
的,按when
大小排队)。这里注意了,这个消息是没有给Message
中的target
赋值的,这个会作为后面判断是否开启同步屏障的依据。
/*package*/ long when; /*package*/ Handler target; 复制代码
我们取postCallback
之后的一个异步消息的例子,在scheduleFrameLocked
方法中:
Message msg = mHandler.obtainMessage(MSG_DO_FRAME); msg.setAsynchronous(true); mHandler.sendMessageAtTime(msg, nextFrameTime); 复制代码
这里很简单,就是把这个消息标记为了异步消息,然后再发送出去,重点是上面代码的第二行。
在MessageQueue
的next
方法中进行了消息读取,在这里做了同步屏障的相关判断:
Message next() { ...... int nextPollTimeoutMillis = 0; for (;;) { ...... // 阻塞,nextPollTimeoutMillis为等待时间,如果为-1则会一直阻塞 nativePollOnce(ptr, nextPollTimeoutMillis); synchronized (this) { // Try to retrieve the next message. Return if found. // 下面的代码目的是获取一个可用的消息,如果找到就return, // 没找到就继续后面我省略的代码(省略了IdHandler的相关代码) // 获取时间,还是通过uptimeMillis这个方法 final long now = SystemClock.uptimeMillis(); Message prevMsg = null; Message msg = mMessages; // 如果队列头部消息为屏障消息,即“target”为空的消息,则去寻找队列中的异步消息 if (msg != null && msg.target == null) { // Stalled by a barrier. Find the next asynchronous message in the queue. do { prevMsg = msg; msg = msg.next; } while (msg != null && !msg.isAsynchronous()); } // 如果队列头部消息不是屏障消息,就会直接处理 // 如果是,就会获取异步消息,获取的到就处理,获取不到就去运行省略的代码 if (msg != null) { if (now < msg.when) { // 当前时间小于消息的时间,设置进入下次循环后的等待时间 // Next message is not ready. Set a timeout to wake up when it is ready. nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE); } else { // Got a message. // 一个标记,表示next循环是不是还在被阻塞着 mBlocked = false; // 移除消息 if (prevMsg != null) { // 移除异步消息 prevMsg.next = msg.next; } else { // 移除同步消息 mMessages = msg.next; } msg.next = null; // 标记为正在使用 msg.markInUse(); return msg; } } else { // No more messages. // 没有获取到消息,接下来运行下面省略的代码,nextPollTimeoutMillis为“-1”,在循环开始的nativePollOnce方法将会一直阻塞。 nextPollTimeoutMillis = -1; } // Process the quit message now that all pending messages have been handled. if (mQuitting) { dispose(); return null; } ......// IdleHandler }// end of synchronized ......// IdleHandler } } 复制代码
可以看到,在next
方法的无限循环中,首先是nativePollOnce
阻塞,然后是取消息的同步代码块(使用synchronized
包裹),在这其中,首先取消息队列的头部消息(即mMessages
),如果是屏障消息(消息的target
为空),则寻找队列中的异步消息进行处理,否则直接处理这条头部消息。
在找到合适的消息后(if(msg != null)
),会将即将处理的消息移除队列并返回;当然,如果没有找到就会将nextPollTimeoutMillis
置为-1,让循环进入阻塞状态。
在next
方法返回消息后,Looper
会调用Handler
的dispatchMessage
回调到对应的方法中,我们来看看Looper.loop()
方法:
public static void loop() { ..... for (;;) { // 无限取消息,“might block” 指的就是nativePollOnce的阻塞 Message msg = queue.next(); // might block if (msg == null) { // No message indicates that the message queue is quitting. // 如果返回的消息是空的,则会退出循环。如果循环没退出并且没有消息,则会被nativePollOnce阻塞着。 return; } ...... // 分发消息,target即是发送消息的Handler msg.target.dispatchMessage(msg); ...... // 回收消息 msg.recycleUnchecked(); } 复制代码
最后Handler
的dispatchMessage
就会调用到handleMessage
或者Message
的callback
(callback
为Runnable
对象),运行消息所指向的内容:
public void dispatchMessage(Message msg) { if (msg.callback != null) { handleCallback(msg); } else { if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg); } } 复制代码
使用同步屏障一定要记得移除消息,消息队列是不会自动移除的。我们通过MessageQueue
的removeSyncBarrier
方法移除屏障消息:
public void removeSyncBarrier(int token) { // Remove a sync barrier token from the queue. // If the queue is no longer stalled by a barrier then wake it. 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."); } // 移除屏障消息,并判断是否需要唤醒队列(nativePollOnce用于阻塞,nativeWake用于唤醒) final boolean needWake; if (prev != null) { prev.next = p.next; needWake = false; } else { mMessages = p.next; needWake = mMessages == null || mMessages.target != null;// 消息队列为空或者首个消息不为屏障消息 } // 被移除的屏障消息需要回收 p.recycleUnchecked(); // If the loop is quitting then it is already awake. // We can assume mPtr != 0 when mQuitting is false. if (needWake && !mQuitting) { nativeWake(mPtr); } } } 复制代码
在removeSyncBarrier
方法中,首先是去寻找屏障消息,找不到会抛出异常;然后是移除屏障消息,并且判断是否需要唤醒消息队列继续取消息(唤醒next
方法,这里的nativeWake
用于唤醒,next
方法中的nativePollOnce
用于阻塞)。
以上就是同步屏障的使用与原理分析了,关于同步屏障的添加与移除方法都是公共的,但是如果要使用还是要小心有坑喔。
/** * Callback interface for discovering when a thread is going to block * waiting for more messages. */ public static interface IdleHandler { /** * Called when the message queue has run out of messages and will now * wait for more. Return true to keep your idle handler active, false * to have it removed. This may be called if there are still messages * pending in the queue, but they are all scheduled to be dispatched * after the current time. */ boolean queueIdle(); } 复制代码
IdlerHandler实际上是一个接口,idle
在RecyclerView
的状态分类中也有出现——SCROLL_STATE_IDLE
,表示停止滚动的状态,同样,IdleHandler
表示的是在消息循环空闲的时候(没有消息)进行的回调方法。接口方法返回值代表是否要移除当前IdleHandler
。
在MessageQueue
中:
private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<>(); public void addIdleHandler(@NonNull IdleHandler handler) { if (handler == null) { throw new NullPointerException("Can't add a null IdleHandler"); } synchronized (this) { mIdleHandlers.add(handler); } } public void removeIdleHandler(@NonNull IdleHandler handler) { synchronized (this) { mIdleHandlers.remove(handler); } } 复制代码
方法很简单,通过一个全局变量mIdleHandlers
管理所有IdleHandler
在MessageQueue
的next
方法中调用了IndleHandler
:
private IdleHandler[] mPendingIdleHandlers; Message next() { ...... int pendingIdleHandlerCount = -1; // -1 only during first iteration int nextPollTimeoutMillis = 0; for (;;) { ...... nativePollOnce(ptr, nextPollTimeoutMillis); synchronized (this) { ...... // Process the quit message now that all pending messages have been handled. if (mQuitting) { dispose(); return null; } // If first time idle, then get the number of idlers to run. // Idle handles only run if the queue is empty or if the first message // in the queue (possibly a barrier) is due to be handled in the future. // 获取IdleHandler个数 if (pendingIdleHandlerCount < 0 && (mMessages == null || now < mMessages.when)) { pendingIdleHandlerCount = mIdleHandlers.size(); } // 如果没有,继续循环 if (pendingIdleHandlerCount <= 0) { // No idle handlers to run. Loop and wait some more. mBlocked = true; continue; } // 添加IdleHandler到准备处理的队列 if (mPendingIdleHandlers == null) { mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)]; } mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers); } // Run the idle handlers. // We only ever reach this code block during the first iteration. for (int i = 0; i < pendingIdleHandlerCount; i++) { final IdleHandler idler = mPendingIdleHandlers[i]; mPendingIdleHandlers[i] = null; // release the reference to the handler boolean keep = false; try { // 调用接口 keep = idler.queueIdle(); } catch (Throwable t) { Log.wtf(TAG, "IdleHandler threw exception", t); } if (!keep) { synchronized (this) { // 移除IdleHandler mIdleHandlers.remove(idler); } } } // Reset the idle handler count to 0 so we do not run them again. pendingIdleHandlerCount = 0; // While calling an idle handler, a new message could have been delivered // so go back and look again for a pending message without waiting. // 下个循环不需等待 nextPollTimeoutMillis = 0; } } 复制代码
在next
方法中,如果没有获取到消息或者消息未准备好(now < mMessages.when
),就会进入IdleHandler
的处理流程(此时nextPollTimeoutMillis
为大于0的值或者-1);如果有,就将IdleHandler
放入“悬挂”队列,即即将处理的队列;然后跳出同步代码块,运行所有IdleHandler
;最后重置pendingIdleHandlerCount
与nextPollTimeoutMillis
;
nextPollTimeoutMillis
置为0表示下个循环不需等待了,因为在处理IdleHandler
的过程中可能已经收到了一些消息,此时需要立即处理。
在ApplicationThread
收到GC_WHEN_IDLE
消息后,会触发scheduleGcIdler
方法:
class H extends Handler { ...... public static final int GC_WHEN_IDLE = 120; ...... public void handleMessage(Message msg) { switch (msg.what) { ...... case GC_WHEN_IDLE: scheduleGcIdler(); ...... } } } 复制代码
在ActivityThread
的scheduleGcIdler
方法中(ApplicationThread
是ActivityThread
的非静态内部类,所以可以直接调用对应方法):
// 添加垃圾回收的IdleHandler void scheduleGcIdler() { if (!mGcIdlerScheduled) { mGcIdlerScheduled = true; Looper.myQueue().addIdleHandler(mGcIdler); } mH.removeMessages(H.GC_WHEN_IDLE); } // 移除垃圾回收的IdleHandler void unscheduleGcIdler() { if (mGcIdlerScheduled) { mGcIdlerScheduled = false; Looper.myQueue().removeIdleHandler(mGcIdler); } mH.removeMessages(H.GC_WHEN_IDLE); } final GcIdler mGcIdler = new GcIdler(); final class GcIdler implements MessageQueue.IdleHandler { @Override public final boolean queueIdle() { doGcIfNeeded(); return false; } } void doGcIfNeeded() { mGcIdlerScheduled = false; final long now = SystemClock.uptimeMillis(); if ((BinderInternal.getLastGcTime()+MIN_TIME_BETWEEN_GCS) < now) { // 执行垃圾回收 BinderInternal.forceGc("bg"); } } 复制代码
这里通过一个自定义的IdleHandler
进行GC
的调用,在线程空闲的时候会调用mGcIdler
,最终通过BinderInternal.forceGc("bg")
方法触发GC
,这个GC
的最小间隔是5秒(MIN_TIME_BETWEEN_GCS
的值)。并且注意了,mGcIdler
的queueIdle
返回的是false
,所以这个mGcIdler
会长期存在于主线程的MessageQueue
中。
这里会关联到ActivityThread
的attach
方法中注册的一个GcWatcher
,它会在App进程内存占用超过虚拟机分配的最大内存的3/4时,对一些Activity进行释放,更具体的分析可以参考这篇文章:关于Activity回收你要知道的事情
最后,如果觉得这篇文章对你有帮助,也帮忙点个赞吧~