ScreenDraw

从onCreate到页面绘制

当手指点击了桌面的App图标时发生了什么 - ProcessOn

Android 屏幕刷新机制

主要参考 https://juejin.cn/post/6863756420380196877#heading-12

省流版:

双缓存:为了解决画面撕裂;画面撕裂来自于只有一个buffer时,正在display的那一帧数据被后一帧的数据覆盖了

Vsync:系统在收到VSync pulse(Vsync脉冲)后,将马上开始下一帧的渲染,(CPU开始计算数据)。

三缓冲:当显示器正在写入FrameBuffer同时GPU也正在写入BackBuffer时,下一次渲染开始了,此时CPU可以使用新增的GraphicBuffer进行计算。减少了Jank。(更多缓冲需要耗费更大的内存)

ChoreoGrapher机制:规定了数据计算开始(measure、layout、draw)的时机(vsync信号),使计算到渲染图像数据能有一个完整的16.6ms:更新ui(request()/invalidate())后编舞者注册vsync信号回调,在下一个vsync信号到时候立刻进行view的测量布局绘制

一、缓冲和Vsync

https://juejin.cn/post/6863756420380196877

1、单缓冲,tearing

图像撕裂.png

由于Gpu跟显示器使用同一个缓冲,导致可能屏幕扫描刷新时,可能读取到的不是同一帧里的图像数据,造成画面撕裂

2、↓ 双缓冲 ↓

双缓冲是为了解决“由于Gpu跟显示器使用同一个缓冲,导致可能屏幕扫描刷新时,可能读取到的不是同一帧里的图像数据,造成画面撕裂”,让绘制和显示器拥有各自的buffer:

GPU 的图像数据写入到 Back Buffer,而显示器使用 Frame Buffer,当屏幕刷新时,Frame Buffer 并不会发生变化,当Back buffer准备就绪后,它们才进行地址交换。(缓存区交换被称为BufferSwap帧传递。)

上面说到的Back Buffer 跟 Frame Buffer地址交换,时间点选择在了 Back buffer完整写入之后,在之后屏幕扫描完一个屏幕(从左到右,从上到下逐行显示每一个像素点,整个过程以60Hz屏为例是16.6ms)后,设备从右下回到左上的这个时间区间(即VBI VerticalBlackingInterval垂直同步间隙),而垂直同步脉冲(vertical sync pulse)就是在VBI时期发出的,脉冲发出时间时立即进行帧传递

总结下就是:垂直同步脉冲是在屏幕扫描到右下最后一个像素后,重置回到左上的这个时间空隙发出的,所以每16.6ms(60HZ屏)发出一个脉冲信号。收到脉冲信号后,如果GPU的缓冲已经准备好了,就会立即进行帧传递。

但是,双缓存只是规定了图像数据数据写入BackBuffer完成后,FrameBuffer与BackBuffer交换的时机,而数据开始计算时间的不确定,则导致了下一帧中,CPU/GPU未能在帧开始的时候就进行计算,进而导致帧结束时CPU/GPU工作未完成(明明CPU/GPU工作时长小于16.6ms),却还是造成掉帧(jank)。

3、↓ 三缓冲 + Vsync ↓

Android 4.x版本的黄油工程 project butter引入三缓冲与Vsync,提升了性能,促使了Android的普及

3.1、 VSync 机制

Android实现即下述第二节choreographer机制

系统在收到VSync pulse(Vsync脉冲)后,将马上开始下一帧的渲染。即一旦收到VSync通知(16ms触发一次),CPU和GPU 才立刻开始计算然后把数据写入buffer。VSync同步使得CPU/GPU充分利用了16.6ms时间,减少jank

3.2、 三缓冲

CPU、GPU、显示器都能尽快拿到 buffer,减少不必要的等待。如果显示器和 GPU 现在都使用着一个 buffer,如果下一次渲染开始了,因为还有一个 buffer 可以用于 CPU 数据的写入,所以可以马上开始下一帧数据的渲染。

三缓冲就是在双缓冲机制基础上增加了一个 Graphic Buffer 缓冲区,这样可以最大限度的利用空闲时间,带来的坏处是多使用的一个 Graphic Buffer 所占用的内存。三缓冲有效利用了等待vysnc的时间,减少了jank,但是带来了延迟。

vsync4.png

二、Choreographer机制 或称 “drawing with vysnc”

image-20210410163432028

img

-> requestLayout() / invalidate() 所有UI的变化都是走到ViewRootImpl的scheduleTraversals()方法。

-> scheduleTraversals()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void scheduleTraversals() {
if (!mTraversalScheduled) {
// 保证同时间多次更改只会刷新一次,例如TextView连续两次setText()也只会走一次绘制流程
mTraversalScheduled = true;
// 添加同步屏障,保证 Vsync 到来立即执行绘制
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
// ...
}
}
// mTraversalRunnable 是Vsync信号回调后执行的Runnable 实例
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}

