1. 程式人生 > >hwui大概流程和FrameInfo統計資訊

hwui大概流程和FrameInfo統計資訊

Android hwui硬體加速從3.0版本開始引入到7.0已經非常複雜,這裡總結下大致的流程和原理

這裡寫圖片描述

如果input的down事件到來,有些view接收到事件要求系統重繪就會呼叫到ViewRootImpl.scheduleTraversals()方法如下程式碼

void scheduleTraversals() {
    if (!mTraversalScheduled) {
        mTraversalScheduled = true;
 mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
 mChoreographer.postCallback(
                Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null
); if (!mUnbufferedInputDispatch) { scheduleConsumeBatchedInput(); } notifyRendererOfFramePending(); pokeDrawLockIfNeeded(); } }

首先新增一個Choreographer.CALLBACK_TRAVERSAL回撥函式,等待app vsync(vyns訊號虛化)訊號到來處理,

注意第一次添加回調會請求surfaceflinger傳送app vsync事件給當前應用,之後呼叫scheduleConsumeBatchedInput()

void scheduleConsumeBatchedInput() {
    if (!mConsumeBatchedInputScheduled) {
        mConsumeBatchedInputScheduled = true;
 mChoreographer.postCallback(Choreographer.CALLBACK_INPUT,
 mConsumedBatchedInputRunnable, null);
 }
}

新增一個CALLBACK_INPUT回撥用於在 app vsynv到來後處理批量input事件.

之後vsync事件到來,會執行到Choreographer.doFrame()方法,下面是關鍵步驟

ry {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Choreographer#doFrame");

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 {
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.");
}

分如下幾步處理

1.處理input回撥處理事件,導致檢視變化

2.處理同步動畫

  1. 處理檢視變化

  2. 處理屬性動畫

對於第3步會更新檢視

這裡要補充的背景知識就是硬體加速的知識,hwui裡增加了一個執行緒render thread,會不斷輪訓新的繪製任務,app main執行緒在執行draw函式的時候

會生成一系列的繪製命令,(另外比較重要的是bitmap資源,如呼叫canvas.drawbitmap就要儲存這個要繪製的圖片). 將這些繪製命令儲存好後最終會

交給render thread去繪製. 交給render thread處理其實就是向它的任務佇列新增訊息,然後在threadloop中處理,

處理的過程要和main執行緒進行資料同步,在同步的過程中main執行緒是被阻塞的,防止main執行緒返回去後修改ui. 同步中最重要的一個步驟就是上傳bitmap

到gpu紋理.同步完成後就可以喚醒主執行緒去做事情了(這裡也並不一定直接喚醒,有時候要等到render thread渲染完成才會喚醒),

之後render thread就可以義無反顧的進行opengl繪製了,繪製完成要呼叫swapbuffer去把buffer交給

surfaceflinger進行合成(swapbuffer的過程會呼叫queueBuffer給surfaceflinger資料,同時也會呼叫dequeueBuffer獲取新的buffer用於下一幀繪製).

交換完buffer就是一些清理工作,之後進入睡眠等待下一次任務. 整個程式碼流程很長,也很複雜不詳細列舉程式碼.

對於先不喚醒main thread的情況就是上傳的紋理太多超出了限制(預設一個應用做多70M),太多的意思是同時使用的紋理資料,不使用的可以釋放(lrucache)

另外對於每一幀資料,hwui會統計如下資訊,這裡做出解釋

enum class FrameInfoIndex {
    Flags = 0,
 IntendedVsync,
 Vsync,
 OldestInputEvent,
 NewestInputEvent,
 HandleInputStart,
 AnimationStart,
 PerformTraversalsStart,
 DrawStart,
 // End of UI frame info

 SyncQueued,

 SyncStart,
 IssueDrawCommandsStart,
 SwapBuffers,
 FrameCompleted,

 DequeueBufferDuration,
 QueueBufferDuration,

 // Must be the last value!
 // Also must be kept in sync with FrameMetrics.java#FRAME_STATS_COUNT
 NumIndexes
};
  • Flags
    enum { WindowLayoutChanged = 1 << 0, RTAnimation = 1 << 1, SurfaceCanvas = 1 << 2, SkippedFrame = 1 << 3,};
    支援上四種flags 其中WindowLayoutChanged代表windowlayout發生變化(這裡會多用wms),SkippedFrame跳幀
    其他的還沒分析清楚

  • IntendedVsync app_vsync的時間

  • Vsync 開始處理vync事件的時間

  • OldestInputEvent 如上處理批量事件中最老的一個inputEvent的時間

  • NewestInputEvent 最新事件

  • HandleInputStart mainthread開始處理input事件的時間

  • AnimationStart mainthread開始處理動畫的時間

  • PerformTraversalsStart mainthread開始遍歷檢視的時間

  • DrawStart mainthread開始執行draw函式的時間

  • SyncQueued 新增事件到ThreadRender的時間

  • SyncStart renderthread開始同步main thread資料的的時間

  • IssueDrawCommandsStart renderThread開始繪製的時間

  • SwapBuffers renderThread開始交換buffer的時間

  • FrameCompleted renderThread 完成交換buffer的時間

  • DequeueBufferDuration renderThread交換buffer中dequeueBuffer花費的時間

  • QueueBufferDuration renderThread queueBuffer的時間

DequeueBufferDuration 和 QueueBufferDuration如果時間比較長的話系統卡段的可能性比較達
如果發生windowlayout的話PerformTraversalsStart和DrawStart 之間間隔比較長的話有可能是系統卡頓引起