用户角度:APP 操作比较缓慢,响应不及时,列表滑动卡顿,动画刷新不流畅等
系统角度:屏幕刷新帧率不稳定,无法保证每秒60(跟手机有关)帧刷新频率,出现掉帧现象
过度绘制
UI 线程进行过度计算任务
频繁 GC
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 的缓存机制
一块缓存区域,由 CPU 计算完成,交给 GPU 进行绘制,然后在输出到 Buffer 缓冲区中,Display 屏幕不断的从 Buffer 中获取内容进行显示。
如上图,使用单缓存, VSync 通知刷新,但是第二帧还未准备好,这时继续绘制第一帧,等第二帧准备完毕时,多显示了一次第一帧,后续流程相同。会一直出现等待显示的问题。
两块缓存区域,屏幕始终从 Frame Buffer 中取出数据进行显示,GPU 始终将渲染完的数据填充到 Back Buffer 中。这两个区域交换时直接更改物理地址名,即将 FrameBuffer 指向 BackBuffer地址,将 BackBuffer 指向 FrameBuffer地址,所以耗时基本可以忽略
如图所示,比较糟糕的一种情况,做了某种重度操作,导致 CPU 和 GPU 特别慢,还是会出现上面那种情况,重复显示同一帧的问题。
使用二级缓存基本可以满足了,除非遇到极端情况才会出现丢帧卡顿的现象,这个时候的兜底逻辑,三级缓存就排上用场了。
Back Buffer一共有两块,一块是备用区。当二级缓存不能胜任时,在启用这块备用 Buffer,当二级缓存可以胜任时,备用区继续闲置。
以上就是有关 VSync 垂直同步了,主要就是协调 GPU 和 Display 的频率,使各个模块达到最佳的工作状态。
用于同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 信号,回调编舞者,从而最终回调我们的注册监听。