mChoreographer.postCallback() // 注册监听Vsync信号(只有注册了监听的Vsync才会回调执行)回调处理内容为mTraversalRunnable,事件类型为CALLBACK_TRAVERSAL(CALLBACK_INPUT输入事件、CALLBACK_ANIMATION动画、CALLBACK_INSETS_ANIMATION插入更新动画、CALLBACK_TRAVERSAL绘制、CALLBACK_COMMIT提交,五种类型顺序执行)

postCallbackDelayedInternal

  • if (dueTime <= now) : 直接执行scheduleFrameLocked
  • else : msg.setAsynchronous(true); 发送延迟到点后的异步消息执行scheduleFrameLocked

-> scheduleFrameLocked()

  • 根据 版本未开启VSYN(4.1以下)直接走doFrame
  • 确认并切换到Choreographer的handler(异步消息)执行 scheduleVsyncLocked()

-> scheduleVsncLocked()

-> scheduleVsync()

-> nativeScheduleVsync(mReceiverPtr);

  • 注册VSYNC信号回调,只有注册监听的那一个Vsync信号才会接收回调最后在onVsync()

这里不应该为软件申请了Vsync信号,Vsync信号是由屏幕/显示设备发出的,无论choreographer是否监听都会在固定的时间间隔发出(60fps->16.6ms),View更新后由choreographer注册监听后才会对下一个Vsync信号回调回来后处理,走doCallbacks ,也就是最初由choreographer注册的回调mChoreographer.postCallback() -> doTraversal() -> 移除同步屏障并真正执行View的measure、layout、draw流程

所以每16ms都会发出Vsync信号,每16ms屏幕都在刷新,但不是每16ms都会走measure、layout、draw

native层注册监听Vsync回调,DisplayEventReceiver::requestNextVsync()

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
// frameworks/base/core/jni/android_view_DisplayEventReceiver.cpp
static void nativeScheduleVsync(JNIEnv* env, jclass clazz, jlong receiverPtr) {
sp<NativeDisplayEventReceiver> receiver = reinterpret_cast<NativeDisplayEventReceiver*>(receiverPtr);
status_t status = receiver->scheduleVsync();
// ...
}

// frameworks/base/libs/androidfw/DisplayEventDispatcher.cpp
status_t DisplayEventDispatcher::scheduleVsync() {
if (!mWaitingForVsync) {
// ...
// mReceiver 是 DisplayEventReceiver 实例
status_t status = mReceiver.requestNextVsync();
mWaitingForVsync = true;
}
return OK;
}

// frameworks/native/libs/gui/DisplayEventReceiver.cpp
status_t DisplayEventReceiver::requestNextVsync() {
if (mEventConnection != NULL) {
// 请求接收下一次Vsync信号的回调
mEventConnection->requestNextVsync();
return NO_ERROR;
}
return NO_INIT;
}

native Vsync信号回调回来了,由FrameDisplayEventReceiver的onVsync方法接收:

-> onVsync()

-> doFrame()

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
void doFrame(long frameTimeNanos, int frame) {
final long startNanos;
synchronized (mLock) {
if (!mFrameScheduled) {
return; // no work to do
}

//...
// 计算掉帧情况 frameTimeNanos是onVsync()的参数,即vsync信号开始时间,以frameTimeNanos为start_time,
//以doFrame方法执行开始时间为end_time, start_time - end_time为帧延迟,以此 时间差/标准帧时间 计算掉帧情况
//标准帧时间 = 1000000000 / 屏幕帧率 单位纳秒 (1纳秒=0.000 000 001秒 )
//...
try {
// 按类型顺序 执行任务
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Choreographer#doFrame");
AnimationUtils.lockAnimationClock(frameTimeNanos / TimeUtils.NANOS_PER_MS);

mFrameInfo.markInputHandlingStart();
doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos); //输入事件,即熟悉的事件分发体系

mFrameInfo.markAnimationsStart();
doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos); //动画
doCallbacks(Choreographer.CALLBACK_INSETS_ANIMATION, frameTimeNanos); //动画

mFrameInfo.markPerformTraversalsStart();
doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos); //测绘

doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos); //提交,处理绘制后的帧更新
} finally {
AnimationUtils.unlockAnimationClock();
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}

doFrame()
-> 按callbacktype依次执行将doCallbacks(),即执行doCallbacks(0、1、2、3、4,? frameTimeNanos)

doCallbacks(int callbackType, long frameTimeNanos)
-> 根据传入的callbackType从mCallbackQuesu中取出对应的队列,执行队列中到达执行时间的CallbackRecord

