需要先仔细阅读一下原文,我这里只是把一些讲解步骤抽炼出来,使得看起来更直观。
涉及到的源码,基本上都是按照调用顺序贴出来的,且版本为 SDK 27。
根据原文可以知道, View#invalidate()
最终会走到 ViewRootImpl#scheduleTraversals()
其中,invalidate 的字面意思是 vt. 使无效;使无价值
,而 View#invalidate()
作用如下:
简单的说,就是使 View 原本的内容无效,从而使得其被刷新。
每个 Activity 对应一颗以 DecorView 为根布局的 View 树,但其实 DecorView 还有 mParent,而且就是 ViewRootImpl,而且每个界面上的 View 的刷新,绘制,点击事件的分发其实都是由 ViewRootImpl 作为发起者的,由 ViewRootImpl 控制这些操作从 DecorView 开始遍历 View 树去分发处理。
Activity 的启动是在 ActivityThread 里完成的,handleLaunchActivity()
会依次间接的执行到 Activity 的 onCreate()
, onStart()
, onResume()
。在执行完这些后 ActivityThread 会调用 WindowManager#addView()
,而这个 addView()
最终其实是调用了 WindowManagerGlobal 的 addView()
方法。然后接下来做的事如下:
// 传入的 view 即为 DecorView 实例
void addView(view,....) {
// 实例化 ViewRootImpl
root = new ViewRootImpl(view.getContext(), display);
root.setView(view, wparams, panelParentView);
}
// 传入的 view 即为 DecorView
void setView(view,....) {
requestLayout();//第一次请求布局,其内部调用 scheduleTraversals();
// assignParent() 方法的作用就将当前 viewRootImpl 实例
// 做为 view(即 decorView) 的 parent,
// 使之绑定在一起
view.assignParent(this);
}
void assignParent(ViewParent parent) {
if (mParent == null) {
mParent = parent;
} else if (parent == null) {
mParent = null;
} else {
throw new RuntimeException("view " + this + " being added, but"
+ " it already has a parent");
}
}
经过上面的步骤,可以了解到 decorView
是怎么与 viewRootImpl
进行绑定的。
从原文可以了解到,调用一个 View 的 invalidate()
请求重绘操作,内部原来是要层层通知到 ViewRootImpl 的 scheduleTraversals()
里去。而且打开一个新的 Activity,它的界面绘制原来是在 onResume()
之后也层层通知到 ViewRootImpl 的 scheduleTraversals()
里去。
下面就整理了一下 scheduleTraversals()
的逻辑流程。
void scheduleTraversals() {
...
mTraversalScheduled = true;
// 在主线程的消息队列中添加一个 Barrier msg
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
// 通过 mChoreographer,会根据实际情况来判断是将 mTraversalRunnable 作为一个
// 异步 msg 添加到主线程的消息队列中,还是会马上触发后面会说到的 scheduleVsyncLocked()
// 方法
// mTraversalRunnable 的 run() 方法调用 doTraversal()
// 简单的说 mTraversalRunnable 是一个执行遍历 View 树的任务(runnable)
mChoreographer
.postCallback(Choreographer.CALLBACK_TRAVERSAL,
mTraversalRunnable,
null)
...
}
// ViewRootImpl 的内部类
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
// 移除主线程 MessageQueue 中的 Barrier msg
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
// 执行遍历
performTraversals();
...
}
}
void performTraversals() {
// 根据实际情况发起测量、布局、绘制三大流程
}
// 传入的 action 即前面的 mTraversalRunnable
postCallback(..., Runnable action, ...) {
// 最终调用 postCallbackDelayedInternal(..., delayMillis == 0, token == null)
}
// 传递进来的 action 即前面的 mTraversalRunnable,
// callbackType 即前面的 Choreographer.CALLBACK_TRAVERSAL
// 传入的 token 为 null
void postCallbackDelayedInternal(int callbackType,
Object action, Object token, long delayMillis) {
...
synchronized (mLock) {
final long now = SystemClock.uptimeMillis();
final long dueTime = now + delayMillis;
// 把前面传入的 mTraversalRunnable 添加进
// mCallbackQueue[Choreographer.CALLBACK_TRAVERSAL] 队列里
// 在添加到队列里的时候,会结合 token 把 mTraversalRunnable 封装成一个
// CallbackRecord 对象,且该对象的成员变量 token==null
// 这个队列跟 MessageQueue 很相似,里面待执行的任务都是根据一个时间戳来排序
mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
if (dueTime <= now) {
// 因为 delayMillis == 0,所以 dueTime == now
// 马上执行 if 中的逻辑
scheduleFrameLocked(now);
} else {
// 如果有延迟,则设置一个异步 msg 到主线程的消息队列中
// 等时间到了再取出来执行该 msg 对应的逻辑,其实最终
// 也是执行 scheduleFrameLocked(now)
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
msg.arg1 = callbackType;
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, dueTime);
}
// 因此对于 if-else 语句,最终都会调用 scheduleFrameLocked()
}
}
//处理上述 MSG_DO_SCHEDULE_CALLBACK 类型的 msg 的逻辑
private final class FrameHandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
...
case MSG_DO_SCHEDULE_CALLBACK:
doScheduleCallback(msg.arg1);
break;
}
}
}
void doScheduleCallback(int callbackType) {
synchronized (mLock) {
if (!mFrameScheduled) {
final long now = SystemClock.uptimeMillis();
if (mCallbackQueues[callbackType].hasDueCallbacksLocked(now)) {
scheduleFrameLocked(now);
}
}
}
}
// Choreographer 的静态内部类
private static final class CallbackRecord {
public CallbackRecord next;
public long dueTime;
public Object action; // Runnable or FrameCallback
public Object token;
public void run(long frameTimeNanos) {
if (token == FRAME_CALLBACK_TOKEN) {
((FrameCallback)action).doFrame(frameTimeNanos);
} else {
// 当为 TraversalRunnable 时,对应当 token 为 null
((Runnable)action).run();
}
}
}
void scheduleFrameLocked() {
if (!mFrameScheduled) {
mFrameScheduled = true;
if (USE_VSYNC) { // USE_VSYNC 默认为 true
// 判断是不是在主线程中
if (isRunningOnLooperThreadLocked()) {
// 如果在主线程中则直接执行 scheduleVsyncLocked()
scheduleVsyncLocked();
} else {
// 如果不是在主线程则通过主线程的 handler 切换到主线程
// 如果代码走了 else 这边来发送一个消息,那么这个消息做的事肯定很重要,因为
// 对这个 Message 设置了异步的标志而且用了sendMessageAtFrontOfQueue()
// 方法,这个方法是将这个 Message 直接放到 MessageQueue 队列里的头部,
// 可以理解成设置了这个 Message 为最高优先级
scheduleVsyncLocked()
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC);
msg.setAsynchronous(true);
mHandler.sendMessageAtFrontOfQueue(msg);
}
// 因此对于 if-else 语句,最终都会调用 scheduleVsyncLocked()
} else {
...
}
}
}
// 处理 MSG_DO_SCHEDULE_VSYNC 类型的 msg (部分源码)
case MSG_DO_SCHEDULE_VSYNC: doScheduleVsync();
void doScheduleVsync() {
synchronized (mLock) {
if (mFrameScheduled) {
scheduleVsyncLocked();
}
}
}
void scheduleVsyncLocked() {
mDisplayEventReceiver.scheduleVsync();
}
void scheduleVsync() {
if (mReceiverPtr == 0) {
Log.w(TAG, "Attempted to schedule a vertical sync pulse but the display event "
+ "receiver has already been disposed.");
} else {
nativeScheduleVsync(mReceiverPtr);
}
}
nativeScheduleVsync(mReceiverPtr)
为 native 方法,其作用根据原文也可以知道,大概就是向底层注册监听下一帧 VSync 信号,有点类似于观察者模式,或者说发布-订阅模式,这样底层在发信号的时候,直接去找这些观察者通知它们就行了。
看到这里,那么在 scheduleTraversals()
中调用 mChoreographer.postCallback(...,mTraversalRunnable...)
的意图就很明显,即将执行 performTraversals()
(遍历 View 树)操作的任务 ViewRootImpl#mTraversalRunnable
放进队列 mCallbackQueues[CALLBACK_TRAVERSAL]
中,并注册监听下一帧 VSync 信号。
需要注意的是,scheduleVsync()
只是监听下一个屏幕刷新信号的事件而已,而不是监听所有的屏幕刷新信号。比如说当前监听了第一帧的刷新信号事件,那么当第一帧的刷新信号来的时候,上层 app 就能接收到事件并作出反应。但如果还想监听第二帧的刷新信号,那么只能等上层 app 接收到第一帧的刷新信号之后再去监听下一帧。
但是这里还只是半部分内容,因为后面还会说明 ViewRootImpl#mTraversalRunnable
该任务(即遍历 View 树)会在什么时候被触发。
根据原文,可以知道在成功向底层注册监听之后,在下一帧 Vsync 发生时会由底层回调 app 层的 FrameDisplayEventReceiver#onVsync()
方法:
FrameDisplayEventReceiver 继承自 DisplayEventReceiver 接收底层的 VSync 信号开始处理UI过程。
VSync 信号由 SurfaceFlinger 实现并定时发送。FrameDisplayEventReceiver 收到信号后,
调用 onVsync() 方法组织消息发送到主线程处理。这个消息主要内容就是 run 方法里面的 doFrame() 方法了,
这里 mTimestampNanos 是信号到来的时间参数。
FrameDisplayEventReceiver 是 Choreographer 的内部类
public class Choreographer {
...
void doFrame(){...}
private final class FrameDisplayEventReceiver
extends DisplayEventReceiver implements Runnable {
...
// 这里 mTimestampNanos 是信号到来的时间参数
public void onVsync(long timestampNanos,
int builtInDisplayId, int frame) {
mTimestampNanos = timestampNanos;
mFrame = frame;
// 因为 FrameDisplayEventReceiver 继承了 Runnable
// 所以这里获得一个 msg 并设置其 callback 为当前
// frameDisplayEventReceiver 实例
Message msg = Message.obtain(mHandler, this);
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
}
public void run() {
mHavePendingVsync = false;
// odFrame() 为 Choreographer 的成员方法
doFrame(mTimestampNanos, mFrame);
}
}
}
上述逻辑会在主线程对应的 MessageQueue
中添加一个 msg
(目的应该是为了从底层回调 onVsync()
的线程切换到 app 所在的主线程),且该 msg
的 callback
即为 FrameDisplayEventReceiver
(FrameDisplayEventReceiver
实现了 Runnable
接口)。
因此当轮询到这个 msg 的时候,就会执行 Choreographer
的 doFrame()
方法。
void doFrame() {
...
doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
...
}
void doCallbacks(int callbackType, long frameTimeNanos) {
CallbackRecord callbacks;
...
// now 用于判断对头任务是否达到可执行时刻
final long now = System.nanoTime();
// 根据 now 时间戳取出 mCallbackQueues[Choreographer.CALLBACK_TRAVERSAL] 队列中
// 第一个符合条件的封装有 mTraversalRunnable 的 CallbackRecord 实例
callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked(
now / TimeUtils.NANOS_PER_MS);
if (callbacks == null) {
return;
}
...
// 从前面得到的第一个符合 now 时间戳条件的 callbackRecord 实例开始,
// 一次性执行该 callbackRecord 实例以及其后面的 callbackRecord 实例
for (CallbackRecord c = callbacks; c != null; c = c.next) {
// 根据前面的内容可以知道 callbackRecord 封装了 mTraversalRunnable 且
// 其 token == null
// 因此根据 CallbackRecord 的源码,c.run() 会触发 mTraversalRunnable
// 的 run() 方法(该 run() 方法是做啥的心里总有点逼数吧!!!)
c.run(frameTimeNanos);
}
}
对于触发 ViewRootImpl#mTraversalRunnable
部分的内容,比较好理解了,当由底层回调了 app 相关的方法后,就会进一步触发 ViewRootImpl#mTraversalRunnable
,从而实现遍历 View 树。
对于屏幕刷新机制,简单的概括的话总共分为两大步:
接下来就要说一下相关的知识点了:
在执行 ViewRootImpl#scheduleTraversals()
的时候,就有一行代码:
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
该行代码的作用就是在主线程的消息队列中添加一个 Barrier msg
,如下演示图:
当轮训到 Barrier msg 的时候,就会以此点开始,遍历接下来的数个 msgs 中的异步 msg。
而这些异步 msg 都是在进行与屏幕刷新相关的逻辑时添加的。之所以这样做,是为了以最快的速度处理这些异步 msg,从而不影响到正常的屏幕刷新的相关逻辑。
(有关具体的 Barrier msg 的原理,可以参阅:Handler 笔记总结)
而异步 msg 添加的时机,主要有如下几个:
Choreographer#postCallbackDelayedInternal
这里是为了有延迟时能够等时间到了再触发 scheduleFrameLocked()
Choreographer#scheduleFrameLocked
这里是为了保证在主线程中马上触发 scheduleVsyncLocked()
,因为这里涉及到 msg 是异步的,而且是直接放在队首的(比前面设置的 Barrier msg 的位置还要靠前)
FrameDisplayEventReceiver#onVsync
这里是为了触发 mCallbackQueues[CALLBACK_TRAVERSAL]
中被封装成 CallbackRecord
的 ViewRootImpl#mTraversalRunnable
(即最终与屏幕刷新相关的任务——遍历 View 树)
而移除 Barrier msg 是在 ViewRootImpl#doTraversal()
中,执行 performTraversals()
前。
引用自原文:
在执行 scheduleTraversals()
的时候,会把一个变量值 mTraversalScheduled
置为 true
,而只在三个地方被赋值为 false
,一个是前面提及的 doTraversal()
,还有就是声明时默认为 false,剩下一个是在取消遍历绘制 View 操作 unscheduleTraversals()
里。
也就是说,当我们调用了一次 scheduleTraversals()之后,直到下一个屏幕刷新信号来的时候,doTraversal() 被取出来执行。在这期间重复调用 scheduleTraversals() 都会被过滤掉的。
View 最终是怎么刷新的呢,就是在执行 performTraversals()
遍历绘制 View 树过程中层层遍历到需要刷新的 View,然后去绘制它的吧。既然是遍历,那么不管上一帧内有多少个 View 发起了刷新的请求,在这一次的遍历过程中全部都会去处理的吧。这也是我们从代码上看到的,每一个屏幕刷新信号来的时候,只会去执行一次 performTraversals()
,因为只需遍历一遍,就能够刷新所有的 View 了。
而 performTraversals()
会被执行的前提是调用了 scheduleTraversals()
来向底层注册监听了下一个屏幕刷新信号事件,所以在同一个 16.6ms 的一帧内,只需要第一个发起刷新请求的 View 来走一遍 scheduleTraversals()
干的事就可以了,其他不管还有多少 View 发起了刷新请求,没必要再去重复向底层注册监听下一个屏幕刷新信号事件了,反正只要有一次遍历绘制 View 树的操作就可以对它们进行刷新了。
自己的理解:
在同一刷新帧内,多次发起刷新请求,只有第一次的有效 ,后面的则会因为特殊的一个标记值而失效,但这并不影响所有 View 刷新到第一次请求之后的后序请求对应的状态,因为这些状态值会实时的更新在 View 对象中并得以保存。
比如设置了一个 View 的 width 为 x1,然后请求刷新,此时在同一刷新帧内,又重新设置为 x2,再次请求刷新,这一次请求是不会生效的,但是此时 View 的 width 却已经更新为了 x2,而不是第一次的 x1,因此在刷新的时候会根据 x2 来展示 View 的宽。
另外,在 Choreographer
中也有一个成员变量,mFrameScheduled
,其作用也跟 mTraversalScheduled
类似,用于过滤多余的触发
private void scheduleVsyncLocked() {
mDisplayEventReceiver.scheduleVsync();
}
的操作。
当下一帧信号来临的时候,底层会回调触发 app 的 “遍历 View 树的操作”,但是在触发操作前,需要先在 mCallbackQueues[Choreographer.CALLBACK_TRAVERSAL]
中找到对应的包含 mTraversalRunnable
的 CallbackRecord
进行处理,才能进一步触发操作。
因此,从更加小的时间粒度来看,并不是信号来了就能马上开始执行遍历 View 树的操作,之间还是会存在一些 “间隙”。
另外,也可能由于某些原因,比如 CPU 资源被占用,导致没有及时的去处理,也会导致 “间隙” 的产生。
而 “遍历 View 树的操作” ,其实就是上图中 CPU 对应的那一横排,简单的说,CPU 对应的操作就是测量,绘制,布局等(即一系列的指令),并将这些操作数据化(而不是直接将内容绘制到屏幕上),然后 CPU 会把这些数据传递给 GPU,GPU 将其转换为 Display 所需要的数据,然后 Display 将其读取并展示出来。
CPU 的操作+ GPU 的操作
需要在下一帧开始前完成,不然 Display 就取不到该帧的内容且无法展示。
CPU负责包括Measure,Layout,Record,Execute的计算操作,
GPU 负责Rasterization(栅格化)操作。
引用自:Android性能优化第(四)篇—Android渲染机制
加了同步屏障到移除前这段时间是干不了我们自己通过主线程 Handler
来分配的工作的,但这些工作最多是被延迟一帧而已,到下一帧都会被处理掉。
这里可以关注一下 Handler 笔记总结 提到的 Idle Handler,其可以在 MessageQueue#next()
中取不到符合条件的 msg 时被调用执行。
前面说的,对于注册监听 Vsync 信号,只会监听下一帧的 Vsync 信号,而不是所有的,因此推测除了 Activity 首次初始化的时候会主动注册关于下一帧 Vsync 信息的监听,剩下的,因为都是在 View 的内容需要刷新的时候才被动去注册的。
关于 “被动”,指是,假设整个 Activity 只有一个 TextView,最初设置的 text 为 “Default”,如果 TextView 一直没有变化,则就不会去注册对于下一帧 Vsync 信号的监听,除非使得 TextView 的内容发生变化,此时设置其变化的代码里面应该会包含类似于 View#invalidate()
的逻辑(即被动设置),所以能够注册关于下一帧 Vsync 信号的监听,从而在下一帧中实现内容的刷新。
而如果没有设置对下一帧 Vsync 信号的监听,此时呈现的内容一直是之前的,也就没有变化。
还有一种推测就是系统会自己实现在处理了新的一帧 Vsync 的信号之后,再自动去实现关于下一帧信号的监听。而如果是这样的话,则相当于一环扣一环的监听每一帧 Vsync 信号,此时,程序猿也没有必要再手动调用 View#invalidate()
了,还有一个点可以从侧面来推到这个,那就是 View 的 onDraw()
方法并不是一直实时在调用的。
而且,还有一点,就是 View 树中的某一个 View 触发 invalidate()
,导致最终触发 ViewRootImpl#performTraversals()
的时候,并不会导致所有的子 View 都重绘(通过针对自定义的 View 的 onDraw() 方法测试的),大概的原因就是每个 View 对应的绘制内容都会有缓存,只有需要刷新内容的时候,才会更新缓存,进行重绘。
关于这点补充,可以通过源码证明,即在首次启动 Activity 的时候,需要将 Activity resume,涉及到的源码部分即ActivityThread#handleResumeActivity()
:
(1)在其中有执行 wm.addView()
这样一句代码,而这里的 wm
具体为 WindowManagerGlobal
,这句代码的作用之一就是为了将 DecorView
与 ViewRootImp
绑定,
(2)并且在 wm.addView()
中会进一步执行 root.setView()
,root
即 ViewRootImpl
,
(3)而在 root.setView()
中,就会首次调用 ViewRootImpl#requestLayout()
,从而进一步触发 scheduleTraversals()
。
而且上述步骤是在执行了 Activity#onResume()
之后才执行的。
额外阅读内容:
1、android屏幕刷新显示机制
2、Android应用程序UI硬件加速渲染的Display List构建过程分析
3、Android图形系统之 VSync