Android繪製優化:系統顯示原理

作者:privatego

鏈接:https://zhuanlan.zhihu.com/p/27344882

來源:知乎

Android的顯示過程可以概括爲:

Android應用程序把經過測量、佈局、繪製後的surface緩存數據,通過SurfaceFlinger把數據渲染到屏幕上,通過Android的刷新機制來刷新數據。即應用層負責繪製,系統層負責渲染,通過進程間通信把應用層需要繪製的數據傳遞到系統層服務,系統層服務通過顯示刷新機制把數據更新到屏幕。

接下來分別從 應用層、系統層和刷新機制三個方面來介紹下Android系統的顯示原理。

應用層

我們都知道一個Android的UI界面layout是整體一棵由很多不同層次的View組成的樹形結構,它們存在着父子關係,子View在父View中,這些View都經過一個相同的流程最終顯示到屏幕上。

在Android中每個View的繪製中有三個核心步驟,通過Measure和Layout來確定當前需要繪製的View所在的大小和位置,通過繪製(Draw)到surface。

1)Measure

用深度優先原則遞歸得到所有View的寬、高;獲取當前View的正確寬度childWidthMeasureSpec和childHeightMeasureSpec之後,可以調用它的成員函數Measure來設置它的大小。若子View是一個ViewGroup,那麼它又會重複執行操作,直到它的所在子孫View的大小都測量完成爲止。

2)Layout

用深度優先原則遞歸得到所有View的位置;當一個子View在應用程序窗口左上角的位置確定之後,再結合它在前面測量過程中確定的寬度和高度,就可以完全確定它在應用程序窗口中的佈局。


3)Draw

Android支持兩種繪製方式:軟件繪製(CPU)和硬件繪製(GPU,要求>= Android3.0)。硬件加速在UI的顯示和繪製的效率遠遠高於CPU繪製,但硬件也有明顯缺點:

  • 耗電:GPU功耗比CPU高;
  • 兼容問題:某些接口和函數不支持硬件加速;
  • 內存大:使用OpenGL的接口至少需要8MB內存。

系統層

Android是通過系統級進程中的SurfaceFlinger服務來把真正需要顯示的數據渲染到屏幕上。SurfaceFlinger的主要工作是:

  • 響應客戶端事件,創建Layer與客戶端的Surface建立連接。
  • 接收客戶端數據及屬性,修改Layer屬性,如尺寸、顏色、透明度等。
  • 將創建的Layer內容刷新到屏幕上。
  • 維持Layer的序列,並對Layer最終輸出做出裁剪計算。


因應用層和系統層分別是兩個不同進程,需要一個跨進程的通信機制來實現數據傳輸,在Android的顯示系統中,使用了Android的匿名共享內存:SharedClient。每一個應用和SurfaceFlinger之間都會創建一個SharedClient,每個SharedClient中,最多可以創建31個SharedBufferStack,每個Surface都對應一個SharedBufferStack,也就是一個window。這意味着一個Android應用程序最多可以包含31個窗口,同時每個SharedBufferStack中又包含兩個(<4.1)或三個(>=4.1)緩衝區。


總結:應用層繪製到緩衝區,SurfaceFlinger把緩存區數據渲染到屏幕,兩個進程之間使用Android的匿名共享內存SharedClient緩存需要顯示的數據。

顯示刷新機制

Android系統一直在不斷的優化、更新,但直到4.0版本發佈,有關UI顯示不流暢的問題仍未得到根本解決。

從Android4.1版本開始,Android對顯示系統進行了重構,引入了三個核心元素:VSYNC, Tripple Buffer和Choreographer。VSYNC是Vertical Synchronized的縮寫,是一種定時中斷;Tripple Buffer是顯示數據的緩衝區;Choreographer起調度作用,將繪製工作統一到VSYNC的某個時間點上,使應用的繪製工作有序進行。

