Android 顯示原理簡介

轉自:http://djt.qq.com/article/view/987

作者:yearzhu,2011年進入騰訊公司,從事過Web端及移動端的測試工作,喜愛新鮮事物及新技術,目前在SNG開放平臺測試組負責的移動互聯SDK的測試工作。

 

現在越來越多的應用開始重視流暢度方面的測試,瞭解Android應用程序是如何在屏幕上顯示的則是基礎中的基礎,就讓我們一起看看小小屏幕中大大的學問。這也是我下篇文章——《Android應用流暢度測試分析》的基礎。

 

    首先,用一句話來概括一下Android應用程序顯示的過程:Android應用程序調用SurfaceFlinger服務把經過測量、佈局和繪製後的Surface渲染到顯示屏幕上。

 

名詞解釋

SurfaceFlinger:Android系統服務,負責管理Android系統的幀緩衝區,即顯示屏幕。

Surface:Android應用的每個窗口對應一個畫布(Canvas),即Surface,可以理解爲Android應用程序的一個窗口。

 

    Android應用程序的顯示過程包含了兩個部分(應用側繪製、系統側渲染)、兩個機制(進程間通訊機制、顯示刷新機制),接下來我們就來一一道來。

 

應用側

一個Android應用程序窗口裏麪包含了很多UI元素,這些UI元素是以樹形結構來組織的,即它們存在着父子關係,其中,子UI元素位於父UI元素裏面,如下圖:

 

 

因此,在繪製一個Android應用程序窗口的UI之前,我們首先要確定它裏面的各個子UI元素在父UI元素裏面的大小以及位置。確定各個子UI元素在父UI元素裏面的大小以及位置的過程又稱爲測量過程和佈局過程。因此,Android應用程序窗口的UI渲染過程可以分爲測量佈局繪製三個階段。如下圖所示:

測量:遞歸(深度優先)確定所有視圖的大小(高、寬)

    佈局:遞歸(深度優先)確定所有視圖的位置(左上角座標)

    繪製:在畫布canvas上繪製應用程序窗口所有的視圖

 

測量、佈局沒有太多要說的,這裏要着重說一下繪製。Android目前有兩種繪製模型:基於軟件的繪製模型和硬件加速的繪製模型(從Android 3.0開始全面支持)。

 

    在基於軟件的繪製模型下,CPU主導繪圖,視圖按照兩個步驟繪製:

1.      讓View層次結構失效

2.      繪製View層次結構

    當應用程序需要更新它的部分UI時,都會調用內容發生改變的View對象的invalidate()方法。無效(invalidation)消息請求會在View對象層次結構中傳遞,以便計算出需要重繪的屏幕區域(髒區)。然後,Android系統會在View層次結構中繪製所有的跟髒區相交的區域。不幸的是,這種方法有兩個缺點:

1.      繪製了不需要重繪的視圖(與髒區域相交的區域)

2.      掩蓋了一些應用的bug(由於會重繪與髒區域相交的區域)

    注意:在View對象的屬性發生變化時,如背景色或TextView對象中的文本等,Android系統會自動的調用該View對象的invalidate()方法。

 

    在基於硬件加速的繪製模式下,GPU主導繪圖,繪製按照三個步驟繪製:

1.      讓View層次結構失效

2.      記錄、更新顯示列表

3.      繪製顯示列表

這種模式下,Android系統依然會使用invalidate()方法和draw()方法來請求屏幕更新和展現View對象。但Android系統並不是立即執行繪製命令,而是首先把這些View的繪製函數作爲繪製指令記錄一個顯示列表中,然後再讀取顯示列表中的繪製指令調用OpenGL相關函數完成實際繪製。另一個優化是,Android系統只需要針對由invalidate()方法調用所標記的View對象的髒區進行記錄和更新顯示列表。沒有失效的View對象則能重放先前顯示列表記錄的繪製指令來進行簡單的重繪工作。

使用顯示列表的目的是,把視圖的各種繪製函數翻譯成繪製指令保存起來,對於沒有發生改變的視圖把原先保存的操作指令重新讀取出來重放一次就可以了,提高了視圖的顯示速度。而對於需要重繪的View,則更新顯示列表,以便下次重用,然後再調用OpenGL完成繪製。

硬件加速提高了Android系統顯示和刷新的速度,但它也不是萬能的,它有三個缺陷:

1.      兼容性(部分繪製函數不支持或不完全硬件加速,參見文章尾)

2.      內存消耗(OpenGL API調用就會佔用8MB,而實際上會佔用更多內存)

3.      電量消耗(GPU耗電)

 

系統側

    Android應用程序在圖形緩衝區中繪製好View層次結構後,這個圖形緩衝區會被交給SurfaceFlinger服務,而SurfaceFlinger服務再使用OpenGL圖形庫API來將這個圖形緩衝區渲染到硬件幀緩衝區中。

 

    由於Android應用程序很少能涉及到Android系統底層,所以SurfaceFlinger服務的執行過程不做過多的介紹。

 

進程間通訊機制

    Android應用程序爲了能夠將自己的UI繪製在系統的幀緩衝區上,它們就必須要與SurfaceFlinger服務進行通信,如圖所示:

Android應用程序與SurfaceFlinger服務是運行在不同的進程中的,因此,它們採用某種進程間通信機制來進行通信。由於Android應用程序在通知SurfaceFlinger服務來繪製自己的UI的時候,需要將UI數據傳遞給SurfaceFlinger服務,例如,要繪製UI的區域、位置等信息。一個Android應用程序可能會有很多個窗口,而每一個窗口都有自己的UI數據,因此,Android系統的匿名共享內存機制就派上用場了。

每一個Android應用程序與SurfaceFlinger服務之間,都會通過一塊匿名共享內存來傳遞UI數據,如下所示:

 

 

但是單純的匿名共享內存在傳遞多個窗口數據時缺乏有效的管理,所以匿名共享內存就被抽象爲一個更上流的數據結構SharedClient,如下圖所示:

 

在每個SharedClient中,最多有31個SharedBufferStack,每個SharedBufferStack都對應一個Surface,即一個窗口。這樣,我們就可以知道爲什麼每一個SharedClient裏面包含的是一系列SharedBufferStack而不是單個SharedBufferStack:一個SharedClient對應一個Android應用程序,而一個Android應用程序可能包含有多個窗口,即Surface。從這裏也可以看出,一個Android應用程序至多可以包含31個窗口。

每個SharedBufferStack中又包含了N個緩衝區(<4.1 N=2; >=4.1 N=3),即顯示刷新機制中即將提到的雙緩衝和三重緩衝技術。

顯示刷新機制

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

 

 

但現實情況並非這麼理想。

1.      時間從0開始,進入第一個16ms:Display顯示第0幀,CPU處理完第一幀後,GPU緊接其後處理繼續第一幀。三者互不干擾,一切正常。

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

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

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

 

    爲解決這個問題,Android 4.1中引入了VSYNC,這類似於時鐘中斷。結果如下圖所示:

 

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

    不過,仔細琢磨後卻會發現一個新問題:上圖中,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,會是什麼情況呢?請看下圖:

 

由上圖可知:

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就能直接使用它,而不至於空閒。出於這一思路就引出了三重緩衝區(Android 4.1)。結果如下圖所示:

由上圖可知:

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

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

到這裏,Android系統的顯示原理就介紹完了。那麼在瞭解這些原理後對我們的流暢度測試有哪些幫助呢,請看我的下篇文章《Android應用流暢度測試分析》。

 

 

附:不同的API Level下,繪製函數對硬件加速模式的支持情況

 

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