Project Butter(黃油計劃)的特性,包括:java
B. 三緩衝支持,改善GPU和CPU之間繪製節奏不一致的問題;android
C. 將用戶輸入,例如touch event,同步到下一個Vsync信號到來時再處理;算法
D. 預測用戶的touch行爲,以得到更好的交互響應;api
E. 每次用戶touch屏幕時,進行CPU Input Boost,以便減小處理延時。數組
隨着時間的推移,Android OS系統一直在不斷進化、壯大,日趨完善。但直到Android 4.0問世,有關UI顯示不流暢的問題也一直未獲得根本解決。在整個進化過程當中,Android在Display(顯示)系統這塊也下了很多功夫,例如,使用硬件加速等技術,但本質緣由彷佛和硬件關係並不大,由於iPhone的硬件配置並不比那些價格相近的Android機器的硬件配置強,而iPhone UI的流暢性強倒是有目共睹的。數據結構
從Android 4.1(版本代號爲Jelly Bean)開始,Android OS開發團隊便力圖在每一個版本中解決一個重要問題(這是否是也意味着Android OS在通過幾輪大規模改善後,開始進入手術刀式的精加工階段呢?)。做爲嚴重影響Android口碑問題之一的UI流暢性差的問題,首先在Android 4.1版本中獲得了有效處理。其解決方法就是本文要介紹的Project Butter。app
Project Butter對Android Display系統進行了重構,引入了三個核心元素,即VSYNC、Triple Buffer和Choreographer([ˌkɒrɪ'ɒɡrəfə(r)] 編舞者)。其中,VSYNC是理解Project Buffer的核心。VSYNC是Vertical Synchronization(垂直同步)的縮寫,是一種在PC上已經很早就普遍使用的技術。讀者可簡單的把它認爲是一種定時中斷。socket
接下來,本文將圍繞VSYNC來介紹Android Display系統的工做方式。請注意,後續討論將以Display爲基準,將其劃分紅16ms長度的時間段,在每一時間段中,Display顯示一幀數據(至關於每秒60幀)。時間段從1開始編號。async
首先是沒有VSYNC的狀況,如圖1所示:ide
圖1 沒有VSYNC的繪圖過程
由圖1可知:
經過上述分析可知,此處發生Jank的關鍵問題在於,爲什麼第1個16ms段內,CPU/GPU沒有及時處理第2幀數據?緣由很簡單,CPU多是在忙別的事情(好比某個應用經過sleep固定時間來實現動畫的逐幀顯示),不知道該處處理UI繪製的時間了。可CPU一旦想起來要去處理第2幀數據,時間又錯過了!
爲解決這個問題,Project Butter引入了VSYNC,這相似於時鐘中斷。結果如圖2所示:
圖2 引入VSYNC的繪製過程
由圖2可知,每收到VSYNC中斷,CPU就開始處理各幀數據。整個過程很是完美。
不過,仔細琢磨圖2卻會發現一個新問題:圖2中,CPU和GPU處理數據的速度彷佛都能在16ms內完成,並且還有時間空餘,也就是說,CPU/GPU的FPS(幀率,Frames Per Second)要高於Display的FPS。確實如此。因爲CPU/GPU只在收到VSYNC時纔開始數據處理,故它們的FPS被拉低到與Display的FPS相同。但這種處理並無什麼問題,由於Android設備的Display FPS通常是60,其對應的顯示效果很是平滑。
若是CPU/GPU的FPS小於Display的FPS,會是什麼狀況呢?請看圖3:
圖3 CPU/GPU FPS較小的狀況
由圖3可知:
爲何CPU不能在第二個16ms處開始繪製工做呢?緣由就是隻有兩個Buffer。若是有第三個Buffer的存在,CPU就能直接使用它,而不至於空閒。出於這一思路就引出了Triple Buffer。結果如圖4所示:
圖4 Triple Buffer的狀況
由圖4可知:
是否是Buffer越多越好呢?回答是否認的。由圖4可知,在第二個時間段內,CPU繪製的第C幀數據要到第四個16ms才能顯示,這比雙Buffer狀況多了16ms延遲。因此,Buffer最好仍是兩個,三個足矣。
介紹了上述背景知識後,下文將分析Android Project Buffer的一些細節。
上一節對VSYNC進行了理論分析,其實也引出了Project Buffer的三個關鍵點:
下面來看Project Buffer實現的細節。
首先被動刀的是SurfaceFlinger家族成員。目標是提供VSYNC中斷。相關類圖如圖5所示:
圖5 SurfaceFlinger中和VSYNC有關的類
由圖5可知:
在SurfaceFlinger家族中,VSyncHandler的實例是EventThread。下邊是EventThread類的聲明:
class EventThread : public Thread, public DisplayHardware::VSyncHandler
由EventThread定義可知,它自己運行在一個單獨的線程中,並繼承了VSyncHandler。EventThread的核心處理在其線程函數threadLoop中完成,其處理邏輯主要是:
經過EventThread,VSYNC中斷事件可派發給多個該中斷的監聽者去處理。相關類如圖6所示:
圖6 EventThread和VSYNC中斷監聽者
由圖6可知:
EventThread最重要的一個VSYNC監聽者就是MessageQueue的mEvents對象。固然,這一切都是爲最大的後臺老闆SurfaceFlinger服務的。來自EventThread的VSYNC中斷信號,將經過MessageQueue轉化爲一個REFRESH消息並傳遞給SurfaceFlinger的onMessageReceived函數處理。
有必要指出,4.1中SurfaceFlinger onMessageReceived函數的實現僅僅是將4.0版本的SurfaceFlinger的核心函數挪過來罷了[②],並未作什麼改動。
以上是Project Buffer對SurfaceFlinger所作的一些改動。那麼Triple Buffer是怎麼處理的呢?幸虧從Android 2.2開始,Display的Page Flip算法就不依賴Buffer的個數,Buffer個數不過是算法的一個參數罷了。因此,Triple Buffer的引入,只是把Buffer的數目改爲了3,而算法自己相對於4.0來講並無變化。圖7爲Triple Buffer的設置示意圖:
圖7 Layer.cpp中對Triple Buffer的設置
圖7所示,爲Layer.cpp中對Buffer個數的設置。TARGET_DISABLE_TRIPLE_BUFFERING宏可設置Buffer的個數。對某些內存/顯存並非很大的設備,也能夠選擇不使用Triple Buffer。
協調動畫,輸入和繪圖的時間。
Choreographer是一個Java類。第一次看到這個詞時,我很激動。一個小小的命名真的反應出了設計者除coding以外的廣博的視界。試想,若是不是對舞蹈有至關了解或喜好,通常人很難想到用這個詞來描述它。
Choreographer的定義和基本結構如圖8所示:
圖8 Choreographer的定義和結構
圖8中:
Choreographer的主要功能是,當收到VSYNC信號時,去調用使用者經過postCallback設置的回調函數。目前一共定義了三種類型的回調,它們分別是:
優先級高低和處理順序有關。當收到VSYNC中斷時,Choreographer將首先處理INPUT類型的回調,而後是ANIMATION類型,最後纔是TRAVERSAL類型。
此外,讀者在自行閱讀Choreographer相關代碼時,還會發現Android對Message/Looper類也進行了一番小改造,使之支持Asynchronous Message和Synchronization Barrier(參照Looper.java的postSyncBarrier函數)。其實現很是巧妙,這部份內容就留給讀者本身理解並欣賞了。
相比SurfaceFlinger,Choreographer是Android 4.1中的新事物,下面將經過一個實例來簡單介紹Choreographer的工做原理。
假如UI中有一個控件invalidate了,那麼它將觸發ViewRootImpl的invalidate函數,該函數將最終調用ViewRootImpl的scheduleTraversals。其代碼如圖9所示:
圖9 ViewRootImpl scheduleTraversals函數的實現
由圖9可知,scheduleTraversals首先禁止了後續的消息處理功能,這是由設置Looper的postSyncBarrier來完成的。一旦設置了SyncBarrier,全部非Asynchronous的消息便將中止派發。
而後,爲Choreographer設置了CALLBACK類型爲TRAVERSAL的處理對象,即mTraversalRunnable。
最後調用scheduleConsumeBatchedInput,這個函數將爲Choreographer設置了CALLBACK類型爲INPUT的處理對象。
Choreographer的postCallback函數將會申請一次VSYNC中斷(經過調用DisplayEventReceiver的scheduleVsync實現)。當VSYNC信號到達時,Choreographer doFrame函數被調用,內部代碼會觸發回調處理。代碼片斷如圖10所示:
圖10 Choreographer doFrame函數片斷
對ViewRootImpl來講,其TRAVERSAL回調對應的處理對象,就是前面介紹的mTraversalRunnable,它的代碼很簡單,如圖11所示:
圖11 mTraversalRunnable的實現
doTraversal內部實現和Android 4.0版本一致。故相比於4.0來講,4.1只是把doTraversal調用位置放到VSYNC中斷處理中了。
經過上邊的介紹,可知Choreographer確實作到了對繪製工做的統一安排,不愧是個長於統籌安排的"舞蹈編導"。
本文經過對Android Project Butter的分析,向讀者介紹了VSYNC原理以及Android Display系統的實現。除了VSYNC外,Project Butter還包括其餘一些細節的改進,例如避免重疊區域的繪製等。
簡言之,Project Butter從本質上解決了Android UI不流暢的問題,並且從Google I/O給出的視頻來看,其效果至關不錯。但實際上它對硬件配置仍是有必定要求的。由於VSYNC中斷處理的線程優先級必定要高,不然EventThread接收到VSYNC中斷,卻不能及時去處理,那就喪失同步的意義了。因此,筆者估計目前將有一大批單核甚至雙核機器沒法嚐到Jelly Bean了。
view.invalidate-->viewgroup.invalidateChildInParent-->...-->ViewRootImpl.invalidateChild-->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(); } } final class TraversalRunnable implements Runnable { @Override public void run() { doTraversal(); } } void unscheduleTraversals() { if (mTraversalScheduled) { mTraversalScheduled = false; mHandler.getLooper().removeSyncBarrier(mTraversalBarrier); mChoreographer.removeCallbacks(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); } } void doTraversal() { if (mTraversalScheduled) { mTraversalScheduled = false; mHandler.getLooper().removeSyncBarrier(mTraversalBarrier); if (mProfile) { Debug.startMethodTracing("ViewAncestor"); } Trace.traceBegin(Trace.TRACE_TAG_VIEW, "performTraversals"); try { performTraversals(); } finally { Trace.traceEnd(Trace.TRACE_TAG_VIEW); } if (mProfile) { Debug.stopMethodTracing(); mProfile = false; } } }
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; } } } private void postCallbackDelayedInternal(int callbackType, Runnable 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); } } } void doScheduleCallback(int callbackType) { synchronized (mLock) { if (!mFrameScheduled) { final long now = SystemClock.uptimeMillis(); if (mCallbackQueues[callbackType].hasDueCallbacksLocked(now)) { scheduleFrameLocked(now); } } } } 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. 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); } } } void doScheduleVsync() { synchronized (mLock) { if (mFrameScheduled) { scheduleVsyncLocked(); } } } // 申請一次VSYNC中斷,當下一幀刷新的時候會回調mDisplayEventReceiver的onVsync(); // 而回調onVsync的線程確定非ui線程,不然的話若是MessageQueue處於阻塞狀態,即ui線程處於阻塞狀態確定無法運行 private void scheduleVsyncLocked() { mDisplayEventReceiver.scheduleVsync(); } private final class FrameDisplayEventReceiver extends DisplayEventReceiver implements Runnable { @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); } @Override public void run() { mHavePendingVsync = false; doFrame(mTimestampNanos, mFrame); } } void doFrame(long frameTimeNanos, int frame) { mFrameScheduled = false; ... 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; synchronized (mLock) { // We use "now" to determine when callbacks become due because it's possible // for earlier processing phases in a frame to post callbacks that should run // in a following phase, such as an input event that causes an animation to start. final long now = System.nanoTime(); callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked(now / TimeUtils.NANOS_PER_MS); if (callbacks == null) { return; } mCallbacksRunning = true; ... } try { // 從頭遍歷到尾,執行run for (CallbackRecord c = callbacks; c != null; c = c.next) { c.run(frameTimeNanos); } } finally { synchronized (mLock) { mCallbacksRunning = false; do { final CallbackRecord next = callbacks.next; recycleCallbackLocked(callbacks); callbacks = next; } while (callbacks != null); } } }
!結合 framework下的 深刻理解控件樹的繪製 的文檔一塊兒看!
animator 是CALLBACK_ANIMATION
Animator.start()-->AnimationHandler.scheduleAnimation private void scheduleAnimation() { if (!mAnimationScheduled) { mChoreographer.postCallback(Choreographer.CALLBACK_ANIMATION, mAnimate, null); mAnimationScheduled = true; } } // Called by the Choreographer. final Runnable mAnimate = new Runnable() { @Override public void run() { mAnimationScheduled = false; // 裏面處理每一幀動畫,在最後再次scheduleAnimation doAnimationFrame(mChoreographer.getFrameTime()); } };
當vsync信號到來時,Choreographer就會執行doFrame裏的幾個操做(input,animation,traversals),
當執行到animation類型的callback時,就會回調doAnimationFrame,在doAnimationFrame裏會根據設置來改變更畫值,
而後會回調監聽animationUpdate,咱們通常會改變對應的屬性值,並invalidate,此時就會出發一次scheduleTraversals,把這次的callback加入Choreographer對應隊列數組中,
當執行完animation類型的callback後,就會執行traversals類型的callback,把traversals類型的callback一個一個的從隊列中取出執行。
在doAnimationFrame最後若是動畫沒完,則會繼續scheduleAnimation。
這就是爲何animation要在traversals的前邊。
// 把invalidate放在 CALLBACK_ANIMATION 中操做,
// 因此在view的繪製時若是調用了invalidate的話就會在以後的 CALLBACK_TRAVERSAL 中直接執行,而不會等到下一幀時才執行,這樣的話就能充分利用這次的幀繪製時間,從而加快繪製。
public void postInvalidateOnAnimation() { // We try only with the AttachInfo because there's no point in invalidating // if we are not attached to our window final AttachInfo attachInfo = mAttachInfo; if (attachInfo != null) { attachInfo.mViewRootImpl.dispatchInvalidateOnAnimation(this); } }
// 把自定義的操做放到 CALLBACK_ANIMATION 中處理。
public void postOnAnimation(Runnable action) { final AttachInfo attachInfo = mAttachInfo; if (attachInfo != null) { attachInfo.mViewRootImpl.mChoreographer.postCallback( Choreographer.CALLBACK_ANIMATION, action, null); } else { // Assume that post will succeed later ViewRootImpl.getRunQueue().post(action); } }
不管是用純java代碼構建Animation對象,仍是經過xml文件定義Animation,其實最終的結果都是
Animation a = new AlphaAnimation(); Animation b = new ScaleAnimation(); Animation c = new RotateAnimation(); Animation d = new TranslateAnimation();
分別是透明度,縮放,旋轉,位移四種動畫效果。
而咱們使用的時候,通常是用這樣的形式:
View.startAnimation(a);
那麼就來看看View中的startAnimation()方法。
先是調用View.setAnimation(Animation)方法給本身設置一個Animation對象,這個對象是View類中的一個名爲mCurrentAnimation的成員變量。
而後它調用invalidate()來重繪本身。
我想,既然setAnimation()了,那麼它要用的時候,確定要getAnimation(),找到這個方法在哪裏調用就行了。因而經過搜索,在View.draw(Canvas, ViewGroup, long)方法中發現了它的調用,代碼片斷以下:
其中調用了View.drawAnimation()方法。
代碼片斷以下:
其中調用了Animation.getTransformation()方法。
該方法直接調用了兩個參數Animation.getTransformation()方法。
該方法先將參數currentTime處理成一個float表示當前動畫進度,好比說,一個2000ms的動畫,已經執行了1000ms了,那麼進度就是0.5或者說50%。
而後將進度值傳入插值器(Interpolator)獲得新的進度值,前者是均勻的,隨着時間是一個直線的線性關係,而經過插值器計算後獲得的是一個曲線的關係。
而後將新的進度值和Transformation對象傳入applyTranformation()方法中。
Animation的applyTransformation()方法是空實現,具體實現它的是Animation的四個子類,而該方法正是真正的處理動畫變化的過程。分別看下四個子類的applyTransformation()的實現。
ScaleAnimation
AlphaAnimation
RotateAnimation
TranslateAnimation
可見applyTransformation()方法就是動畫具體的實現,系統會以一個比較高的頻率來調用這個方法,通常狀況下60FPS,是一個很是流暢的畫面了,也就是16ms,爲了驗證這一點,我在applyTransformation方法中加入計算時間間隔並打印的代碼進行驗證,代碼以下:
最終獲得的log以下圖所示:
右側是"手動"計算出來的時間差,有必定的波動,但大體上是16-17ms的樣子,左側是日誌打印的時間,時間很是規則的相差20ms。
因而,根據以上的結果,能夠得出如下內容:
1.首先證實了一點,Animation.applyTransformation()方法,是動畫具體的調用方法,咱們能夠覆寫這個方法,快速的製做本身的動畫。
2.另外一點,爲何是16ms左右調用這個方法呢?是誰來控制這個頻率的?
對於以上的疑問,我有兩個猜想:
1.系統本身以postDelayed(this, 16)的形式調用的這個方法。
2.系統一個死循環瘋狂的調用,運行一系列方法走到這個位置的間隔恰好是16ms左右,若是主線程卡了,這個間隔就變長了。
在4.1以前是使用handler,4.1以後使用的是VSYNC