1. 程式人生 > >Android黃油計劃之Choreographer原理解析

Android黃油計劃之Choreographer原理解析

   搞客戶端開發,時間也有點了,但是每次想起來,總感覺自己掌握的東西零零散散,沒有一點集在的感覺,應用層的懂,framework的也懂,框架啥的瞭解一點,分層的思想也有一些,JVM的原理啊,記憶體分配和管理啊,執行機制啊啥的也知道一點,每次下班或者沒事了,就在考慮,自己應該有一個主攻方向,往這個方向集中發展一下,首選的幾個目標應該是非常清楚的,我們要掌握android,那麼關於android的View機制、動畫原理這些都是必須要掌握的,所以呢,自己想在這幾個方面花些時間,好好研究一下,這樣才能使自己更具競爭力。

     好了,不管是要了解View機制,還是android動畫,我們應該都需要有Choreographer的知識,明白系統重新整理機制到底是怎麼樣的,這樣才能對其他方面有更好的輔助。本章部落格,我們就來學習一下Android中的Choreographer的執行機制。

     我們都知道,應用層的一個Activity對應一個根View(也就是一個DecorView)、一個WindowState、一個ViewRootImpl,每個物件都非常重要,都是在Activity新增過程中重量級的物件,DecorView是當前Activity的根View,它裡面管理著當前介面的View樹;WindowState物件是當前Activity視窗在系統側WindowManagerService中代理物件;ViewRootImpl則肩負著View的標準三步曲的處理和事件分發,而View繪製也是由Choreographer指導的,Choreographer的英文意思就是編舞者、舞蹈指揮,看著非常形象。那我們就從Choreographer物件的構建開始說起吧,它的構建是在ViewRootImpl的構造方法中的,程式碼如下:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
