在 Android 的绘制架构中,CPU 主要负责视图的测量、布局、记录、并且把内容计算成 Polygons(多边形) 或者 Texture(纹理) ,GPU主要复制把多边形和纹理进行 Rasterization(栅格化) 。如此才能在屏幕上成像。有时使用硬件加速后,GPU 会分担 CPU 的计算任务,而 CPU 会专注处理逻辑,这样减轻 CPU 的负担,使得整个系统效率更高。
刷新率是屏幕每秒刷新的次数。在Android平台上,这个值一般为 60HZ,即屏幕每秒刷新 60 次。
帧率是每秒绘制的帧数;通常只要帧数和刷新率保持一致,画面便流畅。所以我们应该尽量维持 60FPS 的帧率。但有时候由于视图的复杂,它们可能就会出现不一致的情况。
当帧率小于刷新率时,会出现相邻的两帧出现同一画面,便会卡顿。所以一帧画面应该尽量在 16ms 内完成(一秒为一千毫秒,因为屏幕一秒刷新六十次,所以一千除以六十约为十六毫秒),才不会出现卡顿。
那么怎样才能使刷新率和帧率保持一致?用 Vsync 可以实现。
在游戏设置中有时会出现 Vsync,开启可以使游戏更流畅。在 Android 中使用它可以使刷新率和帧率强制保持一致。
Vsync 有两个接收者:SurfaceFlinger(负责合成各个Surface)和 Choreographer (负责控制视图的绘制)。
它是一个c++编写的类,在 Android 系统初始化被创建,配合硬件产生 Vsync 信号。HWComposer 被设计为能够唤醒和睡眠,
这样可以使在需要时才会产生 Vsync 信号,不需要时进入睡眠状态(如:屏幕不动进入睡眠状态,屏幕每次刷新都是显示缓冲区里没发生变化的内容)。
它实际代表了一块内存,用于储存绘制出来的数据,即 View 视图的内容都是被绘制到这个 Canvas 中。
了解完上面的内容后,就明白了,画面卡顿的原因:刷新率与帧率不一致。下面说明 Choreographer 。
Choreographer 翻译来为舞蹈指挥,名字真骚气。。。下面看看它是做什么的。
Choreographer 是一个线程中仅存在一个实例,因此在UI线程只有一个 Choreographer 存在(一个应用中的单例)。其作用为:负责控制视图的绘制。
void scheduleTraversals() {
if (!mTraversalScheduled) {
......//省略
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
}
}
上面的代码可以看到 Choreographer 调用了 postCallback() (请求 一个 Vsync 信号)方法 。先说一下 scheduleTraversals() 这个方法是怎么来的,它是 ViewRootImpl 里面的方法中出现的(点这里看scheduleTraversals()方法以及ViewTootImpl)。继续往下看 postCallback() 方法:
public void postCallback(int callbackType, Runnable action, Object token) {
postCallbackDelayed(callbackType, action, token, 0);
}
上面代码可以看到:在里面又调用了 postCallbackDelayed() 方法,继续看这个方法:
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);
}
上面代码可以看到:又调用了 postCallbackDelayedInternal() 方法。。。继续看这个方法:
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);
}
}
}
上面代码可以看到:它把任务都放在 mCallbackQueues[callbackType] 队列中,接着做了一个判断:如果 dueTime <= now (意思是时间到了,便去请求一个 Vsync 信号,上面说了 16ms 会绘制一帧)调用 scheduleFrameLocked() 方法,否则时间还没到向 FrameHandler 中发送一个 MSG_DO_SCHEDULE_CALLBACK 消息(记住 FrameHandler 下面会了解下这个)。继续看 scheduleFrameLocked() 这个方法:
private void scheduleFrameLocked(long now) {
if (USE_VSYNC) {
if (isRunningOnLooperThreadLocked()) {
scheduleVsyncLocked();
} else {
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC);
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);
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, nextFrameTime);
}
}
上面代码可以看到:首先判断了 USE_VSYNC 是否为 true ,USE_VSYNC 为 true 的意思是开启垂直同步;再判断当前是否在 UI 线程,是的话请求一个 Vsync 信号,否则向 FrameHandler 中发送一个 MSG_DO_SCHEDULE_VSYNC 消息(这个消息意思就是请求一个 Vsync 信号, FrameHandler 下面会说,记住它)。可以看出,不管是不是在 UI 线程,最终都将会请求一个 Vsync 信号;往下看 scheduleVsyncLocked() 方法中有什么:
private void scheduleVsyncLocked() {
mDisplayEventReceiver.scheduleVsync();
}
上面代码可以看到:有一个新的变量 mDisplayEventReceiver ,它调用了方法 scheduleVsync();从字面 DisplayEventReceiver 意思是显示事件接收器,那它应该是一个接收器。。。实际上它是一个 FrameDisplayEventReceiver 类型的对象,下面来看一下 FrameDisplayEventReceiver :
private final class FrameDisplayEventReceiver extends DisplayEventReceiver
implements Runnable {
public void onVsync(){
...
Message msg = Message.obtain(mHandler, this);
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
}
}
上面代码可以看到: FrameDisplayEventReceiver 里面有一个 onVsync() 方法,这个方法用于接收 Vsync 信号,(向 FrameHandler 中发送一个消息,FrameHandler 下面会说,记住它);下面的 message 其实就是通知 Choreographer 回调 mCallbackQueues[callbackType] 中的 callback;至此,开始绘制第一帧了。
从总体上看,从一开始调用了 postCallback() 方法请求一个 Vsync 信号,到最后调用了 onVsync() 方法接收到 Vsync 信号并且通知 Choreographer 回调;这个过程就是在不断的请求 Vsync 信号,接收 Vsync 信号。
是时候看看 FrameHandler (上面有提到)是个什么了:
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:
doScheduleVsync();
break;
case MSG_DO_SCHEDULE_CALLBACK:
doScheduleCallback(msg.arg1);
break;
}
}
}
FrameHandler主要在UI线程处理3种类型的消息:
MSG_DO_FRAME:开始渲染下一帧的操作
MSG_DO_SCHEDULE_VSYNC:请求Vsync信号
MSG_DO_SCHEDULE_CALLBACK:请求执行callback
FrameHandler 虽然不复杂,但在UI的绘制过程中具有重要的作用,所以也要了解。
知识:从上面了解到硬件每 16ms 绘制一帧,即每 16ms 产生一个 Vsync 信号;开始绘制之后,由 Choreographer 不断的去请求一个 Vsync 信号,接收一个 Vsync 信号并且回调 mCallbackQueues[callbackType] 中的 callback,这样如此的反复,布局一帧一帧的就被绘制出来了。
SurfaceFlinger 是系统的一个服务,专门负责每个 Surface 中内容的合成缓存,以待显示在屏幕上。
Surface 的作用就是管理用于绘制视图树的 Canvas(上面有介绍它) 的,而我们 Window 中的视图树都是被绘制到一个由 Surface 提供的 Canvas 上。(不多说,重点在 Choreographer ,这个知道就好)。
更多内容戳我&