Vsync机制和Choreographer详解
Posted xyTianZhao
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Vsync机制和Choreographer详解相关的知识,希望对你有一定的参考价值。
文章目录
UI 卡顿定义
-
用户角度:APP 操作比较缓慢,响应不及时,列表滑动卡顿,动画刷新不流畅等
-
系统角度:屏幕刷新帧率不稳定,无法保证每秒60(跟手机有关)帧刷新频率,出现掉帧现象
卡顿原因及常见解决方式
-
过度绘制
- 去除不必要背景
- 布局视图尽量扁平化
- 减少透明色的使用
-
UI 线程进行过度计算任务
- 减少在 UI 线程中进行重度计算任务
-
频繁 GC
- 频繁 GC 的原因
- 内存抖动
- 瞬间产生大量的对象
- 尽量减少在循环中 new 对象或者使用局部变量
- 避免在 draw 方法中创建对象
- 频繁 GC 的原因
VSync
定义
-
android4.1(Jelly Bean)引入了Vsync(垂直同步信号量)
-
屏幕刷新率
Refresh Rate 或者 SurfaceFlinger,设备刷新屏幕的频率 -
帧率
Frame Rate,单位:FPS,指 GPU 生成帧的频率 -
VSync
屏幕产生硬件的 VSync,由 SurfaceFlinger 将其转换成软件的 VSync 信号。
作用
-
用来同步渲染,让 AppUI 和 SurfaceFlinger 可以按硬件产生的 VSync 节奏进行工作
-
主要为了解决 “Tearing”(撕裂) 现象
-
同步 UI 绘制和动画,使他们尽可能的获得一个达到 60fps 的固定帧率
工作原理
刷新率和帧速率需要协同工作,才能让你的应用程序的内容显示到屏幕上,GPU会获取图像数据进行绘制,然后负责把内容呈现到屏幕上,这将在你的应用程序的生命周期中周而复始地执行.
但是刷新率和帧速率并不是总能够保持相同的节奏,这样就会出现丢帧或者撕裂的现象,Google 为此引入了 Buffer 的缓存机制
Buffer 缓存机制
单缓存
一块缓存区域,由 CPU 计算完成,交给 GPU 进行绘制,然后在输出到 Buffer 缓冲区中,Display 屏幕不断的从 Buffer 中获取内容进行显示。
如上图,使用单缓存, VSync 通知刷新,但是第二帧还未准备好,这时继续绘制第一帧,等第二帧准备完毕时,多显示了一次第一帧,后续流程相同。会一直出现等待显示的问题。
双缓存
两块缓存区域,屏幕始终从 Frame Buffer 中取出数据进行显示,GPU 始终将渲染完的数据填充到 Back Buffer 中。这两个区域交换时直接更改物理地址名,即将 FrameBuffer 指向 BackBuffer地址,将 BackBuffer 指向 FrameBuffer地址,所以耗时基本可以忽略
如图所示,比较糟糕的一种情况,做了某种重度操作,导致 CPU 和 GPU 特别慢,还是会出现上面那种情况,重复显示同一帧的问题。
三缓存
使用二级缓存基本可以满足了,除非遇到极端情况才会出现丢帧卡顿的现象,这个时候的兜底逻辑,三级缓存就排上用场了。
Back Buffer一共有两块,一块是备用区。当二级缓存不能胜任时,在启用这块备用 Buffer,当二级缓存可以胜任时,备用区继续闲置。
以上就是有关 VSync 垂直同步了,主要就是协调 GPU 和 Display 的频率,使各个模块达到最佳的工作状态。
Choreographer
用于同Vsync机制配合,实现统一调度界面绘图。
编舞者,一个很有意思的名字。
使用
如果我们需要让自定义的 View 以 16ms 的频率进行刷新,就可以这么干。
class ChoreographerView extends View implements Choreographer.FrameCallback
public ChoreographerView(Context context)
super(context);
Choreographer.getInstance().postFrameCallback(this);
@Override
public void doFrame(long frameTimeNanos)
invalidate();
Choreographer.getInstance().postFrameCallback(this);
@Override
protected void onDraw(Canvas canvas)
super.onDraw(canvas);
//do somthing
源码分析
上面的使用方法,为什么可以尽可能的保证以 16ms 的频率进行回调呢,我们就来看看他的实现。
首先看一下他的构造方法,通过 ThreadLocal 保证在该线程始终只有一个该对象,使用该线程作为 key,该对象作为 value 保存在一个 map 中。
public final class Choreographer
public static Choreographer getInstance()
return sThreadInstance.get();
private static volatile Choreographer mMainInstance;
// Thread local storage for the choreographer.
private static final ThreadLocal<Choreographer> sThreadInstance =
new ThreadLocal<Choreographer>()
@Override
protected Choreographer initialValue()
Looper looper = Looper.myLooper();
if (looper == null)
throw new IllegalStateException("The current thread must have a looper!");
Choreographer choreographer = new Choreographer(looper, VSYNC_SOURCE_APP);
if (looper == Looper.getMainLooper())
mMainInstance = choreographer;
return choreographer;
;
private Choreographer(Looper looper, int vsyncSource)
mLooper = looper;
mHandler = new FrameHandler(looper);
mDisplayEventReceiver = USE_VSYNC
? new FrameDisplayEventReceiver(looper, vsyncSource)
: null;
mLastFrameTimeNanos = Long.MIN_VALUE;
mFrameIntervalNanos = (long)(1000000000 / getRefreshRate());
mCallbackQueues = new CallbackQueue[CALLBACK_LAST + 1];
for (int i = 0; i <= CALLBACK_LAST; i++)
mCallbackQueues[i] = new CallbackQueue();
在看看构造函数里面,初始化了一些相关变量,比如使用当前线程的 Looper 创建一个处理回调的 FrameHandler。还有接受屏幕 VSync 回调的 FrameDisplayEventReceiver。
同样,先来看一下我们调用 postFrameCallback 之后发生了什么。
看到最终只是将当前的 callback 添加到回调队列中。当处理时间小于当前时间是,就执行 FrameDisplayEventReceiver 的 scheduleVsync 方法等待垂直同步信号回调,然后在执行相应的回调。
public final class Choreographer
public void postFrameCallback(FrameCallback callback)
postFrameCallbackDelayed(callback, 0);
public void postFrameCallbackDelayed(FrameCallback callback, long delayMillis)
......
postCallbackDelayedInternal(CALLBACK_ANIMATION,
callback, FRAME_CALLBACK_TOKEN, delayMillis);
private void postCallbackDelayedInternal(int callbackType,
Object action, Object token, long 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);
private void scheduleFrameLocked(long now)
......
if (USE_VSYNC) //使用垂直同步
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC);
msg.setAsynchronous(true);
mHandler.sendMessageAtFrontOfQueue(msg);
private final class FrameHandler extends Handler
@Override
public void handleMessage(Message msg)
switch (msg.what)
......
case MSG_DO_SCHEDULE_VSYNC:
doScheduleVsync();
break;
......
void doScheduleVsync()
......
scheduleVsyncLocked();
......
private void scheduleVsyncLocked()
mDisplayEventReceiver.scheduleVsync();
我们再来看垂直同步信号的请求与回调。当调用 nativeScheduleVsync 方法时,就开始等待垂直同步信号了,当信号回来时,会回调 onVsync 方法执行后续流程。这里的回调一次的时间间隔,就相当于屏幕的刷新时间间隔,所以能尽可能的达到 16ms 的刷新频率。
public final class Choreographer
private final class FrameDisplayEventReceiver extends DisplayEventReceiver
implements Runnable
// 这个是 DisplayEventReceiver 中的方法,放在这里便于查看调用流程
// 等待下一个垂直同步的脉冲信号
public void scheduleVsync()
......
nativeScheduleVsync(mReceiverPtr);
......
public void onVsync(long timestampNanos, long physicalDisplayId, int frame)
Message msg = Message.obtain(mHandler, this);
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
@Override
public void run()
mHavePendingVsync = false;
doFrame(mTimestampNanos, mFrame);
最后再来看下垂直同步信号回来后的回调操作。
public final class Choreographer
void doFrame(long frameTimeNanos, int frame)
......
AnimationUtils.lockAnimationClock(frameTimeNanos / TimeUtils.NANOS_PER_MS);
......
doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);
......
doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);
......
doCallbacks(Choreographer.CALLBACK_INSETS_ANIMATION, frameTimeNanos);
......
doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
......
doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
......
void doCallbacks(int callbackType, long frameTimeNanos)
CallbackRecord callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked(
now / TimeUtils.NANOS_PER_MS);
......
for (CallbackRecord c = callbacks; c != null; c = c.next)
......
c.run(frameTimeNanos);
看到这里是不就明白了,说白了就是注册一个监听,什么时候回调呢?就是屏幕触发物理硬件的 VSync 信号,在通过 SurfaceFlinger 转换成软件的 VSync 信号,回调编舞者,从而最终回调我们的注册监听。
以上是关于Vsync机制和Choreographer详解的主要内容,如果未能解决你的问题,请参考以下文章
Android-Choreographer 垂直同步 Vsync