Android顯示原理

3.2 Android顯示原理

Android應(yīng)用程序的顯示過程包含了兩個部分(應(yīng)用側(cè)繪制、系統(tǒng)側(cè)渲染)、兩個機制(進程間通訊機制、顯示刷新機制)。

Android的Gui系統(tǒng)是CS模型的窗口系統(tǒng),即后臺運行一個繪制服務(wù)(SurfaceFlinger),當(dāng)客戶端程序需要繪制屏幕時,首先請求服務(wù)端創(chuàng)建一個窗口,然后在該窗口繪制內(nèi)容。對于每個客戶端而言,它們都感覺自己獨占了屏幕;對于服務(wù)端而言,它會給每個客戶窗口分配一個不同的層值,并根據(jù)用戶交互情況動態(tài)改變窗口的層值。

簡單概括Android應(yīng)用程序顯示的過程:Android應(yīng)用程序調(diào)用SurfaceFlinger服務(wù)把經(jīng)過測量、布局和繪制后的Surface渲染到顯示屏幕上。

繪制相關(guān)類

  • SurfaceFlinger:Android系統(tǒng)服務(wù),負責(zé)管理Android系統(tǒng)的幀緩沖區(qū),即顯示屏幕。
  • Surface:該類用于描述一個繪制平面,其內(nèi)部包含了該平面的大小,在屏幕上的位置以及一段屏幕緩沖區(qū)內(nèi)存。
  • Canvas:該類是一個功能類,包含各種繪制函數(shù),比如drawColor()、drawText()等。構(gòu)造Canvas對象時,必須為其指定一段內(nèi)存地址,繪制結(jié)果就是給這段內(nèi)存填充不同的像素值。這段內(nèi)存有兩種類型,普通內(nèi)存和屏幕緩沖區(qū)內(nèi)存,后者繪制結(jié)果會呈現(xiàn)在屏幕上。

3.2.1 應(yīng)用側(cè)

一個Android應(yīng)用程序窗口里面包含了很多UI元素,這些UI元素是以樹形結(jié)構(gòu)來組織的,即它們存在著父子關(guān)系,其中,子UI元素位于父UI元素里面,如下圖:

Android應(yīng)用程序窗口的UI渲染過程可以分為measure、layout和draw三個階段:

  • measure:遞歸(深度優(yōu)先)確定所有視圖的大小(高、寬);
  • layout:遞歸(深度優(yōu)先)確定所有視圖的位置(左上角坐標(biāo));
  • draw:在畫布canvas上繪制應(yīng)用程序窗口所有的視圖。

這里簡單說一下draw。Android目前有兩種繪制模型:基于軟件的繪制模型和硬件加速的繪制模型。

基于軟件的繪制模型

在基于軟件的繪制模型下,CPU主導(dǎo)繪圖,視圖按照兩個步驟繪制:首先讓View層次結(jié)構(gòu)失效;然后繪制View層次結(jié)構(gòu)

當(dāng)應(yīng)用程序需要更新它的部分UI時,都會調(diào)用內(nèi)容發(fā)生改變的View對象的invalidate()方法。invalidation請求會在View對象層次結(jié)構(gòu)中傳遞,以便計算出需要重繪的屏幕區(qū)域(臟區(qū))。然后,Android系統(tǒng)會在View層次結(jié)構(gòu)中繪制所有的跟臟區(qū)相交的區(qū)域。不幸的是,這種方法有兩個缺點:繪制了不需要重繪的視圖(與臟區(qū)域相交的區(qū)域)。

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

基于硬件的繪制模型

在基于硬件加速的繪制模式下,GPU主導(dǎo)繪圖,繪制按照三個步驟繪制:讓View層次結(jié)構(gòu)失效;記錄、更新顯示列表;繪制顯示列表

這種模式下,Android系統(tǒng)依然會使用invalidate()方法和draw()方法來請求屏幕更新和展現(xiàn)View對象。但Android系統(tǒng)并不是立即執(zhí)行繪制命令,而是首先把這些View的繪制函數(shù)作為繪制指令記錄一個顯示列表中,然后再讀取顯示列表中的繪制指令調(diào)用OpenGL相關(guān)函數(shù)完成實際繪制。另一個優(yōu)化是,Android系統(tǒng)只需要針對由invalidate()方法調(diào)用所標(biāo)記的View對象的臟區(qū)進行記錄和更新顯示列表。沒有失效的View對象則能重放先前顯示列表記錄的繪制指令來進行簡單的重繪工作。使用顯示列表的目的是,把視圖的各種繪制函數(shù)翻譯成繪制指令保存起來,對于沒有發(fā)生改變的視圖把原先保存的操作指令重新讀取出來重放一次就可以了,提高了視圖的顯示速度。而對于需要重繪的View,則更新顯示列表,以便下次重用,然后再調(diào)用OpenGL完成繪制。

