关于屏幕卡顿,从表象来讲,是因为主线程有耗时操作,导致屏幕绘制掉帧,屏幕每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 (!mFrameScheduled) {
mFrameScheduled = true;
if (USE_VSYNC) {
if (DEBUG_FRAMES) {
Log.d(TAG, "Scheduling next frame on vsync.");
}
// If running on the Looper thread, then schedule the vsync immediately,
// otherwise post a message to schedule the vsync from the UI thread
// as soon as possible.
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);
if (DEBUG_FRAMES) {
Log.d(TAG, "Scheduling next frame in " + (nextFrameTime - now) + " ms.");
}
Message msg = mHandler.obtainMessage(MSG_DO_FRAME);
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, nextFrameTime);
}
}
}
scheduleFrameLocked方法判断当前线程是否为UI线程,如果是UI线程则执行scheduleVsyncLocked方法,如果非UI线程就通过Handler处理,再来看scheduleVsyncLocked方法
private void scheduleVsyncLocked() {
mDisplayEventReceiver.scheduleVsync();
}
在来看DisplayEventReceiver
/**
* Schedules a single vertical sync pulse to be delivered when the next
* display frame begins.
*/
public void scheduleVsync() {
if (mReceiverPtr == 0) {
Log.w(TAG, "Attempted to schedule a vertical sync pulse but the display event "
+ "receiver has already been disposed.");
} else {
nativeScheduleVsync(mReceiverPtr);
}
}
// Called from native code.
@SuppressWarnings("unused")
private void dispatchVsync(long timestampNanos, int builtInDisplayId, int frame) {
onVsync(timestampNanos, builtInDisplayId, frame);
}
scheduleVsync方法通过底层代码请求vsync,vsync来的时候底层jni代码会调用dispatchVsync
方法,然后回调onVsync这个空方法,由使用的类去实现
public void onVsync(long timestampNanos, int builtInDisplayId, int frame) {
}
这里是屏幕刷新机制的重点,应用必须向底层请求vsync信号,然后下一次vsync信号来的时候会通过jni通知到应用,然后才是应用的绘制逻辑。
Choreographer的内部类FrameDisplayEventReceiver继承了DisplayEventReceiver,并重写了onVsync方法
@Override
public void onVsync(long timestampNanos, int builtInDisplayId, int frame) {
// Ignore vsync from secondary display.
// This can be problematic because the call to scheduleVsync() is a one-shot.
// We need to ensure that we will still receive the vsync from the primary
// display which is the one we really care about. Ideally we should schedule
// vsync for a particular display.
// At this time Surface Flinger won't send us vsyncs for secondary displays
// but that could change in the future so let's log a message to help us remember
// that we need to fix this.
if (builtInDisplayId != SurfaceControl.BUILT_IN_DISPLAY_ID_MAIN) {
Log.d(TAG, "Received vsync from secondary display, but we don't support "
+ "this case yet. Choreographer needs a way to explicitly request "
+ "vsync for a specific display to ensure it doesn't lose track "
+ "of its scheduled vsync.");
scheduleVsync();
return;
}
// Post the vsync event to the Handler.
// The idea is to prevent incoming vsync events from completely starving
// the message queue. If there are no messages in the queue with timestamps
// earlier than the frame time, then the vsync event will be processed immediately.
// Otherwise, messages that predate the vsync event will be handled first.
long now = System.nanoTime();
if (timestampNanos > now) {
Log.w(TAG, "Frame time is " + ((timestampNanos - now) * 0.000001f)
+ " ms in the future! Check that graphics HAL is generating vsync "
+ "timestamps using the correct timebase.");
timestampNanos = now;
}
if (mHavePendingVsync) {
Log.w(TAG, "Already have a pending vsync event. There should only be "
+ "one at a time.");
} else {
mHavePendingVsync = true;
}
mTimestampNanos = timestampNanos;
mFrame = 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);
}
}
当收到vsync信号后,onVsync方法就会被调用,通过Handler往消息队列里插入了一个异步消息并指定执行时间,并在callback里传入了this(Message.obtain(mHandler, this),所以最后会回调run方法,run方法里调用了doFrame方法,如果Handler此时存在耗时操作,那么需要等耗时操作执行完成,Looper才会轮询到下一条消息,run方法才会调用,在来看doFrame方法
void doFrame(long frameTimeNanos, int frame) {
final long startNanos;
synchronized (mLock) {
if (!mFrameScheduled) {
return; // no work to do
}
if (DEBUG_JANK && mDebugPrintNextFrameTimeDelta) {
mDebugPrintNextFrameTimeDelta = false;
Log.d(TAG, "Frame time delta: "
+ ((frameTimeNanos - mLastFrameTimeNanos) * 0.000001f) + " ms");
}
long intendedFrameTimeNanos = frameTimeNanos;
startNanos = System.nanoTime();
final long jitterNanos = startNanos - frameTimeNanos;
if (jitterNanos >= mFrameIntervalNanos) {
final long skippedFrames = jitterNanos / mFrameIntervalNanos;
if (skippedFrames >= SKIPPED_FRAME_WARNING_LIMIT) {
Log.i(TAG, "Skipped " + skippedFrames + " frames! "
+ "The application may be doing too much work on its main thread.");
}
final long lastFrameOffset = jitterNanos % mFrameIntervalNanos;
if (DEBUG_JANK) {
Log.d(TAG, "Missed vsync by " + (jitterNanos * 0.000001f) + " ms "
+ "which is more than the frame interval of "
+ (mFrameIntervalNanos * 0.000001f) + " ms! "
+ "Skipping " + skippedFrames + " frames and setting frame "
+ "time to " + (lastFrameOffset * 0.000001f) + " ms in the past.");
}
frameTimeNanos = startNanos - lastFrameOffset;
}
if (frameTimeNanos < mLastFrameTimeNanos) {
if (DEBUG_JANK) {
Log.d(TAG, "Frame time appears to be going backwards. May be due to a "
+ "previously skipped frame. Waiting for next vsync.");
}
scheduleVsyncLocked();
return;
}
if (mFPSDivisor > 1) {
long timeSinceVsync = frameTimeNanos - mLastFrameTimeNanos;
if (timeSinceVsync < (mFrameIntervalNanos * mFPSDivisor) && timeSinceVsync > 0) {
scheduleVsyncLocked();
return;
}
}
mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos);
mFrameScheduled = false;
mLastFrameTimeNanos = frameTimeNanos;
}
try {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Choreographer#doFrame");
AnimationUtils.lockAnimationClock(frameTimeNanos / TimeUtils.NANOS_PER_MS);
mFrameInfo.markInputHandlingStart();
doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);
mFrameInfo.markAnimationsStart();
doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);
mFrameInfo.markPerformTraversalsStart();
doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
} finally {
AnimationUtils.unlockAnimationClock();
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
if (DEBUG_FRAMES) {
final long endNanos = System.nanoTime();
Log.d(TAG, "Frame " + frame + ": Finished, took "
+ (endNanos - startNanos) * 0.000001f + " ms, latency "
+ (startNanos - frameTimeNanos) * 0.000001f + " ms.");
}
}
doFrame方法较长,一点一点来看
final long jitterNanos = startNanos - frameTimeNanos;当前时间减去vsync来的时间,也就是主线程的耗时时间
final long skippedFrames = jitterNanos / mFrameIntervalNanos;1帧是16毫秒,计算当前跳过了多少帧,比如超时163毫秒,那么就是跳过了10帧
if (skippedFrames >= SKIPPED_FRAME_WARNING_LIMIT) { Log.i(TAG, "Skipped " + skippedFrames + " frames! " + "The application may be doing too much work on its main thread."); }
这里又看到了一个大名鼎鼎的log信息,SKIPPED_FRAME_WARNING_LIMIT默认是30,如果超过了30,就log提示
if (frameTimeNanos < mLastFrameTimeNanos) 如果时间倒退了,就等下一个vsync信号,一般不会走到这里
正常绘制会走到洗面的doCallbacks里,在看doCallbacks里的逻辑
void doCallbacks(int callbackType, long frameTimeNanos) {
CallbackRecord callbacks;
synchronized (mLock) {
// We use "now" to determine when callbacks become due because it's possible
// for earlier processing phases in a frame to post callbacks that should run
// in a following phase, such as an input event that causes an animation to start.
final long now = System.nanoTime();
callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked(
now / TimeUtils.NANOS_PER_MS);
if (callbacks == null) {
return;
}
mCallbacksRunning = true;
// Update the frame time if necessary when committing the frame.
// We only update the frame time if we are more than 2 frames late reaching
// the commit phase. This ensures that the frame time which is observed by the
// callbacks will always increase from one frame to the next and never repeat.
// We never want the next frame's starting frame time to end up being less than
// or equal to the previous frame's commit frame time. Keep in mind that the
// next frame has most likely already been scheduled by now so we play it
// safe by ensuring the commit time is always at least one frame behind.
if (callbackType == Choreographer.CALLBACK_COMMIT) {
final long jitterNanos = now - frameTimeNanos;
Trace.traceCounter(Trace.TRACE_TAG_VIEW, "jitterNanos", (int) jitterNanos);
if (jitterNanos >= 2 * mFrameIntervalNanos) {
final long lastFrameOffset = jitterNanos % mFrameIntervalNanos
+ mFrameIntervalNanos;
if (DEBUG_JANK) {
Log.d(TAG, "Commit callback delayed by " + (jitterNanos * 0.000001f)
+ " ms which is more than twice the frame interval of "
+ (mFrameIntervalNanos * 0.000001f) + " ms! "
+ "Setting frame time to " + (lastFrameOffset * 0.000001f)
+ " ms in the past.");
mDebugPrintNextFrameTimeDelta = true;
}
frameTimeNanos = now - lastFrameOffset;
mLastFrameTimeNanos = frameTimeNanos;
}
}
}
try {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, CALLBACK_TRACE_TITLES[callbackType]);
for (CallbackRecord c = callbacks; c != null; c = c.next) {
if (DEBUG_FRAMES) {
Log.d(TAG, "RunCallback: type=" + callbackType
+ ", action=" + c.action + ", token=" + c.token
+ ", latencyMillis=" + (SystemClock.uptimeMillis() - c.dueTime));
}
c.run(frameTimeNanos);
}
} finally {
synchronized (mLock) {
mCallbacksRunning = false;
do {
final CallbackRecord next = callbacks.next;
recycleCallbackLocked(callbacks);
callbacks = next;
} while (callbacks != null);
}
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked先取出队列里的任务(在postCallbackDelayedInternal方法里添加进来的)
if (callbackType == Choreographer.CALLBACK_COMMIT)是流程的最后一步,数据已经绘制完准备提交的时候,会更正一下时间戳,确保提交时间总是在最后一次vsync时间之后
c.run(frameTimeNanos) 还没到最后一步的时候,取出其它任务出来run,这个任务肯定就是跟View的绘制相关了,记得开始requestLayout传过来的类型吗,Choreographer.CALLBACK_TRAVERSAL,从队列get出来的任务类对应是mTraversalRunnable,类型是TraversalRunnable,定义在ViewRootImpl里面,饶了一圈,回到ViewRootImpl继续看
void scheduleTraversals() {
if (!mTraversalScheduled) {
...
mChoreographer.postCallback( Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); } }
这个mTraversalRunnable 任务绕了一圈,通过请求vsync信号,到收到信号,然后终于被调用了。
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
if (mProfile) {
Debug.startMethodTracing("ViewAncestor");
}
performTraversals();
if (mProfile) {
Debug.stopMethodTracing();
mProfile = false;
}
}
}
先移除同步屏障消息,然后调用performTraversals 方法,这个方法大家都比较熟悉了,View的三个方法回调measure、layout、draw都在里面,需要注意的点是LinearLayout设置权重的情况下会measure两次。
小结:
View的requestLayout方法会调用到ViewRootImpl的requestLayout方法,然后通过scheduleTraversals方法向Chaoreographer提交一个绘制任务,然后在通过DisplayEventReceiver方法向Choreographer提交一个绘制任务,然后再通过DisplayEventReceiver向底层请求vsync信号,当vsync信号来的时候,会通过jni回调回来,通过Handler往主线程消息队列post一个异步任务,最终是viewRootImpl去执行绘制任务,调用performTraversals方法,里面是View的三个方法回调。
现在,相信前面提到问题已经有答案了
应用需要主动请求vsync,vsync来的时候才会通过jni通知到应用,然后才调用View的三个绘制方法,如果没有发起绘制请求,例如没有requestLayout,View的绘制方法是不会被调用的。ViewRootimpl里面的这个View其实是DecorView
有两个地方会造成掉帧
一个是主线程有其他耗时操作,导致doFrame没有机会再vsync信号发出之后16毫米内调用,对应下图的3
另一个是当前doFrame方法耗时,绘制太久,下一个vsync信号来的时候,这一帧还没有画完,对应2
1是正常情况