关于屏幕卡顿,从表象来讲,是因为主线程有耗时操作,导致屏幕绘制掉帧,屏幕每16毫秒会刷新一次,也就是每秒刷新60次,人眼能感觉到的卡顿的帧率是每秒24帧。所以解决卡顿的一般处理方法就是将耗时操作放在子线程、减少View层级、多使用include merge viewstub标签等,来保障屏幕绘制的流畅。
不过再往深里看,会有这样一些问题:卡顿的底层原因是什么?如何理解16毫米刷新一次?假如界面没有更新操作,View会每16毫秒刷新一次吗?
带着这些问题,来看下面的文字。
1.View
View里的requestLayout方法,常用来主动请求UI的更新,我们从这个方法看起
public void requestLayout() {
…
if (mParent != null && !mParent.isLayoutRequested()) {
mParent.requestLayout();
}
…
}
View的requestLayout方法实际上调用的是mParent的requestLayout方法,而mParent是ViewParent的实例,再来看ViewParent接口
/**
* Defines the responsibilities for a class that will be a parent of a View.
* This is the API that a view sees when it wants to interact with its parent.
*
*/
public interface ViewParent {
/**
* Called when something has changed which has invalidated the layout of a
* child of this view parent. This will schedule a layout pass of the view
* tree.
*/
public void requestLayout();
}
这里的mParent.requestLayout最终会调用到ViewRootImpl的requestLayout方法,因为根View是DecorView,而DecorView的parent就是ViewRootImpl。
2.ViewRootImpl
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
ViewRootImpl里的requestLayout方法只有4行代码,首先调用了 checkThread()方法,用来做线程的检查,点进去就看到了大名鼎鼎的异常信息
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
在看scheduleTraversals()方法
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
mTraversalScheduled标志位,这个标志位为false时才可以刷新UI,设置这个标志位的原因是防止短时间多次调用requestLayout重复绘制。
mHandler.getLooper().getQueue().postSyncBarrier()这里发送了一条同步屏障消息,这时候消息队列中的同步消息不会被处理,而是优先处理异步消息。
mChoreographer.postCallback向Choreographer里提交了一个mTraversalRunnable任务,这个任务不会马上执行。
3.Choreographer
postCallback实际调用的方法是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);
}
}
}
addCallbackLocked 将任务添加到队列,不会马上执行。
scheduleFrameLocked 通常delayMillis是0 执行scheduleFrameLocked方法
private void scheduleFrameLocked(long now) {
if (!mFrame