文章目录
相关概念
刷新率:每秒内屏幕刷新的次数,单位是 hz 例如 60hz 现在一些新设备支持更高的刷新率并且有些设备支持多种刷新率
帧速率:GPU 在一秒内操作画面的帧数,单位是 fps 例如 60fps
tearing(屏幕撕裂):屏幕内的数据来自 2 个不同的帧,画面会出现撕裂感

屏幕撕裂的原因:画面显示过程简单的说就是 CPU/GPU 准备好数据存入 buffer 屏幕 从 buffer 中取出数据然后一行一行显示出来,屏幕处理的频率是固定的比如每 60ms 显示完一帧,但是CPU/GPU 写数据是不可控的,所以会出现有些数据根本没显示出来就被重写了 buffer 里的数据可能是来自不同的帧出现画面撕裂。解决这个问题的办法就是使用双缓存 GPU 往 back buffer 中写,屏幕从 frame buffer 中取数据展示,在合适的时机交换两个 buffer 的数据。当扫描完一个屏幕后,设备需要重新回到第一行以进入下一次的循环,此时有一段时间空隙,称为VerticalBlanking Interval(VBI) 因为此时屏幕没有在刷新,也就避免了交换过程中出现 screen tearing的状况。VSync(垂直同步)是 VerticalSynchronization 的简写,它利用 VBI 时期出现的vertical sync pulse来保证双缓冲在最佳时间点才进行交换

在 Android 4.1 之前是上图这样工作的,屏幕显示第 0 帧,CPU/GPU 绘制第一帧在下一个周期内正常展示第一帧,但在展示第一帧的时候 CPU/GPU 没有及时去绘制第二帧导致在第三个周期内只能继续显示第一帧造成 Jank(卡顿)原因是第二帧开始绘制的时机太晚了在需要显示的时候还没有绘制完成,解决这个问题是方式就是利用 VSync 信号

既然造成 Jank 的原因是绘制时机太晚那么就控制 CPU/GPU 在接收到 VSync 信号后立即绘制充分利用 16ms 的时间避免 Jank 这样在帧率和屏幕刷新率一致的情况下就可以很好的显示
但如果帧率低于刷新率的情况下还是会出现 Jank (如上图)原因是第一帧的时候 GPU 没有完成绘制并且两个 buffer 都被占用, CPU 只能等待 buffer ,在第三个周期内 CPU 才拿到 buffer 造成 A 帧重复显示,并且由于 GPU 的处理时长又超出了一个周期所以 B 帧又重复了展示一次。为了解决这个问题就有了 Triple buffer 
可以看到在增加了一个 buffer 后再次出现上述情况 CPU 就可以使用第三个 buffer 去绘制,这样虽然第一帧还是会重复显示但之后就可以正常展示了,相对于帧率与刷新率相同的情况下只是后续的每一帧都延时了一个周期,接下来从代码层面看一下 VSync 信号
Choreographer
Choreographer 的作用是给它发送一个 runnable 这个 runnable 最快在下一个 Vsync 信号来的时候触发。View 所有触发刷新相关的操作都会一层层的往上遍历找到自己的 mParent 直到 ViewRootImpl(ViewRootImpl 实现了 ViewParent 接口)最后会调用 scheduleTraversals 方法,所以从这里开始
void scheduleTraversals() {
if (!mTraversalScheduled) {
// 在一个 Vsync 周期里只会有一次重绘请求有效
mTraversalScheduled = true;
// 插入同步屏障消息,同步消息屏障的作用是为了优先处理异步消息
// 消息队列在发现同步屏障消息后会忽略队列里的同步消息只处理异步消息
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
// 调用 postCallback 传入 mTraversalRunnable
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
mChoreographer 是 Choreographer 实例跟 Looper 一样是线程内的单例
// 这里的 action 就是上面的 mTraversalRunnable
public void postCallback(int callbackType, Runnable action, Object token) {
// 最后一个参数传的是 0
postCallbackDelayed(callbackType, action, token, 0);
}
public void postCallbackDelayed(int callbackType,
Runnable action, Object token, long delayMillis) {
if (action == null) {
throw new IllegalArgumentException("action must not be null");
}
if (callbackType < 0 || callbackType > CALLBACK_LAST) {
throw new IllegalArgumentException("callbackType is invalid");
}
postCallbackDelayedInternal(callbackType, action, token, delayMillis);
}
private void postCallbackDelayedInternal(int callbackType,
Object action, Object token, long delayMillis) {
synchronized (mLock) {
final long now = SystemClock.uptimeMillis()

最低0.47元/天 解锁文章
1376

被折叠的 条评论
为什么被折叠?



