![f2da66691fe9eb8227f3f3ba013e8cad.png](https://i-blog.csdnimg.cn/blog_migrate/6a44a2afb0d84ff0e7e2eeea4ebfda8a.jpeg)
作者:唐子玄
Andriod 界面卡顿是因为掉帧,而掉帧是因为生产帧的速度跟不上消费帧的速度。
消费帧的速度与屏幕刷新率挂钩,屏幕就像连环画,若一秒播放 60 帧,消费一帧的速度为 1000/60 = 16.6 ms,即每 16.6 ms 屏幕就会去取下一帧的显示内容,若没取到,只能继续显示上一帧,画面就停滞了,这就称为“掉帧”,听上去好像丢失了无法找回的东西一样,其实它是形容“显示内容错过了一次显示机会”,描述的是屏幕硬件的一种行为。
屏幕为啥会没有取到显示内容?得从软件层找原因。带着这个问题,读一下 Framework 源码。
第一次看我文章的小伙伴可以关注一下我,顺便关注一下我的专栏:Android高级技术专栏 每天更新各种技术干货,分享更多最热程序员圈内事。Android高级技术专栏zhuanlan.zhihu.com
![2705da2a4937d9039ff866ebd100449d.png](https://i-blog.csdnimg.cn/blog_migrate/1c27941f9675da697ed64800203485d3.jpeg)
Choreographer
ViewRootImpl是一个 Activity 中所有视图的根视图,View 树的遍历就由它发起:
public final class ViewRootImpl implements ViewParent, View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks {
// 编舞者
Choreographer mChoreographer;
// 触发遍历 View 树
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
// 向 Choreographer 抛 View树遍历任务
mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
// 遍历 View 树任务
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
// 构建遍历 View 树任务实例
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
}
ViewRootImpl通过抛任务到Choreographer来触发 View 树遍历。
Choreographer是 android.view包下的一个类,字面意思是“编舞者”,隐含着“需要将两者同步”的意思,编舞即是将动作和节奏同步。而Choreographer是将"绘制内容"和"垂直同步信号"同步。
存放绘制任务
public final class Choreographer {
// 输入任务
public static final int CALLBACK_INPUT = 0;
// 动画任务
public static final int CALLBACK_ANIMATION = 1;
// view树遍历任务
public static final int CALLBACK_TRAVERSAL = 2;
// COMMIT任务
public static final int CALLBACK_COMMIT = 3;
// 暂存任务的链式数组
private final CallbackQueue[] mCallbackQueues;
// 主线程消息处理器
private final FrameHandler mHandler;
// 抛绘制任务
public void postCallback(int callbackType, Runnable action, Object token) {
postCallbackDelayed(callbackType, action, token, 0);
}
// 延迟抛绘制任务
public void postCallbackDelayed(int callbackType,
Runnable action, Object token, long delayMillis) {
...
postCallbackDelayedInternal(callbackType, action, token, delayMillis);
}
// 抛绘制任务的具体实现
private void postCallbackDelayedInternal(int callbackType, Object action, Object token, long delayMillis) {
synchronized (mLock) {
final long now = SystemClock.uptimeMillis();
final long dueTime = now + delayMillis;
// 1. 将绘制任务根据类型暂存在链式结构中
mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
// 2. 订阅下一个垂直同步信号
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 final class FrameHandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
...
case MSG_DO_SCHEDULE_CALLBACK:
// 在未来时间点订阅垂直同步信号
doScheduleCallback(msg.arg1);
break;
}
}
}
void doScheduleCallback(int callbackType) {
synchronized (mLock) {
if (!mFrameScheduled) {