即依次 将 输入队列、 动画队列、绘制队列、提交队列 中所有的到达执行时间的 元素(CallbackRecord)
执行其中的action(viewRootImpl 中mChoreographer.mpostCallback时传递的mTraversalsRunnable)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
void doCallbacks(int callbackType, long frameTimeNanos) {
CallbackRecord callbacks;
synchronized (mLock) {

final long now = System.nanoTime();
// 根据指定的类型CallbackkQueue中查找到达执行时间的CallbackRecord
callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked(now / TimeUtils.NANOS_PER_MS);
//...
try {
// 迭代执行队列所有任务
for (CallbackRecord c = callbacks; c != null; c = c.next) {
// 回调CallbackRecord的run,其内部回调Callback的run
c.run(frameTimeNanos);
}
} finally {
synchronized (mLock) {
//...
//递归回收CallbackRecord
//...
}
}
}

callbacktype分为5种(实际上是四种,新版本Android合并后只剩四种事件:输入、动画、遍历traversal、提交):

1
2
3
4
5
6
7
8
9
//输入事件,首先执行 public static final int CALLBACK_INPUT = 0; 

//动画合并成一种
//动画,第二执行 public static final int CALLBACK_ANIMATION = 1;
//插入更新的动画,第三执行 public static final int CALLBACK_INSETS_ANIMATION = 2;

//绘制,第四执行 public static final int CALLBACK_TRAVERSAL = 3;

//提交,最后执行, public static final int CALLBACK_COMMIT = 4;

优先级的高低和处理顺序有关,每当收到 VSYNC 信号时,Choreographer 将首先处理 INPUT 类型的任务,然后是 ANIMATION 类型,最后才是 TRAVERSAL 类型。

-> doCallbacks()

-> mTraversalRunnable

-> doTraversal()

1
2
3
4
5
6
7
8
9
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
// 移除同步屏障
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
// 真正执行View的measure,layout,draw流程
performTraversals();
}
}

附录 Choreographer部分源码(Android4.1新增)

Choreographer 是线程单例的(ThreadLocal实现),而且必须要和一个 Looper 绑定,因为其内部有一个 Handler 需要和 Looper 绑定,一般是 App 主线程的 Looper 绑定

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
//Choreographer.class  

//postCallback(int callbackType, Object action, Object token) 会一步步走到postCallbackDelayedInternal(delayMills = 0)

private void postCallbackDelayedInternal(int callbackType, Object action, Object token, long delayMillis) {
//...
synchronized (mLock) {
// 当前时间
final long now = SystemClock.uptimeMillis();
// 加上延迟时间
final long dueTime = now + delayMillis;
//取对应类型的CallbackQueue添加任务
mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);

if (dueTime <= now) {
//立即执行
scheduleFrameLocked(now);
} else {
//延迟运行,最终也会走到scheduleFrameLocked()
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
msg.arg1 = callbackType;
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, dueTime);
}
}
}

private void scheduleFrameLocked(long now) {
if (!mFrameScheduled) {
mFrameScheduled = true;
//开启了VSYNC
if (USE_VSYNC) {
if (DEBUG_FRAMES) {
Log.d(TAG, "Scheduling next frame on vsync.");
}


//当前执行的线程,是否是mLooper所在线程
if (isRunningOnLooperThreadLocked()) {
//申请 VSYNC 信号
scheduleVsyncLocked();
} else {
// 若不在,就用mHandler发送消息到原线程,最后还是调用scheduleVsyncLocked方法
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC);
msg.setAsynchronous(true);//异步
mHandler.sendMessageAtFrontOfQueue(msg);
}
} else {
// 如果未开启VSYNC则直接doFrame方法(4.1后默认开启)
final long nextFrameTime = Math.max(
mLastFrameTimeNanos / TimeUtils.NANOS_PER_MS + sFrameDelay, now);
if (DEBUG_FRAMES) {
Log.d(TAG, "Scheduling next frame in " + (nextFrameTime - now) + " ms.");
}
Message msg = mHandler.obtainMessage(MSG_DO_FRAME);
msg.setAsynchronous(true);//异步
mHandler.sendMessageAtTime(msg, nextFrameTime);
}
}
}

三、invalidate/requestLayout流程

invalidate/postInvalidat/requestLayout简要区别:

  1. invalidate只会调onDraw方法且必须在UI线程中调用
  2. postInvalidate只会调onDraw方法,可在非UI线程中回调
  3. requestLayout会调onMeasure、onLayout和onDraw(特定条件下)方法

invalidate

