天天看點

invalidate三部曲之曆經Choreographer

已開通新的部落格,後續文字都會發到新部落格

http://www.0xfree.top

Choreographyer三部曲

  • invalidate三部曲之始于invalidate
  • invalidate三部曲之曆經Choreographer
Choreographer,編舞者。
void scheduleTraversals() {
	if (!mTraversalScheduled) {
		mTraversalScheduled = true;
		mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
		mChoreographer.postCallback(
				Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
		if (!mUnbufferedInputDispatch) {
			scheduleConsumeBatchedInput();
		}
		notifyRendererOfFramePending();
		pokeDrawLockIfNeeded();
	}
}
           

上一期講到了這裡,也就是

scheduleTraversals()

真正核心的内容,隻是加入了 Choreographer 的回調隊列。那麼這個回調隊列是怎麼一回事?

invalidate()

之後不是觸發 View 重新整理了嗎?加入回調隊列和這個有什麼關系?

這一篇文章,我們就來深入了解 Choreographer 這個類。

寫在前邊

Choreographer 負責連接配接 VSYNC 信号和 framework 層繪制。

從這篇文章為什麼是 VSYNC 我們知道, VSYNC 信号是一種重新整理機制,會根據顯示器的頻率定時觸發,通知CPU處理下一幀的繪制準備工作。

另一方面,Android 顯示系統是由 Window 及其内部的 View 構成,顯示的内容需要各個 Window 控件自己定義繪制,那麼顯示系統什麼時候該去處理繪制的工作呢?當然最合适的時機是 VSYNC 信号觸發,而 Choreographer 就是這二者之間的中介。顯示系統想要更新内容的時候,就通過 Choreographer 注冊一個回調,等 VSYNC 信号來臨時,就做一次更新,然後繼續注冊回調監聽下一次,直到沒有需要更新的内容。

另外,Android 為了系統的流暢度,又對繪制的内容分類,優先響應輸入事件,然後是動畫,接下來才是繪制,最後做一些收尾工作,這個繪制内容的分類就展現在内部維護的 4 個回調隊列中,等 VSYNC 事件來臨,按序響應。

整個 Choreographer 就做了這麼多事情,接下來我們按事件流順序讨論

從 scheduleTraversals() 開始講起

scheduleTraversals() 會調用 postCallBack()

/**
 * Posts a callback to run on the next frame.
 * <p>
 * The callback runs once then is automatically removed.
 * </p>
 *
 * @param callbackType The callback type.
 * @param action The callback action to run during the next frame.
 * @param token The callback token, or null if none.
 *
 * @see #removeCallbacks
 * @hide
 */
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;
		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);
		}
	}
}
           

這裡最後會将 Runnable 類型的 action 加入到 callbackType 所屬隊列,callbackType 的值為 CALLBACK_TRAVERSAL。

Choreographer 總共定義了四個類型的回調隊列 CallbackQueue,單向清單,在 Choreographer 初始化的時候就構造好了,然後會根據優先級來處理這四個回調隊列中的内容,我們後續詳解。

然後如果請求執行時間超過目前系統時間,會立即觸發,否則通過 hanlder 延時觸發

注冊回調

private void scheduleFrameLocked(long now) {
	if (!mFrameScheduled) {
		mFrameScheduled = true;
		if (USE_VSYNC) { // 如果使用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 { // 不使用VSYNC,那麼用自定義的定時操作觸發,可用于調試
			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);
		}
	}
}

private void scheduleVsyncLocked() {
	mDisplayEventReceiver.scheduleVsync(); // 注冊一個VSYNC監聽者
}

/**
 * Schedules a single vertical sync pulse to be delivered when the next
 * display frame begins.
 */
public void scheduleVsync() {
	......
	
	nativeScheduleVsync(mReceiverPtr);
	
}
           

立即觸發的内容主要是使用本地方法去注冊了一個回調,當然如果之前注冊過了,這裡什麼都不會做。另外這裡也提供了 USE_VSYNC 變量決定是通過 VSYNC 觸發,還是使用 Handler 定時觸發,友善調試

有注冊當然就有回調,在 DisplayEventReceiver 這個類中,我們找到了從 native 回調回來的方法

// Called from native code.
@SuppressWarnings("unused")
private void dispatchVsync(long timestampNanos, int builtInDisplayId, int frame) {
	onVsync(timestampNanos, builtInDisplayId, frame);
}
           

onVsync

方法的具體實作是在子類中,傳說中的

模闆方法

設計模式,DisplayEventReceiver 隻有一個實作類 FrameDisplayEventReceiver

接收回調

private final class FrameDisplayEventReceiver extends DisplayEventReceiver implements Runnable {
	private boolean mHavePendingVsync;
	private long mTimestampNanos;
	private int mFrame;

	public FrameDisplayEventReceiver(Looper looper, int vsyncSource) {
		super(looper, vsyncSource);
	}

	@Override
	public void onVsync(long timestampNanos, int builtInDisplayId, int frame) { // vsync信号發生時回調
		......

		// 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.
		// 使用Handler來保證vsync信号依次按序執行,防止因為vsync信号,導緻整個隊列餓死,
		// 兩個問題:1. 為啥要用Handler?而不是直接調用執行
		// 2. prevent這句是什麼意思?
		......
		
		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);
	}
}

           

這裡通過 Handler 的方式将自己發送出去執行

doFrame()

,而不是通過直接調用的方式,主要是為了解決不同線程中的同步問題,這裡是通過 Message 的

setAsynchronous()

方法實作。有時間會通過解析 Handler 機制來分析這個方法。

doFrame

兜兜轉轉,終于來到我們今天的主角。如果你使用 systrace 分析過 Android 性能問題,那麼你一定不會對這個函數感到陌生,這就是整個 input, animation, measure, layout, draw 的源頭。

void doFrame(long frameTimeNanos, int frame) {
	......
	
	doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);
	......
	doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);
	......
	doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);

	doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
	
	......
}

void doCallbacks(int callbackType, long frameTimeNanos) {
   CallbackRecord callbacks;
	......
	
		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);
		}
	......
}
           

這四個 doCallbacks 所做的就是處理之前我們通過 postCallback() 傳入的回調,這裡會根據不同的類型,按序處理,通過這段代碼,我們可以看到執行的優先級是

  1. CALLBACK_INPUT,輸入事件
  2. CALLBACK_ANIMATION,動畫
  3. CALLBACK_TRAVERSAL,繪制
  4. CALLBACK_COMMIT,收尾處理

也就是我們文章開頭所說的繪制類型。

結束

還記得我們調用

invalidate()

加入的回調嗎?

在這裡

final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
final class TraversalRunnable implements Runnable {
	@Override
	public void run() {
		doTraversal();
	}
}
           

這個回調是 ViewRootImpl 自己定義的,也就是說 Choreographyer 隻是幫忙居中協調,規範了 ViewRootImpl 的執行時間罷了,具體的内容還需要靠 ViewRootImpl 自己,

好,這一篇我們就分析到這裡,已經将 Choreographyer 摸了個透,

之前神神秘秘的類,其實内容就是這麼簡單

很多時候,恐懼來源于未知,知道了,也就沒有那麼可怕了。

預知

view 樹真實排程發生了什麼?

所謂的硬體加速到底是什麼?

請看下回分解