Android在繪製UI時,會採用一種稱爲“雙緩衝”的技術,雙緩衝即使用兩個緩衝區(在SharedBufferStack中),其中一個稱爲Front Buffer,另外一個稱爲Back Buffer。UI總是先在Back Buffer中繪製,然後再和Front Buffer交換,渲染到顯示設備中。理想情況下,一個刷新會在16ms內完成(60FPS),下圖就是描述的這樣一個刷新過程(Display處理前Front Buffer,CPU、GPU處理Back Buffer。

1.沒有VSYNC信號同步時


但實際運行時情況並不一定如此

1)第一個16ms開始:Display顯示第0幀,CPU處理完第一幀後,GPU緊接其後處理繼續第一幀。三者都在正常工作。

2)進入第二個16ms:因爲早在上一個16ms時間內,第1幀已經由CPU,GPU處理完畢。故Display可以直接顯示第1幀。顯示沒有問題。但在本16ms期間,CPU和GPU卻並未及時去繪製第2幀數據(前面的空白區表示CPU和GPU忙其它的事),直到在本週期快結束時,CPU/GPU纔去處理第2幀數據。

3)進入第三個16ms,此時Display應該顯示第2幀數據,但由於CPU和GPU還沒有處理完第2幀數據,故Display只能繼續顯示第一幀的數據,結果使得第1幀多畫了一次(對應時間段上標註了一個Jank),導致錯過了顯示第二幀。


通過上述分析可知,此處發生Jank的關鍵問題在於,爲何第1個16ms段內,CPU/GPU沒有及時處理第2幀數據?原因很簡單,CPU可能是在忙別的事情,不知道該到處理UI繪製的時間了。可CPU一旦想起來要去處理第2幀數據,時間又錯過了。 爲解決這個問題,Android 4.1中引入了VSYNC,核心目的是解決刷新不同步的問題。

2.引入VSYNC信號同步後


在加入VSYNC信號同步後,每收到VSYNC中斷,CPU就開始處理各幀數據。已經解決了刷新不同步的問題。

但是上圖中仍然存在一個問題:CPU和GPU處理數據的速度似乎都能在16ms內完成,而且還有時間空餘,也就是說,CPU/GPU的FPS(幀率)要高於Display的FPS。由於CPU/GPU只在收到VSYNC時纔開始數據處理,故它們的FPS被拉低到與Display的FPS相同。但這種處理並沒有什麼問題,因爲Android設備的Display FPS一般是60,其對應的顯示效果非常平滑。

但如果CPU/GPU的FPS小於Display的FPS,情況又不同了,將會發生如下圖的情況:


1)在第二個16ms時間段,Display本應顯示B幀,但卻因爲GPU還在處理B幀,導致A幀被重複顯示。

2)同理,在第二個16ms時間段內,CPU無所事事,因爲A Buffer被Display在使用。B Buffer被GPU在使用。注意,一旦過了VSYNC時間點,CPU就不能被觸發以處理繪製工作了。

爲什麼CPU不能在第二個16ms處開始繪製工作呢?原因就是隻有兩個Buffer(Android 4.1之前)。如果有第三個Buffer的存在,CPU就能直接使用它,而不至於空閒。於是在Android4.1以後,引出了第三個緩衝區:Tripple Buffer。Tripple Buffer利用CPU/GPU的空閒等待時間提前準備好數據,並不一定會使用。

3.引入Tripple Buffer後

引入Tripple Buffer後的刷新時序如下圖:


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

是不是Buffer越多越好呢?回答是否定的。由上圖可知,在第二個時間段內,CPU繪製的第C幀數據要到第四個16ms才能顯示,這比雙Buffer情況多了16ms延遲。所以緩衝區並不是越多越好。

總結

從Android系統的顯示原理可以看到,影響繪製的根本原因有以下兩方面:

  • 1.繪製任務太重,繪製一幀內容耗時太長。
  • 2.主線程太忙了,導致VSYNC信號來時還沒有準備好數據導致丟幀。

實際開發中,我們應該只在主線程做以下幾方面的工作:

  • UI生命週期控制
  • 系統事件處理
  • 消息處理
  • 界面佈局
  • 界面繪製
  • 界面刷新

除了這些以外,儘量避免將其它處理放到主線程中,特別是複雜的數據計算和網絡請求。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章