硬件加速提高了Android系統(tǒng)顯示和刷新的速度,它的缺點是:內(nèi)存消耗和電量消耗。

3.2.2 系統(tǒng)側(cè)

SurfaceFlinger服務(wù)負責(zé)管理Android系統(tǒng)的幀緩沖區(qū),Android設(shè)備的屏幕就被當(dāng)做是一個幀緩沖區(qū),而SurfaceFlinger服務(wù)繪制界面就是通過向幀緩沖區(qū)輸出內(nèi)容來完成界面繪制的。SurfaceFlinger服務(wù)接收到Android應(yīng)用程序窗口請求,然后創(chuàng)建了一個繪圖表面,接著為該繪圖表面創(chuàng)建圖形緩沖區(qū),接下來應(yīng)用程序就會往這些準(zhǔn)備好的圖形緩沖區(qū)填充自己的UI數(shù)據(jù),當(dāng)填充完成之后,它就可以向SurfaceFlinger服務(wù)發(fā)送請求,請求將UI數(shù)據(jù)渲染到硬件幀緩沖區(qū)中去,最后的結(jié)果就是我們在屏幕上看到了應(yīng)用程序窗口的UI。

3.2.3 進程間通訊機制

Android應(yīng)用程序為了能夠?qū)⒆约旱腢I繪制在系統(tǒng)的幀緩沖區(qū)上,它們就必須要與SurfaceFlinger服務(wù)進行通信,如圖所示:

通過上圖我們可以看到,SurfaceFlinger服務(wù)和Android應(yīng)用程序是一對一服務(wù)的,它們之間有一個連接,而這個連接是Binder對象來完成的。當(dāng)應(yīng)用程序與SurfaceFlinger服務(wù)建立連接的時候,Client對象被SurfaceFlinger服務(wù)創(chuàng)建的 ,當(dāng)它們連接成功之后,Android應(yīng)用程序就可以獲得一個對應(yīng)的Client對象的Binder接口,Android應(yīng)用程序就可以用其來繪制自己的窗口界面了。 要想讓SurfaceBinder來繪制自己的UI,Android應(yīng)用程序需要將UI數(shù)據(jù)傳遞給SurfaceFlinger,包括繪制的區(qū)域以及位置信息等等。一個應(yīng)用程序會有多個窗口,而每一個窗口都有自己的UI數(shù)據(jù)。Android系統(tǒng)提供了一種匿名共享內(nèi)存機制,它以驅(qū)動程序的形式實現(xiàn)在內(nèi)核空間中。它有兩個特點,一是作為內(nèi)存管理系統(tǒng)的輔助,來管理不再使用的內(nèi)存塊,二是它可以用來實現(xiàn)進程間的內(nèi)存共享(詳細的內(nèi)容不再概述)。每一個應(yīng)用程序與SurfaceFlinger服務(wù)會通過一塊匿名共享內(nèi)存來方便傳遞UI數(shù)據(jù),如下圖所示:

但是單純的匿名共享內(nèi)存在傳遞多個窗口數(shù)據(jù)時缺乏有效的管理,所以匿名共享內(nèi)存就被抽象為一個更高層的數(shù)據(jù)結(jié)構(gòu)SharedClient,如下圖所示:

在每個SharedClient中,最多有31個SharedBufferStack,每個SharedBufferStack都對應(yīng)一個Surface,即一個窗口。SharedBufferStack就是共享緩沖區(qū)堆棧,由Android應(yīng)用程序和SurfaceFlinger程序共享,每一個SharedBufferStack就是一個緩沖區(qū),只不過這個緩沖區(qū)需要按照一定規(guī)則進行訪問。一個SharedClient對應(yīng)一個Android應(yīng)用程序,而一個Android應(yīng)用程序可能包含有多個窗口,即Surface。從這里也可以看出,一個Android應(yīng)用程序至多可以包含31個窗口。
每個SharedBufferStack中又包含了N個緩沖區(qū)(<4.1 N=2; >=4.1 N=3),即顯示刷新機制中即將提到的雙緩沖和三重緩沖技術(shù)。

3.2.4 顯示刷新機制

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