public ViewRootImpl(Context context, Display display) { mContext = context; mWindowSession = WindowManagerGlobal.getWindowSession(); mDisplay = display; mBasePackageName = context.getBasePackageName(); mDisplayAdjustments = display.getDisplayAdjustments(); mThread = Thread.currentThread(); mLocation = new WindowLeaked(null); mLocation.fillInStackTrace(); mWidth = -1; mHeight = -1; mDirty = new Rect(); mTempRect = new Rect(); mVisRect = new Rect(); mWinFrame = new Rect(); mWindow = new W(this); mTargetSdkVersion = context.getApplicationInfo().targetSdkVersion; mViewVisibility = View.GONE; mTransparentRegion = new Region(); mPreviousTransparentRegion = new Region(); // [+LEUI-9331] mPreBlurParams = new BlurParams(); // [-LEUI-9331] mFirst = true; // true for the first time the view is added mAdded = false; mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this); mAccessibilityManager = AccessibilityManager.getInstance(context); mAccessibilityInteractionConnectionManager = new AccessibilityInteractionConnectionManager(); mAccessibilityManager.addAccessibilityStateChangeListener( mAccessibilityInteractionConnectionManager); mHighContrastTextManager = new HighContrastTextManager(); mAccessibilityManager.addHighTextContrastStateChangeListener( mHighContrastTextManager); mViewConfiguration = ViewConfiguration.get(context); mDensity = context.getResources().getDisplayMetrics().densityDpi; mNoncompatDensity = context.getResources().getDisplayMetrics().noncompatDensityDpi; mFallbackEventHandler = new PhoneFallbackEventHandler(context); mChoreographer = Choreographer.getInstance(); mDisplayManager = (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE); loadSystemProperties(); /** * M: increase instance count and check log property to determine * whether to enable/disable log system. @{ */ mIdent = sIdent++; checkViewRootImplLogProperty(); if (LOCAL_LOGV) { enableLog(true, "a"); } if (DEBUG_LIFECYCLE) { Log.v(TAG, "ViewRootImpl construct: context = " + context + ", mThread = " + mThread + ", mChoreographer = " + mChoreographer + ", mTraversalRunnable = " + mTraversalRunnable + ", this = " + this); } }
snippet_file_0.txt

     從構造方法中可以看到Choreographer是單例模式的,也就是一個ViewRootImpl物件對應一個Choreographer,當介面需要重繪時,都會呼叫到ViewRootImp類的scheduleTraversals()方法,這裡的實現也比較簡單,程式碼如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
void scheduleTraversals() { if (!mTraversalScheduled) { mTraversalScheduled = true; mTraversalBarrier = mHandler.getLooper().postSyncBarrier(); mChoreographer.postCallback( Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); scheduleConsumeBatchedInput(); } }
snippet_file_0.txt

     mTraversalScheduled表示是否已經發起重繪,每次scheduleTraversals()方法呼叫之後,就會將它置為true,然後在下次呼叫doTraversal()又先將它置為false,然後呼叫mChoreographer.postCallback()新增一個Runnable,請注意,第一個引數是Choreographer.CALLBACK_TRAVERSAL,在Choreographer當前,新增的型別一共有三種,分別是:CALLBACK_INPUT、CALLBACK_ANIMATION、CALLBACK_TRAVERSAL,分別表示事件回撥、動畫回撥、繪製回撥。postCallback()方法是轉而呼叫postCallbackDelayed()方法的,最後一個引數delayMillis傳的是0,表示當前的重繪不需要延時,我們跟進去看一下新增的postCallbackDelayed()方法的程式碼:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
/** * Posts a callback to run on the next frame after the specified delay. * <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 after the specified delay. * @param token The callback token, or null if none. * @param delayMillis The delay time in milliseconds. * * @see #removeCallback * @hide */ public void postCallbackDelayed(int callbackType, Runnable action, Object token, long delayMillis) { if (action == null) { throw new IllegalArgumentException("action must not be null"); } if (callbackType < 0 || callbackType > CALLBACK_LAST) { throw new IllegalArgumentException("callbackType is invalid"); } postCallbackDelayedInternal(callbackType, action, token, delayMillis); }
snippet_file_0.txt      首先判斷引數action是否為空,action就是我們要回調的物件,回撥物件都為空了,那我們還幹啥呢?其實判斷callbackType,在整個過程中,只定義了上面描述的三種類型的事件,如果傳入的type值不符合,那就丟擲一個IllegalArgumentException("callbackType is invalid")異常。引數正常了,繼續呼叫postCallbackDelayedInternal()進一步處理。postCallbackDelayedInternal()方法的程式碼如下:
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
private void postCallbackDelayedInternal(int callbackType, Object action, Object token, long delayMillis) { if (DEBUG) { 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); 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); } } }
snippet_file_0.txt      此處獲取當前時間,然後加上要延遲的時間,作為當前Callback的時間點,以這個時間點作為標準,把Callback物件新增到mCallbackQueues[callbackType]隊列當中,這塊的邏輯和Looper、MessageQueue、Handler中新增Message的邏輯很相似,大家可以對比學習。然後判斷dueTime <= now,這塊的邏輯看了半天,我確實沒看懂,dueTime會有比now小的情況嗎,也就是傳進來的delayMillis小於0,再往上講,就是當前要新增的回撥要在上一次新增的回撥之前,這感覺不太可能吧?如果有弄懂的朋友,煩請解答一下。此處應該是執行else分支,往當前的佇列中新增一個Message,那麼通過Handler機制就會進行處理,此處的mHandler是一個FrameHandler物件,我們來看一下FrameHandler的程式碼:
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
private final class FrameHandler extends Handler { public FrameHandler(Looper looper) { super(looper); } @Override public void handleMessage(Message msg) { switch (msg.what) { case MSG_DO_FRAME: doFrame(System.nanoTime(), 0); break; case MSG_DO_SCHEDULE_VSYNC: doScheduleVsync(); break; case MSG_DO_SCHEDULE_CALLBACK: doScheduleCallback(msg.arg1); break; } } }
snippet_file_0.txt      這裡的message訊息也比較簡單,MSG_DO_FRAME指系統在沒有使用Vsync機制的時候,使用非同步訊息來重新整理螢幕,當然,大家一定要理解,此處的重新整理其實只是重新整理螢幕工作的很小一部分,只是回撥ViewRootImpl方法中新增的Runnable物件,最終是呼叫根View的draw方法,讓每個子View有把自己的影象元素填充到分配好的視訊記憶體當中,而要完全顯示,還有很多工作要作,最終是在SurfaceFlinger類中對所有視窗的View進行合成,然後渲染,最終post到FrameBuffer上,才能顯示出來的;MSG_DO_SCHEDULE_VSYNC當然就是指系統使用Vsync來重新整理了;MSG_DO_SCHEDULE_CALLBACK就是指新增Callback或者FrameCallback完成的訊息了。好了,我們繼續看MSG_DO_SCHEDULE_CALLBACK的訊息處理,它是呼叫doScheduleCallback(msg.arg1)來進行處理的,msg.arg1是剛才新增訊息時的型別。我們整個看一下handleMessage()方法的程式碼,發現非常簡單,這也是一個非常好的習慣,我們平時的程式碼當中,也應該儘量這樣實現,這樣一眼就可以看出來這個方法所要作的事情,把具體的處理放到每個細節方法中去。我們來看一下doScheduleCallback()方法的實現:
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
void doScheduleCallback(int callbackType) { synchronized (mLock) { if (!mFrameScheduled) { final long now = SystemClock.uptimeMillis(); if (mCallbackQueues[callbackType].hasDueCallbacksLocked(now)) { scheduleFrameLocked(now); } } } }
snippet_file_0.txt      mFrameScheduled和ViewRootImpl的scheduleTraversals()方法中的變數mTraversalScheduled作用是一樣的,也是判斷當前是否正在執行新增,然後呼叫(mCallbackQueues[callbackType].hasDueCallbacksLocked(now))判斷是否已處理過Callback事務,該方法的判斷也很簡單,(mHead != null && mHead.dueTime<= now),如果當前佇列頭不為空,並且佇列頭元素的時間點小於當前的時間點,那就說明是之前新增的,則需要對它進行處理;相反,如果佇列頭為空或者新增的時間點大於當前的時間點,也就是要延遲處理,則不需要任何操作。條件符合的話,就呼叫scheduleFrameLocked(now)進一步處理,我們來看一下scheduleFrameLocked()方法的實現:
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
private void scheduleFrameLocked(long now) { if (!mFrameScheduled) { mFrameScheduled = true; if (USE_VSYNC) { if (DEBUG) { 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 / NANOS_PER_MS + sFrameDelay, now); if (DEBUG) { Log.d(TAG, "Scheduling next frame in " + (nextFrameTime - now) + " ms."); } Message msg = mHandler.obtainMessage(MSG_DO_FRAME); msg.setAsynchronous(true); mHandler.sendMessageAtTime(msg, nextFrameTime); } } }
snippet_file_0.txt      此片一開始就把mFrameScheduled賦值為true,表示事務開始執行了,那麼上面doScheduleCallback()方法當中的程式碼此該就不會再執行了。接下來的邏輯以USE_VSYNC分開,意思也非常明瞭,就是系統是否使用Vsync重新整理機制,它是通過獲取系統屬性得到的,private static final boolean USE_VSYNC =  SystemProperties.getBoolean("debug.choreographer.vsync",true)。如果使用了Vsync垂直同步機制,則一步判斷當前執行緒是否具備訊息迴圈,如果有訊息迴圈,則立即請求下一次Vsync訊號,如果不具有訊息迴圈,則通過當前程序的主執行緒請求Vsync訊號;如果沒有使用Vsync機制,則使用非同步訊息延時執行螢幕重新整理。是否具有訊息迴圈是通過呼叫isRunningOnLooperThreadLocked()方法完成判斷的,它的實現很簡單,return Looper.myLooper() == mLooper。因為當Choreographer物件在建立的時候,引數looper就是呼叫Looperlooper = Looper.myLooper()獲取回來的,也就是說當前程序肯定是有訊息迴圈的,所以此處的判斷為true,其他兩個分支:當前執行緒不具備訊息迴圈和系統未使用Vsync同步機制的邏輯,我們就不分析了,大家有興趣的話,可以自己跟蹤一下。進入if分支,繼續呼叫scheduleVsyncLocked()方法進行處理,它的實現非常簡單,就是呼叫mDisplayEventReceiver.scheduleVsync()來請求下一次Vsync訊號。     看到這裡,是不是感覺邏輯有點多了,開始亂了,轉來轉去的,系統到底要幹啥?呵呵,我們暫停下來梳理一下,系統作了這麼多事情最終的目的就是在下一次Vsync訊號到來的時候,將Choreographer當中的三個佇列中的事務執行起來,這些事務是應用層ViewRootImpl在scheduleTraversals()方法中新增進去的,在Choreographer當中,我們要先將外邊傳進來的Callback放入佇列,然後就要去請求Vsync訊號,因為Vsync訊號是定時產生的,你不請求,它就不會理你,當然你收不到回撥,也就不知道啥時候通知ViewRootImpl執行View的measure、layout、draw了,這樣說一下,大家清楚我們要幹什麼了嗎?我第一次看Choreographer類的程式碼時候,看了半天,也是亂了,所以這裡大概理一下。     好,我們搞清楚目的了,繼續往前走,我們現在已經將Callback新增到佇列中了,下一步要作的就是請求Vsync訊號了。mDisplayEventReceiver是一個FrameDisplayEventReceiver物件,我們來看一下它的程式碼定義:
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
private final class FrameDisplayEventReceiver extends DisplayEventReceiver implements Runnable { private boolean mHavePendingVsync; private long mTimestampNanos; private int mFrame; public FrameDisplayEventReceiver(Looper looper) { super(looper); } @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 / NANOS_PER_MS); } @Override public void run() { mHavePendingVsync = false; doFrame(mTimestampNanos, mFrame); } }
snippet_file_0.txt      我們可以看到這裡的mTimestampNanos時間定義都是納秒級別的,因為Vsync訊號是用來同步螢幕重新整理頻率的,所以對時間的要求非常高,才採用了納秒級別的,如果大家對Vsync訊號的產生機制不瞭解的話,可以看我前面的部落格:Vsync垂直同步訊號分發和SurfaceFlinger響應執行渲染流程分析(一),mDisplayEventReceiver類變數是在Choreographer的構造方法中賦值的,我們繼續來看它的scheduleVsync()方法的實現,因為FrameDisplayEventReceiver類是繼承DisplayEventReceiver的,而它沒用對scheduleVsync()方法重寫,所以是呼叫父類的:
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
/** * 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); } }
snippet_file_0.txt      它的實現很簡單,判斷描述符mReceiverPtr是否合法,如果非法就列印日誌,什麼也不作了,合法的話,就繼續呼叫native方法nativeScheduleVsync(mReceiverPtr)來請求Vsync訊號。nativeScheduleVsync()方法實現在android_view_DisplayEventReceiver.cpp當中,是通過定義JNINativeMethod gMethods[]來定義方法呼叫指標的,因為此類的程式碼不多,這裡就全部貼出來,方便大家檢視:
   1
   2
   3
   4
   5
   6
   7
   8
   9
  10
  11
  12
  13
  14
  15
  16
  17
  18
  19
  20
  21
  22
  23
  24
  25
  26
  27
  28
  29
  30
  31
  32
  33
  34
  35
  36
  37
  38
  39
  40
  41
  42
  43
  44
  45
  46
  47
  48
  49
  50
  51
  52
  53
  54
  55
  56
  57
  58
  59
  60
  61
  62
  63
  64
  65
  66
  67
  68
  69
  70
  71
  72
  73
  74
  75
  76
  77
  78
  79
  80
  81
  82
  83
  84
  85
  86
  87
  88
  89
  90
  91
  92
  93
  94
  95
  96
  97
  98
  99
 100
 101
 102
 103
 104
 105
 106
 107
 108
 109
 110
 111
 112
 113
 114
 115
 116
 117
 118
 119
 120
 121
 122
 123
 124
 125
 126
 127
 128
 129
 130
 131
 132
 133
 134
 135
 136
 137
 138
 139
 140
 141
 142
 143
 144
 145
 146
 147
 148
 149
 150
 151
 152
 153
 154
 155
 156
 157
 158
 159
 160
 161
 162
 163
 164
 165
 166
 167
 168
 169
 170
 171
 172
 173
 174
 175
 176
 177
 178
 179
 180
 181
 182
 183
 184
 185
 186
 187
 188
 189
 190
 191
 192
 193
 194
 195
 196
 197
 198
 199
 200
 201
 202
 203
 204
 205