作者:yearzhu,2011年進(jìn)入騰訊公司,從事過(guò)Web端及移動(dòng)端的測(cè)試工作,喜愛(ài)新鮮事物及新技術(shù),目前在SNG開(kāi)放平臺(tái)測(cè)試組負(fù)責(zé)的移動(dòng)互聯(lián)SDK的測(cè)試工作。
現(xiàn)在越來(lái)越多的應(yīng)用開(kāi)始重視流暢度方面的測(cè)試,了解Android應(yīng)用程序是如何在屏幕上顯示的則是基礎(chǔ)中的基礎(chǔ),就讓我們一起看看小小屏幕中大大的學(xué)問(wèn)。這也是我下篇文章——《Android應(yīng)用流暢度測(cè)試分析》的基礎(chǔ)。
首先,用一句話來(lái)概括一下Android應(yīng)用程序顯示的過(guò)程:Android應(yīng)用程序調(diào)用SurfaceFlinger服務(wù)把經(jīng)過(guò)測(cè)量、布局和繪制后的Surface渲染到顯示屏幕上。
名詞解釋 SurfaceFlinger:Android系統(tǒng)服務(wù),負(fù)責(zé)管理Android系統(tǒng)的幀緩沖區(qū),即顯示屏幕。 Surface:Android應(yīng)用的每個(gè)窗口對(duì)應(yīng)一個(gè)畫(huà)布(Canvas),即Surface,可以理解為Android應(yīng)用程序的一個(gè)窗口。
Android應(yīng)用程序的顯示過(guò)程包含了兩個(gè)部分(應(yīng)用側(cè)繪制、系統(tǒng)側(cè)渲染)、兩個(gè)機(jī)制(進(jìn)程間通訊機(jī)制、顯示刷新機(jī)制),接下來(lái)我們就來(lái)一一道來(lái)。
應(yīng)用側(cè) 一個(gè)Android應(yīng)用程序窗口里面包含了很多UI元素,這些UI元素是以樹(shù)形結(jié)構(gòu)來(lái)組織的,即它們存在著父子關(guān)系,其中,子UI元素位于父UI元素里面,如下圖:
因此,在繪制一個(gè)Android應(yīng)用程序窗口的UI之前,我們首先要確定它里面的各個(gè)子UI元素在父UI元素里面的大小以及位置。確定各個(gè)子UI元素在父UI元素里面的大小以及位置的過(guò)程又稱為測(cè)量過(guò)程和布局過(guò)程。因此,Android應(yīng)用程序窗口的UI渲染過(guò)程可以分為測(cè)量、布局和繪制三個(gè)階段。如下圖所示: 測(cè)量:遞歸(深度優(yōu)先)確定所有視圖的大小(高、寬) 布局:遞歸(深度優(yōu)先)確定所有視圖的位置(左上角坐標(biāo)) 繪制:在畫(huà)布canvas上繪制應(yīng)用程序窗口所有的視圖
測(cè)量、布局沒(méi)有太多要說(shuō)的,這里要著重說(shuō)一下繪制。Android目前有兩種繪制模型:基于軟件的繪制模型和硬件加速的繪制模型(從Android 3.0開(kāi)始全面支持)。
在基于軟件的繪制模型下,CPU主導(dǎo)繪圖,視圖按照兩個(gè)步驟繪制: 1. 讓View層次結(jié)構(gòu)失效 2. 繪制View層次結(jié)構(gòu) 當(dāng)應(yīng)用程序需要更新它的部分UI時(shí),都會(huì)調(diào)用內(nèi)容發(fā)生改變的View對(duì)象的invalidate()方法。無(wú)效(invalidation)消息請(qǐng)求會(huì)在View對(duì)象層次結(jié)構(gòu)中傳遞,以便計(jì)算出需要重繪的屏幕區(qū)域(臟區(qū))。然后,Android系統(tǒng)會(huì)在View層次結(jié)構(gòu)中繪制所有的跟臟區(qū)相交的區(qū)域。不幸的是,這種方法有兩個(gè)缺點(diǎn): 1. 繪制了不需要重繪的視圖(與臟區(qū)域相交的區(qū)域) 2. 掩蓋了一些應(yīng)用的bug(由于會(huì)重繪與臟區(qū)域相交的區(qū)域) 注意:在View對(duì)象的屬性發(fā)生變化時(shí),如背景色或TextView對(duì)象中的文本等,Android系統(tǒng)會(huì)自動(dòng)的調(diào)用該View對(duì)象的invalidate()方法。
在基于硬件加速的繪制模式下,GPU主導(dǎo)繪圖,繪制按照三個(gè)步驟繪制: 1. 讓View層次結(jié)構(gòu)失效 2. 記錄、更新顯示列表 3. 繪制顯示列表 這種模式下,Android系統(tǒng)依然會(huì)使用invalidate()方法和draw()方法來(lái)請(qǐng)求屏幕更新和展現(xiàn)View對(duì)象。但Android系統(tǒng)并不是立即執(zhí)行繪制命令,而是首先把這些View的繪制函數(shù)作為繪制指令記錄一個(gè)顯示列表中,然后再讀取顯示列表中的繪制指令調(diào)用OpenGL相關(guān)函數(shù)完成實(shí)際繪制。另一個(gè)優(yōu)化是,Android系統(tǒng)只需要針對(duì)由invalidate()方法調(diào)用所標(biāo)記的View對(duì)象的臟區(qū)進(jìn)行記錄和更新顯示列表。沒(méi)有失效的View對(duì)象則能重放先前顯示列表記錄的繪制指令來(lái)進(jìn)行簡(jiǎn)單的重繪工作。 使用顯示列表的目的是,把視圖的各種繪制函數(shù)翻譯成繪制指令保存起來(lái),對(duì)于沒(méi)有發(fā)生改變的視圖把原先保存的操作指令重新讀取出來(lái)重放一次就可以了,提高了視圖的顯示速度。而對(duì)于需要重繪的View,則更新顯示列表,以便下次重用,然后再調(diào)用OpenGL完成繪制。 硬件加速提高了Android系統(tǒng)顯示和刷新的速度,但它也不是萬(wàn)能的,它有三個(gè)缺陷: 1. 兼容性(部分繪制函數(shù)不支持或不完全硬件加速,參見(jiàn)文章尾) 2. 內(nèi)存消耗(OpenGL API調(diào)用就會(huì)占用8MB,而實(shí)際上會(huì)占用更多內(nèi)存) 3. 電量消耗(GPU耗電)
系統(tǒng)側(cè) Android應(yīng)用程序在圖形緩沖區(qū)中繪制好View層次結(jié)構(gòu)后,這個(gè)圖形緩沖區(qū)會(huì)被交給SurfaceFlinger服務(wù),而SurfaceFlinger服務(wù)再使用OpenGL圖形庫(kù)API來(lái)將這個(gè)圖形緩沖區(qū)渲染到硬件幀緩沖區(qū)中。
由于Android應(yīng)用程序很少能涉及到Android系統(tǒng)底層,所以SurfaceFlinger服務(wù)的執(zhí)行過(guò)程不做過(guò)多的介紹。
進(jìn)程間通訊機(jī)制 Android應(yīng)用程序?yàn)榱四軌驅(qū)⒆约旱腢I繪制在系統(tǒng)的幀緩沖區(qū)上,它們就必須要與SurfaceFlinger服務(wù)進(jìn)行通信,如圖所示: Android應(yīng)用程序與SurfaceFlinger服務(wù)是運(yùn)行在不同的進(jìn)程中的,因此,它們采用某種進(jìn)程間通信機(jī)制來(lái)進(jìn)行通信。由于Android應(yīng)用程序在通知SurfaceFlinger服務(wù)來(lái)繪制自己的UI的時(shí)候,需要將UI數(shù)據(jù)傳遞給SurfaceFlinger服務(wù),例如,要繪制UI的區(qū)域、位置等信息。一個(gè)Android應(yīng)用程序可能會(huì)有很多個(gè)窗口,而每一個(gè)窗口都有自己的UI數(shù)據(jù),因此,Android系統(tǒng)的匿名共享內(nèi)存機(jī)制就派上用場(chǎng)了。 每一個(gè)Android應(yīng)用程序與SurfaceFlinger服務(wù)之間,都會(huì)通過(guò)一塊匿名共享內(nèi)存來(lái)傳遞UI數(shù)據(jù),如下所示:
但是單純的匿名共享內(nèi)存在傳遞多個(gè)窗口數(shù)據(jù)時(shí)缺乏有效的管理,所以匿名共享內(nèi)存就被抽象為一個(gè)更上流的數(shù)據(jù)結(jié)構(gòu)SharedClient,如下圖所示:
在每個(gè)SharedClient中,最多有31個(gè)SharedBufferStack,每個(gè)SharedBufferStack都對(duì)應(yīng)一個(gè)Surface,即一個(gè)窗口。這樣,我們就可以知道為什么每一個(gè)SharedClient里面包含的是一系列SharedBufferStack而不是單個(gè)SharedBufferStack:一個(gè)SharedClient對(duì)應(yīng)一個(gè)Android應(yīng)用程序,而一個(gè)Android應(yīng)用程序可能包含有多個(gè)窗口,即Surface。從這里也可以看出,一個(gè)Android應(yīng)用程序至多可以包含31個(gè)窗口。 每個(gè)SharedBufferStack中又包含了N個(gè)緩沖區(qū)(<4.1 N=2; >=4.1 N=3),即顯示刷新機(jī)制中即將提到的雙緩沖和三重緩沖技術(shù)。
顯示刷新機(jī)制 一般我們?cè)诶L制UI的時(shí)候,都會(huì)采用一種稱為“雙緩沖”的技術(shù)。雙緩沖意味著要使用兩個(gè)緩沖區(qū)(SharedBufferStack中),其中一個(gè)稱為Front Buffer,另外一個(gè)稱為Back Buffer。UI總是先在Back Buffer中繪制,然后再和Front Buffer交換,渲染到顯示設(shè)備中。理想情況下,這樣一個(gè)刷新會(huì)在16ms內(nèi)完成(60FPS),下圖就是描述的這樣一個(gè)刷新過(guò)程(Display處理前Front Buffer,CPU、GPU處理Back Buffer。
但現(xiàn)實(shí)情況并非這么理想。 1. 時(shí)間從0開(kāi)始,進(jìn)入第一個(gè)16ms:Display顯示第0幀,CPU處理完第一幀后,GPU緊接其后處理繼續(xù)第一幀。三者互不干擾,一切正常。 2. 時(shí)間進(jìn)入第二個(gè)16ms:因?yàn)樵缭谏弦粋€(gè)16ms時(shí)間內(nèi),第1幀已經(jīng)由CPU,GPU處理完畢。故Display可以直接顯示第1幀。顯示沒(méi)有問(wèn)題。但在本16ms期間,CPU和GPU卻并未及時(shí)去繪制第2幀數(shù)據(jù)(注意前面的空白區(qū)),而是在本周期快結(jié)束時(shí),CPU/GPU才去處理第2幀數(shù)據(jù)。 3. 時(shí)間進(jìn)入第3個(gè)16ms,此時(shí)Display應(yīng)該顯示第2幀數(shù)據(jù),但由于CPU和GPU還沒(méi)有處理完第2幀數(shù)據(jù),故Display只能繼續(xù)顯示第一幀的數(shù)據(jù),結(jié)果使得第1幀多畫(huà)了一次(對(duì)應(yīng)時(shí)間段上標(biāo)注了一個(gè)Jank)。 通過(guò)上述分析可知,此處發(fā)生Jank的關(guān)鍵問(wèn)題在于,為何第1個(gè)16ms段內(nèi),CPU/GPU沒(méi)有及時(shí)處理第2幀數(shù)據(jù)?原因很簡(jiǎn)單,CPU可能是在忙別的事情,不知道該到處理UI繪制的時(shí)間了。可CPU一旦想起來(lái)要去處理第2幀數(shù)據(jù),時(shí)間又錯(cuò)過(guò)了!
為解決這個(gè)問(wèn)題,Android 4.1中引入了VSYNC,這類似于時(shí)鐘中斷。結(jié)果如下圖所示:
由上圖可知,每收到VSYNC中斷,CPU就開(kāi)始處理各幀數(shù)據(jù)。整個(gè)過(guò)程非常完美。 不過(guò),仔細(xì)琢磨后卻會(huì)發(fā)現(xiàn)一個(gè)新問(wèn)題:上圖中,CPU和GPU處理數(shù)據(jù)的速度似乎都能在16ms內(nèi)完成,而且還有時(shí)間空余,也就是說(shuō),CPU/GPU的FPS(幀率,F(xiàn)rames Per Second)要高于Display的FPS。確實(shí)如此。由于CPU/GPU只在收到VSYNC時(shí)才開(kāi)始數(shù)據(jù)處理,故它們的FPS被拉低到與Display的FPS相同。但這種處理并沒(méi)有什么問(wèn)題,因?yàn)?b style="background-color: rgb(255, 255, 102); ">Android設(shè)備的Display FPS一般是60,其對(duì)應(yīng)的顯示效果非常平滑。 如果CPU/GPU的FPS小于Display的FPS,會(huì)是什么情況呢?請(qǐng)看下圖:
由上圖可知: 1. 在第二個(gè)16ms時(shí)間段,Display本應(yīng)顯示B幀,但卻因?yàn)镚PU還在處理B幀,導(dǎo)致A幀被重復(fù)顯示。 2. 同理,在第二個(gè)16ms時(shí)間段內(nèi),CPU無(wú)所事事,因?yàn)锳 Buffer被Display在使用。B Buffer被GPU在使用。注意,一旦過(guò)了VSYNC時(shí)間點(diǎn),CPU就不能被觸發(fā)以處理繪制工作了。 為什么CPU不能在第二個(gè)16ms處開(kāi)始繪制工作呢?原因就是只有兩個(gè)Buffer(Android 4.1之前)。如果有第三個(gè)Buffer的存在,CPU就能直接使用它,而不至于空閑。出于這一思路就引出了三重緩沖區(qū)(Android 4.1)。結(jié)果如下圖所示: 由上圖可知: 第二個(gè)16ms時(shí)間段,CPU使用C Buffer繪圖。雖然還是會(huì)多顯示A幀一次,但后續(xù)顯示就比較順暢了。 是不是Buffer越多越好呢?回答是否定的。由上圖可知,在第二個(gè)時(shí)間段內(nèi),CPU繪制的第C幀數(shù)據(jù)要到第四個(gè)16ms才能顯示,這比雙Buffer情況多了16ms延遲。所以,Buffer最好還是兩個(gè),三個(gè)足矣。
到這里,Android系統(tǒng)的顯示原理就介紹完了。那么在了解這些原理后對(duì)我們的流暢度測(cè)試有哪些幫助呢,請(qǐng)看我的下篇文章《Android應(yīng)用流暢度測(cè)試分析》。
附:不同的API Level下,繪制函數(shù)對(duì)硬件加速模式的支持情況
|
|