调用 View.invalidate() 方法后会逐级往上调用父 View 的相关方法,最终在 Choreographer 的控制下调用 ViewRootImpl.performTraversals() 方法。只有满足 可见性、尺寸发生变化时 等条件才会执行 measure 和 layout 流程,否则只执行 draw 流程,draw 流程的执行过程与是否开启硬件加速有关:

关闭硬件加速则从 DecorView 开始往下的所有子 View 都会被重新绘制
开启硬件加速则只有调用 invalidate 方法的 View 才会重新绘制。

requestLayout

调用 View.requestLayout 方法后会依次调用 performMeasure, performLayout 和 performDraw 方法,调用者 View 及其父 View 会重新从上往下进行 measure, layout 流程,一般情况下不会执行 draw 流程(子 View 会通过判断其尺寸/顶点是否发生改变而决定是否重新 measure/layout/draw 流程)。

小结:因此,当只需要进行重绘时可以使用 invalidate 方法,如果需要重新测量和布局则可以使用 requestLayout 方法,而 requestLayout 方法不一定会重绘,因此如果要进行重绘可以再手动调用 invalidate 方法。

子线程更新UI

1、为啥会崩:

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
-> 视图更新操作invalidata
-> View.invalidate
-> View.invalidateInternal
-> ViewGroup.invalidateChild //正常情况View上一层级为ViewGroup,如果上一层直接时ViewRootImpl则没有这层
-> ViewParent.invalidateChildInParent //这里会不断do while去取上一个结点的mParent,从ViewGroup一直到ViewRootImpl为止
-> ViewRootImpl.invalidateChildInParent //DecorView的mParent是ViewRootImpl
-> ViewRootImpl.checkThread //在这里执行checkThread,如果非ViewRootImpl创建线程则抛出异常


ViewRootImpl.class


final Thread mThread;
...
public ViewRootImpl(Context context, Display display) {
mcontext = context;
mWindowSession = WindowMangerGlobal.getWindowSession();
...
mThread = Thread.currentThread();
...
}
...

@Override //override from ViewParent interface
public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
checkThread();
if (DEBUG_DRAW) Log.v(mTag, "Invalidate child: " + dirty);

if (dirty == null) {
invalidate();
return null;
} else if (dirty.isEmpty() && !mIsAnimating) {
return null;
}

if (mCurScrollY != 0 || mTranslator != null) {
mTempRect.set(dirty);
dirty = mTempRect;
if (mCurScrollY != 0) {
dirty.offset(0, -mCurScrollY);
}
if (mTranslator != null) {
mTranslator.translateRectInAppWindowToScreen(dirty);
}
if (mAttachInfo.mScalingRequired) {
dirty.inset(-1, -1);
}
}

invalidateRectOnScreen(dirty);

return null;
}


1
2
3
4
5
6
7
8
9
10
11
12
-> 视图更新操作requestLayout
-> mParent.reqeustLayout // 不断向父控件(mparent即ViewParent接口,ViewGroup与ViewRootImpl都实现了该接口,可做为view的parent)请求布局,最后调用到ViewRootImpl的requestLayout
-> ViewRootImpl.requestLayout

ViewRootImpl.class
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
1
2
3
4
5
6
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}

2、子线程更新UI怎么不崩:

  • 子线程在ViewRootImpl初始化之前(handleResumeActivity之后初始化)
  • ViewRootImpl创建线程 只需要与 View的更新线程 是同一个就行(初始化在子线程,更新在主线程一样会崩)

View.post与getHandler.post

view.post在内部类AttachInfo未实例化之前是会将action通过getRunQueue().post(action)缓存起来,延后到handleResumeActivity中初始化ViewRootImp时调用setView时,会从顶向下调用View/ViewGroup的dispatchAttachToWindow方法,在此方法内缓存的action会被执行

Activity的ViewRootImpl在onResume方法中创建的,具体见下

四、同步消息屏障

在invalidate/requestLayout执行时,最后都会走向ViewRootImpl的scheduleTraversals()方法中,这个方法中会调用mHandler.getLooper().getQueue().postSyncBarrier();,本质上是往主线程Looper中post一个target==null的消息,作为同步消息屏障,过滤掉所有非异步的消息,只执行异步消息。

同时Choreographer注册Vsync回调,下一个Vsync消息发出后回调回来移除同步消息屏障并执行 performTraversals(也就是measure、layout、draw)

五、ChoreoGrapher什么时候初始化

Activity启动后,执行完ActivitityThread.performResumeActivity(),再执行WindowManagerImpl.addView(),在其内部会执行ViewRootImpl的初始化。Choreographer的初始化就是在ViewRootImpl的构造方法中执行的。

ViewRootImpl的关键全局变量除了Choreographer(掌管绘制相关)外,还有attachInfo()

img

Author

white crow

Posted on

2021-04-01

Updated on

2024-04-29

Licensed under