主線程運行的本質,其實就是 Message 的處理過程,咱們的各類操做,包括每一幀的渲染操做 ,都是經過 Message 的形式發給主線程的 MessageQueue ,MessageQueue 處理完消息繼續等下一個消息,以下圖所示java
MethodTrace 圖示android
Systrace 圖示git
引入 Vsync 以前的 Android 版本,渲染一幀相關的 Message ,中間是沒有間隔的,上一幀繪製完,下一幀的 Message 緊接着就開始被處理。這樣的問題就是,幀率不穩定,可能高也可能低,不穩定,以下圖github
MethodTrace 圖示shell
Systrace 圖示app
對於用戶來講,穩定的幀率纔是好的體驗,好比你玩王者榮耀,相比 fps 在 60 和 40 之間頻繁變化,用戶感受更好的是穩定在 50 fps 的狀況。異步
因此 Android 的演進中,引入了 Vsync + TripleBuffer + Choreographer 的機制,其主要目的就是提供一個穩定的幀率輸出機制,讓軟件層和硬件層能夠以共同的頻率一塊兒工做。ide
Choreographer 的引入,主要是配合 Vsync ,給上層 App 的渲染提供一個穩定的 Message 處理的時機,也就是 Vsync 到來的時候 ,系統經過對 Vsync 信號週期的調整,來控制每一幀繪製操做的時機. 目前大部分手機都是 60Hz 的刷新率,也就是 16.6ms 刷新一次,系統爲了配合屏幕的刷新頻率,將 Vsync 的週期也設置爲 16.6 ms,每一個 16.6 ms , Vsync 信號喚醒 Choreographer 來作 App 的繪製操做 ,這就是引入 Choreographer 的主要做用函數
Choreographer 扮演 Android 渲染鏈路中承上啓下的角色工具
從上面能夠看出來, Choreographer 擔任的是一個工具人的角色,他之因此重要,是由於經過 Choreographer + SurfaceFlinger + Vsync + TripleBuffer 這一套從上到下的機制,保證了 Android App 能夠以一個穩定的幀率運行(目前大部分是 60fps),減小幀率波動帶來的不適感.
瞭解 Choreographer 還能夠幫助 App 開發者知道程序每一幀運行的基本原理,也能夠加深對 Message、Handler、Looper、MessageQueue、Measure、Layout、Draw 的理解 , 不少 APM 工具也用到了 Choreographer( 利用 FrameCallback + FrameInfo ) + MessageQueue ( 利用 IdleHandler ) + Looper ( 設置自定義 MessageLogging) 這些組合拳,深刻了解了這些以後,再去作優化,腦子裏的思路會更清晰。
另外雖然畫圖是一個比較好的解釋流程的好路子,可是我我的不是很喜歡畫圖,由於平時 Systrace 和 MethodTrace 用的比較多,Systrace 是按從左到右展現整個系統的運行狀況的一個工具(包括 cpu、SurfaceFlinger、SystemServer、App 等關鍵進程),使用 Systrace 和 MethodTrace 也能夠很方便地展現關鍵流程。當你對系統代碼比較熟悉的時候,看 Systrace 就能夠和手機運行的實際狀況對應起來。因此下面的文章除了一些網圖以外,其餘的我會多以 Systrace 來展現.
下圖以滑動桌面爲例子,咱們先看一下從左到右滑動桌面的一個完整的預覽圖(App 進程),能夠看到 Systrace 中從左到右,每個綠色的幀都表示一幀,表示最終咱們能夠手機上看到的畫面
有了上面這個總體的概念,咱們將 UI Thread 的每一幀放大來看,看看 Choreogrepher 的位置以及 Choreogrepher 是怎麼組織每一幀的
第一步初始化完成後,後續就會在步驟 2-9 之間循環
同時也附上這一幀所對應的 MethodTrace(這裏預覽一下便可,下面會有詳細的大圖)
下面咱們就從源碼的角度,來看一下具體的實現
// Thread local storage for the choreographer.
private static final ThreadLocal<Choreographer> sThreadInstance =
new ThreadLocal<Choreographer>() {
@Override
protected Choreographer initialValue() {
// 獲取當前線程的 Looper
Looper looper = Looper.myLooper();
......
// 構造 Choreographer 對象
Choreographer choreographer = new Choreographer(looper, VSYNC_SOURCE_APP);
if (looper == Looper.getMainLooper()) {
mMainInstance = choreographer;
}
return choreographer;
}
};
複製代碼
private Choreographer(Looper looper, int vsyncSource) {
mLooper = looper;
// 1. 初始化 FrameHandler
mHandler = new FrameHandler(looper);
// 2. 初始化 DisplayEventReceiver
mDisplayEventReceiver = USE_VSYNC
? new FrameDisplayEventReceiver(looper, vsyncSource)
: null;
mLastFrameTimeNanos = Long.MIN_VALUE;
mFrameIntervalNanos = (long)(1000000000 / getRefreshRate());
//3. 初始化 CallbacksQueues
mCallbackQueues = new CallbackQueue[CALLBACK_LAST + 1];
for (int i = 0; i <= CALLBACK_LAST; i++) {
mCallbackQueues[i] = new CallbackQueue();
}
......
}
複製代碼
private final class FrameHandler extends Handler {
......
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_DO_FRAME://開始渲染下一幀的操做
doFrame(System.nanoTime(), 0);
break;
case MSG_DO_SCHEDULE_VSYNC://請求 Vsync
doScheduleVsync();
break;
case MSG_DO_SCHEDULE_CALLBACK://處理 Callback
doScheduleCallback(msg.arg1);
break;
}
}
}
複製代碼
在 Activity 啓動過程,執行完 onResume 後,會調用 Activity.makeVisible(),而後再調用到 addView(), 層層調用會進入以下方法
ActivityThread.handleResumeActivity(IBinder, boolean, boolean, String) (android.app)
-->WindowManagerImpl.addView(View, LayoutParams) (android.view)
-->WindowManagerGlobal.addView(View, LayoutParams, Display, Window) (android.view)
-->ViewRootImpl.ViewRootImpl(Context, Display) (android.view)
public ViewRootImpl(Context context, Display display) {
......
mChoreographer = Choreographer.getInstance();
......
}
複製代碼
Vsync 的註冊和回調經過 FrameDisplayEventReceiver 這個類,因此能夠先簡單介紹一下。 FrameDisplayEventReceiver 繼承 DisplayEventReceiver , 有三個比較重要的方法
private final class FrameDisplayEventReceiver extends DisplayEventReceiver implements Runnable {
......
@Override
public void onVsync(long timestampNanos, long physicalDisplayId, int frame) {
......
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);
}
public void scheduleVsync() {
......
nativeScheduleVsync(mReceiverPtr);
......
}
}
複製代碼
從下面的函數調用棧能夠看到,Choreographer 的內部類 FrameDisplayEventReceiver.onVsync 負責接收 Vsync 回調,通知 UIThread 進行數據處理。
那麼 FrameDisplayEventReceiver 是經過什麼方式在 Vsync 信號到來的時候回調 onVsync 呢?答案是 FrameDisplayEventReceiver 的初始化的時候,最終經過監聽文件句柄的形式,其對應的初始化流程以下
android/view/Choreographer.java
private Choreographer(Looper looper, int vsyncSource) {
mLooper = looper;
mDisplayEventReceiver = USE_VSYNC
? new FrameDisplayEventReceiver(looper, vsyncSource)
: null;
......
}
複製代碼
android/view/Choreographer.java
public FrameDisplayEventReceiver(Looper looper, int vsyncSource) {
super(looper, vsyncSource);
}
複製代碼
android/view/DisplayEventReceiver.java
public DisplayEventReceiver(Looper looper, int vsyncSource) {
......
mMessageQueue = looper.getQueue();
mReceiverPtr = nativeInit(new WeakReference<DisplayEventReceiver>(this), mMessageQueue,
vsyncSource);
}
複製代碼
nativeInit 後續的代碼能夠本身跟一下,能夠對照這篇文章和源碼,因爲篇幅比較多,這裏就不細寫了(www.jianshu.com/p/304f56f5d…) , 後續梳理好這一塊的邏輯後,會在另外的文章更新。
DisplayEventReceiver 初始化完成後,Choreographer 與 SurfaceFlinger 的 Vsync 信號傳遞通道就創建好了,後續有 Vsync 信號到來時,就能夠沿着這條通道,一路回調到 Java 層的 Choreographer.FrameDisplayEventReceiver.onVsync 方法了。
Choreographer 處理繪製的邏輯核心在 Choreographer.doFrame 函數中,從下圖能夠看到,FrameDisplayEventReceiver.onVsync post 了本身,其 run 方法直接調用了 doFrame 開始一幀的邏輯處理
android/view/Choreographer.java
public void onVsync(long timestampNanos, long physicalDisplayId, int frame) {
......
mTimestampNanos = timestampNanos;
mFrame = frame;
Message msg = Message.obtain(mHandler, this);
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
}
public void run() {
mHavePendingVsync = false;
doFrame(mTimestampNanos, mFrame);
}
複製代碼
doFrame 函數主要作下面幾件事
void doFrame(long frameTimeNanos, int frame) {
final long startNanos;
synchronized (mLock) {
......
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.");
}
}
......
}
......
}
複製代碼
Choreographer.doFrame 的掉幀檢測比較簡單,從下圖能夠看到,Vsync 信號到來的時候會標記一個 start_time ,執行 doFrame 的時候標記一個 end_time ,這兩個時間差就是 Vsync 處理時延,也就是掉幀
咱們以 Systrace 的掉幀的實際狀況來看掉幀的計算邏輯
這裏須要注意的是,這種方法計算的掉幀,是前一幀的掉幀狀況,而不是這一幀的掉幀狀況,這個計算方法是有缺陷的,會致使有的掉幀沒有被計算到
Choreographer 中 FrameInfo 來負責記錄幀的繪製信息,doFrame 執行的時候,會把每個關鍵節點的繪製時間記錄下來,咱們使用 dumpsys gfxinfo 就能夠看到。固然 Choreographer 只是記錄了一部分,剩餘的部分在 hwui 那邊來記錄。
從 FrameInfo 這些標誌就能夠看出記錄的內容,後面咱們看 dumpsys gfxinfo 的時候數據就是按照這個來排列的
// Various flags set to provide extra metadata about the current frame
private static final int FLAGS = 0;
// Is this the first-draw following a window layout?
public static final long FLAG_WINDOW_LAYOUT_CHANGED = 1;
// A renderer associated with just a Surface, not with a ViewRootImpl instance.
public static final long FLAG_SURFACE_CANVAS = 1 << 2;
@LongDef(flag = true, value = {
FLAG_WINDOW_LAYOUT_CHANGED, FLAG_SURFACE_CANVAS })
@Retention(RetentionPolicy.SOURCE)
public @interface FrameInfoFlags {}
// The intended vsync time, unadjusted by jitter
private static final int INTENDED_VSYNC = 1;
// Jitter-adjusted vsync time, this is what was used as input into the
// animation & drawing system
private static final int VSYNC = 2;
// The time of the oldest input event
private static final int OLDEST_INPUT_EVENT = 3;
// The time of the newest input event
private static final int NEWEST_INPUT_EVENT = 4;
// When input event handling started
private static final int HANDLE_INPUT_START = 5;
// When animation evaluations started
private static final int ANIMATION_START = 6;
// When ViewRootImpl#performTraversals() started
private static final int PERFORM_TRAVERSALS_START = 7;
// When View:draw() started
private static final int DRAW_START = 8;
複製代碼
doFrame 函數記錄從 Vsync time 到 markPerformTraversalsStart 的時間
void doFrame(long frameTimeNanos, int frame) {
......
mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos);
// 處理 CALLBACK_INPUT Callbacks
mFrameInfo.markInputHandlingStart();
// 處理 CALLBACK_ANIMATION Callbacks
mFrameInfo.markAnimationsStart();
// 處理 CALLBACK_INSETS_ANIMATION Callbacks
// 處理 CALLBACK_TRAVERSAL Callbacks
mFrameInfo.markPerformTraversalsStart();
// 處理 CALLBACK_COMMIT Callbacks
......
}
複製代碼
void doFrame(long frameTimeNanos, int frame) {
......
// 處理 CALLBACK_INPUT Callbacks
doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);
// 處理 CALLBACK_ANIMATION Callbacks
doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);
// 處理 CALLBACK_INSETS_ANIMATION Callbacks
doCallbacks(Choreographer.CALLBACK_INSETS_ANIMATION, frameTimeNanos);
// 處理 CALLBACK_TRAVERSAL Callbacks
doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
// 處理 CALLBACK_COMMIT Callbacks
doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
......
}
複製代碼
Input 回調調用棧
**input callback 通常是執行 **ViewRootImpl.ConsumeBatchedInputRunnable
android/view/ViewRootImpl.java
final class ConsumeBatchedInputRunnable implements Runnable {
@Override
public void run() {
doConsumeBatchedInput(mChoreographer.getFrameTimeNanos());
}
}
void doConsumeBatchedInput(long frameTimeNanos) {
if (mConsumeBatchedInputScheduled) {
mConsumeBatchedInputScheduled = false;
if (mInputEventReceiver != null) {
if (mInputEventReceiver.consumeBatchedInputEvents(frameTimeNanos)
&& frameTimeNanos != -1) {
scheduleConsumeBatchedInput();
}
}
doProcessInputEvents();
}
}
複製代碼
Input 時間通過處理,最終會傳給 DecorView 的 dispatchTouchEvent,這就到了咱們熟悉的 Input 事件分發
Animation 回調調用棧
通常咱們接觸的多的是調用 View.postOnAnimation 的時候,會使用到 CALLBACK_ANIMATION
public void postOnAnimation(Runnable action) {
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
attachInfo.mViewRootImpl.mChoreographer.postCallback(
Choreographer.CALLBACK_ANIMATION, action, null);
} else {
// Postpone the runnable until we know
// on which thread it needs to run.
getRunQueue().post(action);
}
}
複製代碼
那麼通常是何時回調用到 View.postOnAnimation 呢,我截取了一張圖,你們能夠本身去看一下,接觸最多的應該是 startScroll,Fling 這種操做
其調用棧根據其 post 的內容,下面是桌面滑動鬆手以後的 fling 動畫。
另外咱們的 Choreographer 的 FrameCallback 也是用的 CALLBACK_ANIMATION
public void postFrameCallbackDelayed(FrameCallback callback, long delayMillis) {
if (callback == null) {
throw new IllegalArgumentException("callback must not be null");
}
postCallbackDelayedInternal(CALLBACK_ANIMATION,
callback, FRAME_CALLBACK_TOKEN, delayMillis);
}
複製代碼
Traversal 調用棧
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
//爲了提升優先級,先 postSyncBarrier
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
}
}
final class TraversalRunnable implements Runnable {
@Override
public void run() {
// 真正開始執行 measure、layout、draw
doTraversal();
}
}
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
// 這裏把 SyncBarrier remove
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
// 真正開始
performTraversals();
}
}
private void performTraversals() {
// measure 操做
if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight() || contentInsetsChanged || updatedConfiguration) {
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
}
// layout 操做
if (didLayout) {
performLayout(lp, mWidth, mHeight);
}
// draw 操做
if (!cancelDraw && !newSurface) {
performDraw();
}
}
複製代碼
doTraversal 的 TraceView 示例
因爲動畫、滑動、Fling 這些操做的存在,咱們須要一個連續的、穩定的幀率輸出機制。這就涉及到了 Vsync 的請求邏輯,在連續的操做,好比動畫、滑動、Fling 這些狀況下,每一幀的 doFrame 的時候,都會根據狀況觸發下一個 Vsync 的申請,這樣咱們就能夠得到連續的 Vsync 信號。咱們下面以 Animation 爲例,看看 Animation 是如何驅動下一個 Vsync ,來持續更新畫面的
android/animation/ObjectAnimator.java
public void start() {
super.start();
}
複製代碼
android/animation/ValueAnimator.java
private void start(boolean playBackwards) {
......
addAnimationCallback(0); // 動畫 start 的時候添加 Animation Callback
......
}
private void addAnimationCallback(long delay) {
......
getAnimationHandler().addAnimationFrameCallback(this, delay);
}
複製代碼
android/animation/AnimationHandler.java
public void addAnimationFrameCallback(final AnimationFrameCallback callback, long delay) {
if (mAnimationCallbacks.size() == 0) {
// post FrameCallback
getProvider().postFrameCallback(mFrameCallback);
}
......
}
// 這裏的 mFrameCallback 回調 doFrame,裏面 post了本身
private final Choreographer.FrameCallback mFrameCallback = new Choreographer.FrameCallback() {
@Override
public void doFrame(long frameTimeNanos) {
doAnimationFrame(getProvider().getFrameTime());
if (mAnimationCallbacks.size() > 0) {
// post 本身
getProvider().postFrameCallback(this);
}
}
};
複製代碼
調用 postFrameCallback 會走到 mChoreographer.postFrameCallback ,這裏就會觸發 Choreographer 的 Vsync 請求邏輯
android/animation/AnimationHandler.java
public void postFrameCallback(Choreographer.FrameCallback callback) {
mChoreographer.postFrameCallback(callback);
}
複製代碼
android/view/Choreographer.java
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) {
// 請求 Vsync scheduleFrameLocked ->scheduleVsyncLocked-> mDisplayEventReceiver.scheduleVsync ->nativeScheduleVsync
scheduleFrameLocked(now);
} else {
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
msg.arg1 = callbackType;
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, dueTime);
}
}
}
複製代碼
經過上面的 Animation.start 設置,利用了 Choreographer.FrameCallback 接口,每一幀都去請求下一個 Vsync 動畫過程當中一幀的 TraceView 示例
因爲 Choreographer 的位置,許多性能監控的手段都是利用 Choreographer 來作的,除了自帶的掉幀計算,Choreographer 提供的 FrameCallback 和 FrameInfo 都給 App 暴露了接口,讓 App 開發者能夠經過這些方法監控自身 App 的性能,其中經常使用的方法以下:
public interface FrameCallback {
public void doFrame(long frameTimeNanos);
}
複製代碼
Choreographer.getInstance().postFrameCallback(youOwnFrameCallback );
複製代碼
public void postFrameCallbackDelayed(FrameCallback callback, long delayMillis) {
......
postCallbackDelayedInternal(CALLBACK_ANIMATION,
callback, FRAME_CALLBACK_TOKEN, delayMillis);
}
複製代碼
TinyDancer 就是使用了這個方法來計算 FPS (github.com/friendlyrob…)
adb shell dumpsys gfxinfo framestats
Window: StatusBar
Stats since: 17990256398ns
Total frames rendered: 1562
Janky frames: 361 (23.11%)
50th percentile: 6ms
90th percentile: 23ms
95th percentile: 36ms
99th percentile: 101ms
Number Missed Vsync: 33
Number High input latency: 683
Number Slow UI thread: 273
Number Slow bitmap uploads: 8
Number Slow issue draw commands: 18
Number Frame deadline missed: 287
HISTOGRAM: 5ms=670 6ms=128 7ms=84 8ms=63 9ms=38 10ms=23 11ms=21 12ms=20 13ms=25 14ms=39 15ms=65 16ms=36 17ms=51 18ms=37 19ms=41 20ms=20 21ms=19 22ms=18 23ms=15 24ms=14 25ms=8 26ms=4 27ms=6 28ms=3 29ms=4 30ms=2 31ms=2 32ms=6 34ms=12 36ms=10 38ms=9 40ms=3 42ms=4 44ms=5 46ms=8 48ms=6 53ms=6 57ms=4 61ms=1 65ms=0 69ms=2 73ms=2 77ms=3 81ms=4 85ms=1 89ms=2 93ms=0 97ms=2 101ms=1 105ms=1 109ms=1 113ms=1 117ms=1 121ms=2 125ms=1 129ms=0 133ms=1 150ms=2 200ms=3 250ms=0 300ms=1 350ms=1 400ms=0 450ms=0 500ms=0 550ms=0 600ms=0 650ms=0
---PROFILEDATA---
Flags,IntendedVsync,Vsync,OldestInputEvent,NewestInputEvent,HandleInputStart,AnimationStart,PerformTraversalsStart,DrawStart,SyncQueued,SyncStart,IssueDrawCommandsStart,SwapBuffers,FrameCompleted,DequeueBufferDuration,QueueBufferDuration,
0,10158314881426,10158314881426,9223372036854775807,0,10158315693363,10158315760759,10158315769821,10158316032165,10158316627842,10158316838988,10158318055915,10158320387269,10158321770654,428000,773000,
0,10158332036261,10158332036261,9223372036854775807,0,10158332799196,10158332868519,10158332877269,10158333137738,10158333780654,10158333993206,10158335078467,10158337689561,10158339307061,474000,885000,
0,10158348665353,10158348665353,9223372036854775807,0,10158349710238,10158349773102,10158349780863,10158350405863,10158351135967,10158351360446,10158352300863,10158354305654,10158355814509,471000,836000,
0,10158365296729,10158365296729,9223372036854775807,0,10158365782373,10158365821019,10158365825238,10158365975290,10158366547946,10158366687217,10158367240706,10158368429248,10158369291852,269000,476000,
複製代碼
命令解釋:
數據:
掉幀 jank 計算
每一行均可以經過下面的公式獲得一個值,該值是一個標準,咱們稱爲jankflag,若是當前行的jankflag與上一行的jankflag發生改變,那麼就叫掉幀
ceil((C - A) / refresh-period)
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken("android.ui.ISurfaceComposer");
mFlinger.transact(1013, data, reply, 0);
final int pageFlipCount = reply.readInt();
final long now = System.nanoTime();
final int frames = pageFlipCount - mLastPageFlipCount;
final long duration = now - mLastUpdateTime;
mFps = (float) (frames * 1e9 / duration);
mLastPageFlipCount = pageFlipCount;
mLastUpdateTime = now;
reply.recycle();
data.recycle();
複製代碼
SKIPPED_FRAME_WARNING_LIMIT 默認爲30 , 應用能夠獲取 hook 這個值進行修改
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.");
}
}
複製代碼
App 能夠經過 hook 修改這個默認值,以便根據這個來監控性能
Field field = Choreographer.class.getDeclaredField("SKIPPED_FRAME_WARNING_LIMIT");
field.setAccessible(true);
field.set(Choreographer.class, 0);
複製代碼
Blockcanary 計算作性能監控使用的是 Looper 的消息機制,經過對 MessageQueue 中每個 Message 的先後進行記錄,打到監控性能的目的
android/os/Looper.java
public static void loop() {
...
for (;;) {
...
// This must be in a local variable, in case a UI event sets the logger
Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
msg.target.dispatchMessage(msg);
if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}
...
}
}
複製代碼
所謂的異步消息其實就是這樣的,咱們能夠經過 enqueueBarrier 往消息隊列中插入一個 Barrier,那麼隊列中執行時間在這個 Barrier 之後的同步消息都會被這個 Barrier 攔截住沒法執行,直到咱們調用 removeBarrier 移除了這個 Barrier,而異步消息則沒有影響,消息默認就是同步消息,除非咱們調用了 Message 的 setAsynchronous,這個方法是隱藏的。只有在初始化 Handler 時經過參數指定往這個 Handler 發送的消息都是異步的,這樣在 Handler 的 enqueueMessage 中就會調用 Message 的 setAsynchronous 設置消息是異步的,從上面 Handler.enqueueMessage 的代碼中能夠看到。
所謂異步消息,其實只有一個做用,就是在設置 Barrier 時仍能夠不受 Barrier 的影響被正常處理,若是沒有設置 Barrier,異步消息就與同步消息沒有區別,能夠經過 removeSyncBarrier 移除 Barrier
scheduleTraversals 的時候 postSyncBarrier
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
//爲了提升優先級,先 postSyncBarrier
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
}
}
複製代碼
doTraversal 的時候 removeSyncBarrier
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
// 這裏把 SyncBarrier remove
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
// 真正開始
performTraversals();
}
}
複製代碼
Choreographer post Message 的時候,會把這些消息設爲 Asynchronous ,這樣Choreographer 中的這些 Message 的優先級就會比較高,
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
msg.arg1 = callbackType;
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, dueTime);
複製代碼
系統廠商因爲能夠直接修改源碼,也利用這方面的便利,作一些功能和優化,不過因爲保密的問題,代碼就不直接放上來了,我能夠大概說一下思路,感興趣的能夠私下討論
Choreographer 自己是沒有 input 消息的, 不過修改源碼以後,input 消息能夠直接給到 Choreographer 這裏, 有了這些 Input 消息,Choreographer 就能夠作一些事情,好比說提早響應,不去等 Vsync
當一個 Android App 退到後臺以後,只要他沒有被殺死,那麼他作什麼事情你們都不要奇怪,由於這就是 Android。有的 App 退到後臺以後還在持續調用 Choreographer 中的 Animation Callback,而這個 Callback 的執行徹底是無心義的,並且用戶還不知道,可是對 cpu 的佔用是比較高的。
因此在 Choreographer 中會針對這種狀況作優化,禁止不符合條件的 App 在後臺繼續無用的操做
和移動事件優化同樣,因爲有了 Input 事件的信息,在某些場景下咱們能夠通知 SurfaceFlinger 不用取等待 Vsync 直接作合成操做
咱們前面說,主線程的全部操做都是給予 Message 的 ,若是某個操做,非重要的 Message 被排列到了隊列後面,那麼對這個操做產生影響;而經過從新排列 MessageQueue,在應用啓動的時候,把啓動相關的重要的啓動 Message 放到隊列前面,來起到加快啓動速度的做用
90 fps 的手機上 , Vsync 間隔從 16.6ms 變成了 11.1ms ,這帶來了巨大的性能和功耗挑戰,如何在一幀內完成渲染的必要操做,是手機廠商必需要思考和優化的地方:
點贊或者交流,能夠移步本文的知乎界面 知乎 - Android 基於 Choreographer 的渲染機制詳解
小廠系統研發工程師 , 更多信息能夠點擊 關於我 , 很是但願和你們一塊兒交流 , 共同進步 .