1.1.Project Butter,ui繪製原理,動畫原理

Android Project Butter分析

Project Butter(黃油計劃)的特性,包括:java

  1. 經過Vsync信號來同步UI繪製和動畫,使得它們能夠得到一個達到60fps的固定的幀率;

    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可知:

  •  時間從0開始,進入第一個16ms:Display顯示第0幀,CPU處理完第一幀後,GPU緊接其後處理繼續第一幀。三者互不干擾,一切正常。
  • 時間進入第二個16ms:由於早在上一個16ms時間內,第1幀已經由CPU,GPU處理完畢。故Display能夠直接顯示第1幀。顯示沒有問題。但在本16ms期間,CPU和GPU卻並未及時去繪製第2幀數據(注意前面的空白區),而是在本週期快結束時,CPU/GPU纔去處理第2幀數據。
  • 時間進入第3個16ms,此時Display應該顯示第2幀數據,但因爲CPU和GPU尚未處理完第2幀數據,故Display只能繼續顯示第一幀的數據,結果使得第1幀多畫了一次(對應時間段上標註了一個Jank)。

經過上述分析可知,此處發生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可知:

  • 在第二個16ms時間段,Display本應顯示B幀,但卻由於GPU還在處理B幀,致使A幀被重複顯示。
  • 同理,在第二個16ms時間段內,CPU無所事事,由於A Buffer被Display在使用。B Buffer被GPU在使用。注意,一旦過了VSYNC時間點,CPU就不能被觸發以處理繪製工做了。

 

爲何CPU不能在第二個16ms處開始繪製工做呢?緣由就是隻有兩個Buffer。若是有第三個Buffer的存在,CPU就能直接使用它,而不至於空閒。出於這一思路就引出了Triple Buffer。結果如圖4所示:

4  Triple Buffer的狀況

 

由圖4可知:

  • 第二個16ms時間段,CPU使用C Buffer繪圖。雖然仍是會多顯示A幀一次,但後續顯示就比較順暢了。

 

是否是Buffer越多越好呢?回答是否認的。由圖4可知,在第二個時間段內,CPU繪製的第C幀數據要到第四個16ms才能顯示,這比雙Buffer狀況多了16ms延遲。因此,Buffer最好仍是兩個,三個足矣。

介紹了上述背景知識後,下文將分析Android Project Buffer的一些細節。

二Project Buffer分析

上一節對VSYNC進行了理論分析,其實也引出了Project Buffer的三個關鍵點:

  • 核心關鍵:須要VSYNC定時中斷。
  • Triple Buffer:當雙Buffer不夠使用時,該系統可分配第三塊Buffer。
  • 另外,還有一個很是隱祕的關鍵點:即將繪製工做都統一到VSYNC時間點上。這就是Choreographer的做用。Choreographer是一個極富詩意的詞,意爲舞蹈編導。在它的統一指揮下,應用的繪製工做都將變得層次分明。

下面來看Project Buffer實現的細節。

2.1SurfaceFlinger家族的改進

首先被動刀的是SurfaceFlinger家族成員。目標是提供VSYNC中斷。相關類圖如圖5所示:

5  SurfaceFlinger中和VSYNC有關的類

 

由圖5可知:

  • HardwareComposer封裝了相關的HAL層,若是硬件廠商提供的HAL層實現能定時產生VSYNC中斷,則直接使用硬件的VSYNC中斷,不然HardwareComposer內部會經過VSyncThread來模擬產生VSYNC中斷(其實現很簡單,就是sleep固定時間,而後喚醒)。
  • 當VSYNC中斷產生時(無論是硬件產生仍是VSyncThread模擬的),VSyncHandler的onVSyncReceived函數將被調用。因此,對VSYNC中斷來講,VSyncHandler的onVSyncReceived,就是其中斷處理函數。

 

在SurfaceFlinger家族中,VSyncHandler的實例是EventThread。下邊是EventThread類的聲明:

class EventThread : public Thread, public DisplayHardware::VSyncHandler

 

由EventThread定義可知,它自己運行在一個單獨的線程中,並繼承了VSyncHandler。EventThread的核心處理在其線程函數threadLoop中完成,其處理邏輯主要是:

  • 等待下一次VSYNC的到來,並派發該中斷事件給VSYNC監聽者。

經過EventThread,VSYNC中斷事件可派發給多個該中斷的監聽者去處理。相關類如圖6所示:

6  EventThreadVSYNC中斷監聽者

 

