Android屏幕刷新机制(二): invalidate()发生了什么

参考

文章完全参考自:Android 屏幕刷新机制,简化了部分代码逻辑。

invalidate()

如果大家了解View体系里的相关知识应该知道,每个Activity 的根布局都是 DecorViewDecorView 的 parent 又是 ViewRootImpl,所以子view执行invalidate()方法时最终会引导到ViewRootImpl中来。

ViewRootImplinvalidate()方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void invalidate() {
mDirty.set(0, 0, mWidth, mHeight);
if (!mWillDrawSoon) {
scheduleTraversals();
}
}

void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
//看这句:postCallback
mChoreographer.postCallback(
// mTraversalRunnable
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}

mChoreographer.postCallback()第二个参数传入了一个runnable对象,先看这个对象是啥。

TraversalRunnable

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}

void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

if (mProfile) {
Debug.startMethodTracing("ViewAncestor");
}
//看这里~
performTraversals();

if (mProfile) {
Debug.stopMethodTracing();
mProfile = false;
}
}
}

会跳转到performTraversals中来,这个方法是界面刷新的源头。这个方法使得ViewTree开始View的工作流程。performMeasure、performLayout、performDraw方法都在里面。

回过头再看这行代码:

1
mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);

Choreographer.postCallback

再看这个方法。

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
public void postCallback(int callbackType, Runnable action, Object token) {
postCallbackDelayed(callbackType, action, token, 0);
}
public void postCallbackDelayed(int callbackType,Runnable action, Object token, long delayMillis) {
...
postCallbackDelayedInternal(callbackType, action, token, delayMillis);
}
private void postCallbackDelayedInternal(int callbackType,
Object action, Object token, long delayMillis) {
synchronized (mLock) {
final long now = SystemClock.uptimeMillis();
final long dueTime = now + delayMillis;
//哦豁
mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);

if (dueTime <= now) {
scheduleFrameLocked(now);
} else {
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
msg.arg1 = callbackType;
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, dueTime);
}
}
}

哦豁,期盼许久的addCallbackLocked,在这里出现了。上一篇不是不知道传进来的token、action是什么么?现在知道了。token是null,action是TraversalRunnable,所以屏幕发送VSync信号后,系统走的是TraversalRunnable.run()方法。

然后到了performTraversals中来,开始重绘界面。

invalidate()方法所做的,不过是向CallbackQueue中添入一个CallbackRecord

image-20201101151630877

scheduleFrameLocked

还没走完,下面还有几行。一个判断,看看scheduleFrameLocked方法。

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
if (dueTime <= now) {
scheduleFrameLocked(now);
} else {
//这个mHandler,便是我们久违的FrameHandler
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);//2
msg.arg1 = callbackType;
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, dueTime);
}

private void scheduleFrameLocked(long now) {
if (!mFrameScheduled) {
mFrameScheduled = true;
if (USE_VSYNC) {
// If running on the Looper thread, then schedule the vsync immediately,
// otherwise post a message to schedule the vsync from the UI thread
// as soon as possible.
//翻译:如果在主线程(Looper)就立即计划(schedule)一个vsync,否则快马加鞭传信给主线程,
//让主线程 马上(as soon as possible) 计划(schedule)一个vsync。
if (isRunningOnLooperThreadLocked()) {
scheduleVsyncLocked();
} else {
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC);//1
msg.setAsynchronous(true);
mHandler.sendMessageAtFrontOfQueue(msg);
}
} else {
final long nextFrameTime = Math.max(
mLastFrameTimeNanos / TimeUtils.NANOS_PER_MS + sFrameDelay, now);
Message msg = mHandler.obtainMessage(MSG_DO_FRAME);//0
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, nextFrameTime);
}
}
}

这里的mHandle,便是我们久违的FrameHandler。看样子是分不同的情况,传不同的信息(message)走FrameHandler了。官方的注释也写的很清楚,尽快计划一个VSync,虽然不知道计划VSync是什么鬼。

那就看代码:追踪scheduleVsyncLocked();会发现最终会走到DisplayEventReceiver.scheduleVsync()方法中来。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* Schedules a single vertical sync pulse to be delivered when the next
* display frame begins.
* 计划一个VSync脉冲,用于下一帧到来时的交付。
* 说人话:注册一个监听器,用于监听下一帧的VSync信号
*/
@UnsupportedAppUsage
public 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);
}
}

可见最后这里会注册一个监听器,表明已经有屏幕绘制的安排了,下一帧要用我绘制的帧。

如果你记忆力不错的话,应该记得 Android屏幕刷新机制(一)开篇第一段代码就是引用的这个代码。那时候就说了onVsync之前要先scheduleVsync,就在这里执行了。

再看看另外三种情况:

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
private final class FrameHandler extends Handler {
public FrameHandler(Looper looper) {
super(looper);
}

@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_DO_FRAME:
//开始制作下一帧
doFrame(System.nanoTime(), 0);
break;
case MSG_DO_SCHEDULE_VSYNC:
//走scheduleVsyncLocked,还是立即执行了
doScheduleVsync();
break;
//做个判断,然后走scheduleFrameLocked,还是立即执行了
case MSG_DO_SCHEDULE_CALLBACK:
doScheduleCallback(msg.arg1);
break;
}
}
}

void doScheduleVsync() {
synchronized (mLock) {
if (mFrameScheduled) {
scheduleVsyncLocked();
}
}
}

void doScheduleCallback(int callbackType) {
synchronized (mLock) {
if (!mFrameScheduled) {
final long now = SystemClock.uptimeMillis();
if (mCallbackQueues[callbackType].hasDueCallbacksLocked(now)) {
scheduleFrameLocked(now);
}
}
}
}

可见,出了MSG_DO_FRAME,其他的情况都是走向了nativeScheduleVsync,调用原生的方法把图像传递给了屏幕。MSG_DO_FRAME的意思就是这一帧没有图像了,系统直接去取下一帧的图像了。

总结

最后,放张流程图总结。

image-20201101185948648