閱讀309 返回首頁    go 阿裏雲 go 技術社區[雲棲]


Android Project Butter分析


一背景知識介紹

隨著時間的推移,Android OS係統一直在不斷進化、壯大,日趨完善。但直到Android 4.0問世,有關UI顯示不流暢的問題也一直未得到根本解決。在整個進化過程中,AndroidDisplay(顯示)係統這塊也下了不少功夫,例如,使用硬件加速等技術,但本質原因似乎和硬件關係並不大,因為iPhone的硬件配置並不比那些價格相近的Android機器的硬件配置強,而iPhone UI的流暢性強卻是有目共睹的。

Android 4.1(版本代號為Jelly Bean)開始,Android OS開發團隊便力圖在每個版本中解決一個重要問題(這是不是也意味著Android OS在經過幾輪大規模改善後,開始進入手術刀式的精加工階段呢?)。作為嚴重影響Android口碑問題之一的UI流暢性差的問題,首先在Android 4.1版本中得到了有效處理。其解決方法就是本文要介紹的Project Butter

Project ButterAndroid Display係統進行了重構,引入了三個核心元素,即VSYNCTriple BufferChoreographer。其中,VSYNC是理解Project Buffer的核心。VSYNCVertical Synchronization(垂直同步)的縮寫,是一種在PC上已經很早就廣泛使用的技術。讀者可簡單的把它認為是一種定時中斷。

接下來,本文將圍繞VSYNC來介紹Android Display係統的工作方式[①]。請注意,後續討論將以Display為基準,將其劃分成16ms長度的時間段,在每一時間段中,Display顯示一幀數據(相當於每秒60幀)。時間段從1開始編號。

首先是沒有VSYNC的情況,如圖1所示:

 

沒有VSYNC的繪圖過程

由圖1可知:

  •  時間從0開始,進入第一個16msDisplay顯示第0幀,CPU處理完第一幀後,GPU緊接其後處理繼續第一幀。三者互不幹擾,一切正常。
  • 時間進入第二個16ms:因為早在上一個16ms時間內,第1幀已經由CPUGPU處理完畢。故Display可以直接顯示第1幀。顯示沒有問題。但在本16ms期間,CPUGPU卻並未及時去繪製第2幀數據(注意前麵的空白區),而是在本周期快結束時,CPU/GPU才去處理第2幀數據。
  • 時間進入第316ms,此時Display應該顯示第2幀數據,但由於CPUGPU還沒有處理完第2幀數據,故Display隻能繼續顯示第一幀的數據,結果使得第1幀多畫了一次(對應時間段上標注了一個Jank)。
  • 通過上述分析可知,此處發生Jank的關鍵問題在於,為何第116ms段內,CPU/GPU沒有及時處理第2幀數據?原因很簡單,CPU可能是在忙別的事情(比如某個應用通過sleep固定時間來實現動畫的逐幀顯示),不知道該到處理UI繪製的時間了。可CPU一旦想起來要去處理第2幀數據,時間又錯過了!

為解決這個問題,Project Buffer引入了VSYNC,這類似於時鍾中斷。結果如圖2所示:

引入VSYNC的繪製過程

由圖2可知,每收到VSYNC中斷,CPU就開始處理各幀數據。整個過程非常完美。

不過,仔細琢磨圖2卻會發現一個新問題:圖2中,CPUGPU處理數據的速度似乎都能在16ms內完成,而且還有時間空餘,也就是說,CPU/GPUFPS(幀率,Frames Per Second)要高於DisplayFPS。確實如此。由於CPU/GPU隻在收到VSYNC時才開始數據處理,故它們的FPS被拉低到與DisplayFPS相同。但這種處理並沒有什麼問題,因為Android設備的Display FPS一般是60,其對應的顯示效果非常平滑。

如果CPU/GPUFPS小於DisplayFPS,會是什麼情況呢?請看圖3

3  CPU/GPU FPS較小的情況

由圖3可知:

  • 在第二個16ms時間段,Display本應顯示B幀,但卻因為GPU還在處理B幀,導致A幀被重複顯示。
  • 同理,在第二個16ms時間段內,CPU無所事事,因為A BufferDisplay在使用。B BufferGPU在使用。注意,一旦過了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.1  SurfaceFlinger家族的改進

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

5  SurfaceFlinger中和VSYNC有關的類

由圖5可知:

  • HardwareComposer封裝了相關的HAL層,如果硬件廠商提供的HAL層實現能定時產生VSYNC中斷,則直接使用硬件的VSYNC中斷,否則HardwareComposer內部會通過VSyncThread來模擬產生VSYNC中斷(其實現很簡單,就是sleep固定時間,然後喚醒)。
  • VSYNC中斷產生時(不管是硬件產生還是VSyncThread模擬的),VSyncHandleronVSyncReceived函數將被調用。所以,對VSYNC中斷來說,VSyncHandleronVSyncReceived,就是其中斷處理函數。

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

