在ViewRootImpl中有這么個(gè)方法scheduleTraversals
伦忠,如果你深入過View的繪制流程,那你應(yīng)該知道就是從這個(gè)方法開始觸發(fā)performTraversals
阿蝶,來調(diào)出之后的measure,layout鸯檬,draw隐岛。
-
初見
我們先貼出這個(gè)方法:
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
拋開其他的諸如阻塞handler等代碼,我們直接看```
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
-
探索
我們繼續(xù)跟著這個(gè)方法深究尖昏,看看它到底干了什么:
postCallback()->postCallbackDelayed()->postCallbackDelayedInternal():
private void postCallbackDelayedInternal(int callbackType,
Object action, Object token, long delayMillis) {
if (DEBUG_FRAMES) {
Log.d(TAG, "PostCallback: type=" + callbackType
+ ", action=" + action + ", token=" + token
+ ", delayMillis=" + delayMillis);
}
synchronized (mLock) {
final long now = SystemClock.uptimeMillis();
final long dueTime = now + delayMillis;
mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
//delayMillis傳的是0仰税,故此處進(jìn)入條件
if (dueTime <= now) {
//實(shí)際上單單從這個(gè)方法的名字我們就能意識(shí)到做的是跟幀有關(guān)的工作。
scheduleFrameLocked(now);
} else {
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
msg.arg1 = callbackType;
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, dueTime);
}
}
}
上鎖抽诉,來調(diào)動(dòng)幀的行為陨簇,很有可能就跟界面的一幀一幀的刷新有關(guān),我們接著往下看:
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.
//是否是主線程掸鹅,一般界面刷新能走到這一步都是在主線程中刷新的塞帐,所以進(jìn)入條件
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);
}
}
}
這里有個(gè)常量USE_VSYNC拦赠,表示是否允許動(dòng)畫和繪制的垂直同步巍沙,默認(rèn)是為true
// Enable/disable vsync for animations and drawing.
private static final boolean USE_VSYNC = SystemProperties.getBoolean(
"debug.choreographer.vsync", true);
緊接著scheduleVsyncLocked()-> mDisplayEventReceiver.scheduleVsync()->nativeScheduleVsync(mReceiverPtr)
走到一個(gè)native方法,底層我就暫時(shí)不分析荷鼠,有興趣的讀者可以去看一下句携,最后會(huì)在底層處理垂直同步,然后回調(diào)onVsync方法允乐,抽象類DisplayEventReceiver并沒有實(shí)現(xiàn)這個(gè)方法矮嫉,我們找到實(shí)現(xiàn)類是FrameDisplayEventReceiver。
@Override
public void onVsync(long timestampNanos, int builtInDisplayId, int frame) {
...
mTimestampNanos = timestampNanos;
mFrame = frame;
Message msg = Message.obtain(mHandler, this);
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
}
把自身包裝成Message傳給了handler牍疏,調(diào)用其run方法:
@Override
public void run() {
mHavePendingVsync = false;
doFrame(mTimestampNanos, mFrame);
}
void doFrame(long frameTimeNanos, int frame) {
final long startNanos;
synchronized (mLock) {
...
//是否有跳幀蠢笋,如果有那么就打印log并且修正偏差
}
//執(zhí)行callback
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方法做的就是渲染下一幀,第一個(gè)痛不塊中就是去檢測(cè)是否卡頓并修補(bǔ)卡頓鳞陨。然后開始做渲染工作昨寞,我們看幾個(gè)doCallbacks方法的參數(shù):
CALLBACK_INPUT:輸入
CALLBACK_ANIMATION:動(dòng)畫
CALLBACK_TRAVERSAL:遍歷,執(zhí)行measure厦滤、layout援岩、draw
CALLBACK_COMMIT:遍歷完成的提交操作,用來修正動(dòng)畫啟動(dòng)時(shí)間
會(huì)從上往下的去執(zhí)行一次
void doCallbacks(int callbackType, long frameTimeNanos) {
CallbackRecord callbacks;
synchronized (mLock) {
...
final long now = System.nanoTime();
callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked(
now / TimeUtils.NANOS_PER_MS);
if (callbacks == null) {
return;
}
mCallbacksRunning = true;
...
}
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);
}
}
Choreographer內(nèi)部維護(hù)了這四種鏈表掏导,渲染每一幀的時(shí)候都會(huì)從上往下的去執(zhí)行相應(yīng)的渲染操作享怀,有輸入那么就先渲染輸入隊(duì)列,有動(dòng)畫就渲染動(dòng)畫趟咆,然后遍歷添瓷,然后提交梅屉。
-
總結(jié)
控制外部輸入事件處理,動(dòng)畫執(zhí)行仰坦,UI變化履植,以及提交執(zhí)行都是在同一個(gè)類中做的處理,即是Choreographer悄晃。
在Choreographer對(duì)象中有四條鏈表玫霎,分別保存著待處理的輸入事件,待處理的動(dòng)畫事件妈橄,待處理的遍歷事件庶近,以及待處理的提交時(shí)間。
每次執(zhí)行的時(shí)候眷蚓,Choreographer會(huì)根據(jù)當(dāng)前的時(shí)間鼻种,只處理事件鏈表中最后一個(gè)事件,當(dāng)有耗時(shí)操作在主線程時(shí)沙热,事件不能及時(shí)執(zhí)行叉钥,就會(huì)出現(xiàn)所謂的“跳幀”,“卡頓”現(xiàn)象篙贸。
Choreographer的共有方法postCallback(callbackType, Object)是往事件鏈表中放事件的方法投队。而doFrame()是消耗這些事件的方法。