Android動畫原理分析

最近在Android上做了一些動畫效果,網上查了一些資料,有各種各樣的使用方式,於是乘熱打鐵,想具體分析一下動畫是如何實現的,Animation, Animator都有哪些區別等等。

首先說Animationandroid.view.animation.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左右,如果主線程卡了,這個間隔就變長了。

 

爲了找到答案,我在Stack Overflow上發帖問了下,然後得到一個情報,那就是讓我去看看Choreographer(android.view.Choreographer)類。

 

1.Choreographer的構造方法

看了下Choreographer類的構造方法,是private的,不允許new外部類new,於是又發現了它有一個靜態的getInstance()方法,那麼,我需要找到getInstance()方法被誰調用了,就可以知道Choreographer對象在什麼地方被使用。一查,發現Choreographer.getInstance()在ViewRootImpl的構造方法中被調用。以下代碼是ViewRootImpl.ViewRootImpl(Context, Display)的片段。

OK,找到了ViewRootImpl中擁有一個mChoreographer對象,接下來,我需要去找,它如何被使用了,調用了它的哪些方法。於是發現如下代碼:

scheduleTraversals()方法中,發現了這個對象的使用。

 

2.Choreographer.postCallback(int, Runnable, Object)

該方法輾轉調用了兩個內部方法,最終是調用了Choreographer.postCallbackDelayedInternal()方法。

 

3.Choreographer.postCallbackDelayedInternal(int, Object, Object, long)

這個方法中,

1.首先拿到當前的時間。

這裏參數中有一個delay,它的值可以具體查看一下,你會發現它就是一個靜態常量,定義在Choreographer類中,它的值是10ms。也就是說,理想情況下,所有的時間都是以100FPS來運行的。

2.將要執行的內容加入到一個mCallbackQueues中。

3.然後執行scheduleFrameLocked()或者發送一個Message。

 

接着我們看Choreographer.scheduleFrameLocked(long)方法

 

4.Choreographer.scheduleFrameLocked(long)

if判斷進去的部分是是否使用垂直同步,暫時不考慮。

else進去的部分,還是將消息發送到mHandler對象中。那我們就直接來看mHandler對象就好了

 

5.Choreographer.FrameHandler

mHandler實例的類型是FrameHandler,它的定義就在Choreographer類中,代碼如下:

它的處理方法中有三個分支,但最終都會調用這個doFrame()方法。

 

6.Choreographer.doFrame()

doFrame()方法巴拉巴拉一大段,但在下面有非常工整的一段代碼,一下就吸引了我的眼球。

它調用了三次doCallbacks()方法,暫且不說這個方法是幹什麼的,但從它的第一個參數可以看到分別是輸入(INPUT),動畫(ANIMATION),遍歷(TRAVERSAL)

於是,我先是看了下這三個常量的意義。下圖所示:

顯然,註釋是說:輸入事件最先處理,然後處理動畫,最後才處理view的佈局和繪製。接下來我們看看Choreographer.doCallbacks()裏面做了什麼。

 

7.Choreographer.doCallbacks(int, long)

這個方法的操作非常統一,有三種不同類型的操作(輸入,動畫,遍歷),但在這裏卻看不見這些具體事件的痕跡,這裏我們不得不分析一下mCallbackQueues這個成員變量了。

mCallbackQueues是一個CallbackQueue對象數組。而它的下標,其意義並不是指元素1,元素2,元素3……而是指類型,請看上面doCallbacks()的代碼,參數callbackType傳給了mCallbackQueues[callbackType]中,而callbackType是什麼呢?

其實就是前面說到的三個常量,CALLBACK_INPUTCALLBACK_ANIMATIONCALLBACK_TRAVERVAL

那麼只需要根據不同的callbackType,就可以從這個數組裏面取出不同類型的CallbackQueue對象來。

 

那麼CallbackQueue又是什麼呢?

CallbackQueueChoreographer的一個內部類,其中我認爲有兩個很重要的方法,分別是:extractDueCallbacksLocked(long)addCallbackLocked(long, Object, Object)

先說addCallbackLocked(long, Object, Object)

1.CallbackQueue.addCallbackLocked(long, Object, Object)

首先它通過一個內部方法構建了一個CallbackRecord對象,然後後面的if判斷和while循環,大致上是將參數中的對象鏈接在CallbackRecord的尾部。其實CallbackRecord就是一個鏈表結構的對象。

 

2.CallbackQueue.extractDueCallbacksLocked(long)

這個方法是根據當前的時間,選出執行鏈表中與該時間最近的一個操作來處理,實際上,我們可以通俗的理解爲“跳幀”。

想象一下,如果主線程運行的非常快速,非常流暢,每一步都能在10ms內準時運行到,那麼我們的執行鏈表中的元素始終只有一個。

如果主線程中做了耗時操作,那麼各種事件一直在往各自的鏈表中添加,但是當主線程有空來執行的時候,發現鏈表已經那麼多積累的過期的事件了,那麼就直接選擇最後一個來執行,那麼界面上看起來,就是卡頓了一下。

 

到這裏爲止,我們得出以下結論:

1.控制外部輸入事件處理,動畫執行,UI變化都是在同一個類中做的處理,即是Choreographer,其中它規定的了理想的運行間隔爲10ms,因爲各種操作需要花費一定的時間,所以外部執行的間隔統計出來是大約16ms。

2.在Choreographer對象中有三條鏈表,分別保存着待處理的輸入事件,待處理的動畫事件,待處理的遍歷事件。

3.每次執行的時候,Choreographer會根據當前的時間,只處理事件鏈表中最後一個事件,當有耗時操作在主線程時,事件不能及時執行,就會出現所謂的“跳幀”,“卡頓”現象。

4.Choreographer的共有方法postCallback(callbackType, Object)是往事件鏈表中放事件的方法。而doFrame()是消耗這些事件的方法。

 

事到如今,已經探究出不少有用的細節。這裏,又給自己提出一個問題,根據以上的事實,那麼,只需要找到哪些東西再往這三條鏈表中放事件呢?

於是進一步探究一下。我們只需要找到postCallback()被哪些方法調用了即可。

於是請點擊這裏,通過grepcode列舉了調用postCallback()的方法。

 

搞明白了Choreographer的工作原理,再去看ObjectAnimator,ValueAnimator的實現,就非常的輕鬆了。

ObjectAnimator.start()方法實際上是輾轉幾次調用了ValueAnimator的start()方法,ValueAnimator.start()又調用了一個臨時變量animationHandler.start()。

animationHandler實際上是一個Runnable,其中start()方法調用了scheduleAnimation()。

而這個方法:

調用了postCallback()方法。

將this(Runnable)post之後,實際上肯定就是要執行Runnable.run()方法

run()方法中又調用了doAnimationFrame()方法。這個方法具體的實現了動畫的某一幀的過程,然後再次調用了scheduleAnimation()方法。

就相當於postDelayed(this, 16)這種方式了。

 

到這裏爲止,對Animation原理的分析就到此結束了,本來只想分析下Animation的實現過程,沒想到順帶研究了一下Choreographer的工作原理,今天收穫還是不少。

其實還有好多疑問,技術學習也一天急不得,靠的是每日慢慢的積累,相信總有一天,各種疑惑都會迎刃而解。

發佈了39 篇原創文章 · 獲贊 3 · 訪問量 6萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章