由圖6可知:

  • SurfaceFlinger從Thread派生,其核心功能單獨運行在一個線程中。
  • SurfaceFlinger包括一個EventThread和一個MessageQueue對象。
  • 對象經過mEvents成員指向一個IDisplayEventConnection類型的對象。IDisplayEventConnection是一個純虛類,它表明VSYNC中斷的監聽者。其實體類是EventThread的內部類Connection。
  • IDisplayEventConnection定義了一個getDataChannel函數,該函數返回一個BitTube實例。這個實例提供的read/write方法,用於傳送具體的信號數據(其內部實現爲socketpair,可經過Binder實現進程跨越)。

 

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。

2.2Choreographer介紹

協調動畫,輸入和繪圖的時間。

Choreographer是一個Java類。第一次看到這個詞時,我很激動。一個小小的命名真的反應出了設計者除coding以外的廣博的視界。試想,若是不是對舞蹈有至關了解或喜好,通常人很難想到用這個詞來描述它。

Choreographer的定義和基本結構如圖8所示:

8  Choreographer的定義和結構

圖8中:

  • Choreographer是線程單例的,並且必需要和一個Looper綁定,由於其內部有一個Handler須要和Looper綁定。
  • DisplayEventReceiver是一個abstract class,其JNI的代碼部分會建立一個IDisplayEventConnection的VSYNC監聽者對象。這樣,來自EventThread的VSYNC中斷信號就能夠傳遞給Choreographer對象了。由圖8可知,當VSYNC信號到來時,DisplayEventReceiver的onVsync函數將被調用。
  • 另外,DisplayEventReceiver還有一個scheduleVsync函數。當應用須要繪製UI時,將首先申請一次VSYNC中斷,而後再在中斷處理的onVsync函數去進行繪製。
  • Choreographer定義了一個FrameCallback interface,每當VSYNC到來時,其doFrame函數將被調用。這個接口對Android Animation的實現起了很大的幫助做用。之前都是本身控制時間,如今終於有了固定的時間中斷。

 

Choreographer的主要功能是,當收到VSYNC信號時,去調用使用者經過postCallback設置的回調函數。目前一共定義了三種類型的回調,它們分別是:

  • CALLBACK_INPUT:優先級最高,和輸入事件處理有關。
  • CALLBACK_ANIMATION:優先級其次,和Animation的處理有關。
  • CALLBACK_TRAVERSAL:優先級最低,Handles layout and draw. Runs after all other asynchronous messages have been handled。
  • CALLBACK_COMMIT:Handles post-draw operations for the frame. Runs after traversal completes, 遍歷完成的提交操做,用來修正動畫啓動時間。

 

優先級高低和處理順序有關。當收到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了。

 

 

 

choreographer流程圖

 

 

doFrame流程圖

 

 

UI繪製原理

view.invalidate-->viewgroup.invalidateChildInParent-->...-->ViewRootImpl.invalidateChild-->ViewRootImpl.scheduleTraversals

ViewRootImpl:

    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;
            }
        }
    }

 

 

Choreographer:

    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

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());
        }
};

 

爲何animation要在traversals的前邊

當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的前邊。

 

api16 View新加入的幾個方法

// 把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);
    }
}

 

 

Animation

不管是用純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()方法。

   

1.View.startAnimation(Animation)

先是調用View.setAnimation(Animation)方法給本身設置一個Animation對象,這個對象是View類中的一個名爲mCurrentAnimation的成員變量。

而後它調用invalidate()來重繪本身。

我想,既然setAnimation()了,那麼它要用的時候,確定要getAnimation(),找到這個方法在哪裏調用就行了。因而經過搜索,在View.draw(Canvas, ViewGroup, long)方法中發現了它的調用,代碼片斷以下:

   

2.View.draw(Canvas, ViewGroup, long)

其中調用了View.drawAnimation()方法。

   

3.View.drawAnimation(ViewGroup, long, Animation, boolean)

代碼片斷以下:

其中調用了Animation.getTransformation()方法。

   

4.Animation.getTransformation(long, Transformation, float)

該方法直接調用了兩個參數Animation.getTransformation()方法。

   

5.Animation.getTransformation(long, Transformation)

該方法先將參數currentTime處理成一個float表示當前動畫進度,好比說,一個2000ms的動畫,已經執行了1000ms了,那麼進度就是0.5或者說50%。

而後將進度值傳入插值器(Interpolator)獲得新的進度值,前者是均勻的,隨着時間是一個直線的線性關係,而經過插值器計算後獲得的是一個曲線的關係。

而後將新的進度值和Transformation對象傳入applyTranformation()方法中。

   

6.Animation.applyTransformation(float, Transformation)

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

 

Choreographer簡單幀率監控應用

https://www.jianshu.com/p/9b14bf15ddaf

相關文章
相關標籤/搜索