class EventThread : public Thread, public DisplayHardware::VSyncHandler

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

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

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

6  EventThreadVSYNC中斷監聽者

由圖6可知:

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

EventThread最重要的一個VSYNC監聽者就是MessageQueuemEvents對象。當然,這一切都是為最大的後台老板SurfaceFlinger服務的。來自EventThreadVSYNC中斷信號,將通過MessageQueue轉化為一個REFRESH消息並傳遞給SurfaceFlingeronMessageReceived函數處理。

有必要指出,4.1SurfaceFlinger onMessageReceived函數的實現僅僅是將4.0版本的SurfaceFlinger的核心函數挪過來罷了[②],並未做什麼改動。

以上是Project BufferSurfaceFlinger所做的一些改動。那麼Triple Buffer是怎麼處理的呢?幸好從Android 2.2開始,DisplayPage Flip算法就不依賴Buffer的個數,Buffer個數不過是算法的一個參數罷了。所以,Triple Buffer的引入,隻是把Buffer的數目改成了3,而算法本身相對於4.0來說並沒有變化。圖7Triple Buffer的設置示意圖:

7  Layer.cpp中對Triple Buffer的設置

7所示,為Layer.cpp中對Buffer個數的設置。TARGET_DISABLE_TRIPLE_BUFFERING宏可設置Buffer的個數。對某些內存/顯存並不是很大的設備,也可以選擇不使用Triple Buffer

2.2  Choreographer介紹

Choreographer是一個Java類。第一次看到這個詞時,我很激動。一個小小的命名真的反應出了設計者除coding之外的廣博的視界。試想,如果不是對舞蹈有相當了解或喜愛,一般人很難想到用這個詞來描述它。

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

8  Choreographer的定義和結構

8中:

  • Choreographer是線程單例的,而且必須要和一個Looper綁定,因為其內部有一個Handler需要和Looper綁定。
  • DisplayEventReceiver是一個abstract class,其JNI的代碼部分會創建一個IDisplayEventConnectionVSYNC監聽者對象。這樣,來自EventThreadVSYNC中斷信號就可以傳遞給Choreographer對象了。由圖8可知,當VSYNC信號到來時,DisplayEventReceiveronVsync函數將被調用。
  • 另外,DisplayEventReceiver還有一個scheduleVsync函數。當應用需要繪製UI時,將首先申請一次VSYNC中斷,然後再在中斷處理的onVsync函數去進行繪製。
  • Choreographer定義了一個FrameCallback interface,每當VSYNC到來時,其doFrame函數將被調用。這個接口對Android Animation的實現起了很大的幫助作用。以前都是自己控製時間,現在終於有了固定的時間中斷。
  • Choreographer的主要功能是,當收到VSYNC信號時,去調用使用者通過postCallback設置的回調函數。目前一共定義了三種類型的回調,它們分別是:
  • CALLBACK_INPUT:優先級最高,和輸入事件處理有關。
  • CALLBACK_ANIMATION:優先級其次,和Animation的處理有關。
  • CALLBACK_TRAVERSAL:優先級最低,和UI等控件繪製有關。

優先級高低和處理順序有關。當收到VSYNC中斷時,Choreographer將首先處理INPUT類型的回調,然後是ANIMATION類型,最後才是TRAVERSAL類型。

此外,讀者在自行閱讀Choreographer相關代碼時,還會發現AndroidMessage/Looper[③]也進行了一番小改造,使之支持Asynchronous MessageSynchronization Barrier(參照Looper.javapostSyncBarrier函數)。其實現非常巧妙,這部分內容就留給讀者自己理解並欣賞了。

相比SurfaceFlingerChoreographerAndroid 4.1中的新事物,下麵將通過一個實例來簡單介紹Choreographer的工作原理。

假如UI中有一個控件invalidate了,那麼它將觸發ViewRootImplinvalidate函數,該函數將最終調用ViewRootImplscheduleTraversals。其代碼如圖9所示:

9  ViewRootImpl scheduleTraversals函數的實現

由圖9可知,scheduleTraversals首先禁止了後續的消息處理功能,這是由設置LooperpostSyncBarrier來完成的。一旦設置了SyncBarrier,所有非Asynchronous的消息便將停止派發。

然後,為Choreographer設置了CALLBACK類型為TRAVERSAL的處理對象,即mTraversalRunnable

最後調用scheduleConsumeBatchedInput,這個函數將為Choreographer設置了CALLBACK類型為INPUT的處理對象。

ChoreographerpostCallback函數將會申請一次VSYNC中斷(通過調用DisplayEventReceiverscheduleVsync實現)。當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了。

 

最後更新:2017-04-03 12:56:09

  上一篇:go 網絡訪問優化下載
  下一篇:go 網絡訪問優化下載