但現(xiàn)實情況并非這么理想。

  1. 時間從0開始,進入第一個16ms:Display顯示第0幀,CPU處理完第一幀后,GPU緊接其后處理繼續(xù)第一幀。三者互不干擾,一切正常。
  2. 時間進入第二個16ms:因為早在上一個16ms時間內(nèi),第1幀已經(jīng)由CPU,GPU處理完畢。故Display可以直接顯示第1幀。顯示沒有問題。但在本16ms期間,CPU和GPU卻并未及時去繪制第2幀數(shù)據(jù)(注意前面的空白區(qū)),而是在本周期
    快結(jié)束時,CPU/GPU才去處理第2幀數(shù)據(jù)。
  3. 時間進入第3個16ms,此時Display應(yīng)該顯示第2幀數(shù)據(jù),但由于CPU和GPU還沒有處理完第2幀數(shù)據(jù),故Display只能繼續(xù)顯示第一幀的數(shù)據(jù),結(jié)果使得第1幀多畫了一次(對應(yīng)時間段上標(biāo)注了一個Jank)。
    通過上述分析可知,此處發(fā)生Jank的關(guān)鍵問題在于,在第1個16ms段內(nèi),CPU/GPU沒有及時處理第2幀數(shù)據(jù)。原因很簡單,CPU可能是在忙別的事情,不知道該到處理UI繪制的時間了。可CPU一旦想起來要去處理第2幀數(shù)據(jù),時間又錯過了!
    為解決這個問題,Android 4.1中引入了VSYNC,這類似于時鐘中斷。結(jié)果如下圖所示:

由上圖可知,每收到VSYNC中斷,CPU就開始處理各幀數(shù)據(jù)。整個過程非常完美。不過,仔細琢磨后卻會發(fā)現(xiàn)一個新問題:上圖中,CPU和GPU處理數(shù)據(jù)的速度似乎都能在16ms內(nèi)完成,而且還有時間空余,也就是說,CPU/GPU的FPS(幀率,F(xiàn)rames Per Second)要高于Display的FPS。確實如此。由于CPU/GPU只在收到VSYNC時才開始數(shù)據(jù)處理,故它們的FPS被拉低到與Display的FPS相同。但這種處理并沒有什么問題,因為Android設(shè)備的Display FPS一般是60,其對應(yīng)的顯示效果非常平滑。
如果CPU/GPU的FPS小于Display的FPS,會是什么情況呢?請看下圖:

由上圖可知:

  1. 在第二個16ms時間段,Display本應(yīng)顯示B幀,但卻因為GPU還在處理B幀,導(dǎo)致A幀被重復(fù)顯示。
  2. 同理,在第二個16ms時間段內(nèi),CPU無所事事,因為A Buffer被Display在使用。B Buffer被GPU在使用。注意,一旦過了VSYNC時間點,CPU就不能被觸發(fā)以處理繪制工作了。
    為什么CPU不能在第二個16ms處開始繪制工作呢?原因就是只有兩個Buffer(Android 4.1之前)。如果有第三個Buffer的存在,CPU就能直接使用它,而不至于空閑。出于這一思路就引出了三重緩沖區(qū)(Android 4.1)。結(jié)果如下圖所示:

由上圖可知:第二個16ms時間段,CPU使用C Buffer繪圖。雖然還是會多顯示A幀一次,但后續(xù)顯示就比較順暢了。
是不是Buffer越多越好呢?回答是否定的。由上圖可知,在第二個時間段內(nèi),CPU繪制的第C幀數(shù)據(jù)要到第四個16ms才能顯示,這比雙Buffer情況多了16ms延遲。所以,Buffer最好還是兩個,三個足矣。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • 作者:yearzhu,2011年進入騰訊公司,從事過Web端及移動端的測試工作,喜愛新鮮事物及新技術(shù),目前在SNG...
    Viking_Den閱讀 968評論 0 5
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 173,287評論 25 708
  • 本篇文章是基于谷歌有關(guān)Graphic的一篇概覽文章的翻譯:http://source.android.com/de...
    lee_3do閱讀 7,182評論 2 21
  • 再次附上我的github https://github.com/BudSpore 該文章由全棧工程師,開源博主韓...
    Chris鍋閱讀 6,224評論 4 31
  • 偶爾在舊書攤上發(fā)現(xiàn)—本殘破的《紅巖》,花五元錢買回,翻了幾頁卻再也沒有小時侯第一次讀她的感覺。 那是—段多么酸澀而...
    向西行閱讀 120評論 3 2