為了理解App是如何進(jìn)行渲染的吼旧,我們必須了解手機(jī)硬件是如何工作毡惜,那么就必須理解什么是VSYNC澎埠。
在講解VSYNC之前捣炬,我們需要了解兩個(gè)相關(guān)的概念:
Refresh Rate:代表了屏幕在一秒內(nèi)刷新屏幕的次數(shù),這取決于硬件的固定參數(shù)仪壮,例如60Hz憨颠。
Frame Rate:代表了GPU在一秒內(nèi)繪制操作的幀數(shù),例如30fps积锅,60fps爽彤。
GPU會(huì)獲取圖形數(shù)據(jù)進(jìn)行渲染,然后硬件負(fù)責(zé)把渲染后的內(nèi)容呈現(xiàn)到屏幕上缚陷,他們兩者不停的進(jìn)行協(xié)作适篙。
不幸的是,刷新頻率和幀率并不是總能夠保持相同的節(jié)奏箫爷。如果發(fā)生幀率與刷新頻率不一致的情況嚷节,就會(huì)容易出現(xiàn)Tearing的現(xiàn)象(畫面上下兩部分顯示內(nèi)容發(fā)生斷裂聂儒,來(lái)自不同的兩幀數(shù)據(jù)發(fā)生重疊)。
理解圖像渲染里面的雙重與三重緩存機(jī)制硫痰,這個(gè)概念比較復(fù)雜衩婚,請(qǐng)移步查看這里:http://source.android.com/devices/graphics/index.html,還有這里http://article.yeeyan.org/view/37503/304664效斑。
通常來(lái)說(shuō)非春,幀率超過刷新頻率只是一種理想的狀況,在超過60fps的情況下缓屠,GPU所產(chǎn)生的幀數(shù)據(jù)會(huì)因?yàn)榈却齎SYNC的刷新信息而被Hold住奇昙,這樣能夠保持每次刷新都有實(shí)際的新的數(shù)據(jù)可以顯示。但是我們遇到更多的情況是幀率小于刷新頻率敌完。
在這種情況下储耐,某些幀顯示的畫面內(nèi)容就會(huì)與上一幀的畫面相同。糟糕的事情是蠢挡,幀率從超過60fps突然掉到60fps以下弧岳,這樣就會(huì)發(fā)生LAG,JANK业踏,HITCHING等卡頓掉幀的不順滑的情況。這也是用戶感受不好的原因所在涧卵。
Project Buffer背景介紹
隨著時(shí)間的推移勤家,AndroidOS系統(tǒng)一直在不斷進(jìn)化、壯大柳恐,日趨完善伐脖。但直到Android 4.0問世,有關(guān)UI顯示不流暢的問題也一直未得到根本解決乐设。在整個(gè)進(jìn)化過程中讼庇,Android在Display(顯示)系統(tǒng)這塊也下了不少功夫,例如近尚,使用硬件加速等技術(shù)蠕啄,但本質(zhì)原因似乎和硬件關(guān)系并不大,因?yàn)閕Phone的硬件配置并不比那些價(jià)格相近的Android機(jī)器的硬件配置強(qiáng)戈锻,而iPhone UI的流暢性強(qiáng)卻是有目共睹的歼跟。
從Android 4.1(版本代號(hào)為Jelly Bean)開始,Android OS開發(fā)團(tuán)隊(duì)便力圖在每個(gè)版本中解決一個(gè)重要問題(這是不是也意味著Android OS在經(jīng)過幾輪大規(guī)模改善后格遭,開始進(jìn)入手術(shù)刀式的精加工階段呢哈街?)。作為嚴(yán)重影響Android口碑問題之一的UI流暢性差的問題拒迅,首先在Android 4.1版本中得到了有效處理骚秦。其解決方法就是本文要介紹的Project Butter她倘。
Project Butter對(duì)Android Display系統(tǒng)進(jìn)行了重構(gòu),引入了三個(gè)核心元素作箍,即VSYNC硬梁、Triple Buffer和Choreographer。其中蒙揣,VSYNC是理解Project Buffer的核心靶溜。VSYNC是Vertical Synchronization(垂直同步)的縮寫,是一種在PC上已經(jīng)很早就廣泛使用的技術(shù)懒震。讀者可簡(jiǎn)單的把它認(rèn)為是一種定時(shí)中斷罩息。
接下來(lái),本文將圍繞VSYNC來(lái)介紹Android Display系統(tǒng)的工作方式[①]个扰。請(qǐng)注意瓷炮,后續(xù)討論將以Display為基準(zhǔn),將其劃分成16ms長(zhǎng)度的時(shí)間段递宅,在每一時(shí)間段中娘香,Display顯示一幀數(shù)據(jù)(相當(dāng)于每秒60幀)。時(shí)間段從1開始編號(hào)办龄。
首先是沒有VSYNC的情況烘绽,如圖1所示:
由圖1可知:
時(shí)間從0開始,進(jìn)入第一個(gè)16ms:Display顯示第0幀俐填,CPU處理完第一幀后安接,GPU緊接其后處理繼續(xù)第一幀。三者互不干擾英融,一切正常盏檐。
時(shí)間進(jìn)入第二個(gè)16ms:因?yàn)樵缭谏弦粋€(gè)16ms時(shí)間內(nèi),第1幀已經(jīng)由CPU驶悟,GPU處理完畢胡野。故Display可以直接顯示第1幀。顯示沒有問題痕鳍。但在本16ms期間硫豆,CPU和GPU卻并未及時(shí)去繪制第2幀數(shù)據(jù)(注意前面的空白區(qū)),而是在本周期快結(jié)束時(shí)额获,CPU/GPU才去處理第2幀數(shù)據(jù)够庙。
時(shí)間進(jìn)入第3個(gè)16ms,此時(shí)Display應(yīng)該顯示第2幀數(shù)據(jù)抄邀,但由于CPU和GPU還沒有處理完第2幀數(shù)據(jù)耘眨,故Display只能繼續(xù)顯示第一幀的數(shù)據(jù),結(jié)果使得第1幀多畫了一次(對(duì)應(yīng)時(shí)間段上標(biāo)注了一個(gè)Jank)境肾。
通過上述分析可知剔难,此處發(fā)生Jank的關(guān)鍵問題在于胆屿,為何第1個(gè)16ms段內(nèi),CPU/GPU沒有及時(shí)處理第2幀數(shù)據(jù)偶宫?原因很簡(jiǎn)單非迹,CPU可能是在忙別的事情(比如某個(gè)應(yīng)用通過sleep固定時(shí)間來(lái)實(shí)現(xiàn)動(dòng)畫的逐幀顯示),不知道該到處理UI繪制的時(shí)間了纯趋≡魇蓿可CPU一旦想起來(lái)要去處理第2幀數(shù)據(jù),時(shí)間又錯(cuò)過了吵冒!
為解決這個(gè)問題纯命,Project Buffer引入了VSYNC,這類似于時(shí)鐘中斷痹栖。結(jié)果如圖2所示:
由圖2可知亿汞,每收到VSYNC中斷,CPU就開始處理各幀數(shù)據(jù)揪阿。整個(gè)過程非常完美疗我。
不過,仔細(xì)琢磨圖2卻會(huì)發(fā)現(xiàn)一個(gè)新問題:圖2中南捂,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í)才開始數(shù)據(jù)處理,故它們的FPS被拉低到與Display的FPS相同愿卒。但這種處理并沒有什么問題缚去,因?yàn)锳ndroid設(shè)備的Display FPS一般是60,其對(duì)應(yīng)的顯示效果非常平滑琼开。
如果CPU/GPU的FPS小于Display的FPS易结,會(huì)是什么情況呢?請(qǐng)看圖3:
由圖3可知:
在第二個(gè)16ms時(shí)間段柜候,Display本應(yīng)顯示B幀搞动,但卻因?yàn)镚PU還在處理B幀,導(dǎo)致A幀被重復(fù)顯示渣刷。
同理鹦肿,在第二個(gè)16ms時(shí)間段內(nèi),CPU無(wú)所事事辅柴,因?yàn)锳 Buffer被Display在使用箩溃。B Buffer被GPU在使用瞭吃。注意,一旦過了VSYNC時(shí)間點(diǎn)涣旨,CPU就不能被觸發(fā)以處理繪制工作了歪架。
為什么CPU不能在第二個(gè)16ms處開始繪制工作呢?原因就是只有兩個(gè)Buffer霹陡。如果有第三個(gè)Buffer的存在和蚪,CPU就能直接使用它,而不至于空閑烹棉。出于這一思路就引出了Triple Buffer攒霹。結(jié)果如圖4所示:
圖4? Triple Buffer的情況
由圖4可知:
第二個(gè)16ms時(shí)間段,CPU使用C Buffer繪圖峦耘。雖然還是會(huì)多顯示A幀一次剔蹋,但后續(xù)顯示就比較順暢了。
是不是Buffer越多越好呢辅髓?回答是否定的泣崩。由圖4可知,在第二個(gè)時(shí)間段內(nèi)洛口,CPU繪制的第C幀數(shù)據(jù)要到第四個(gè)16ms才能顯示矫付,這比雙Buffer情況多了16ms延遲。所以第焰,Buffer最好還是兩個(gè)买优,三個(gè)足矣。
介紹了上述背景知識(shí)后挺举,下文將分析Android Project Buffer的一些細(xì)節(jié)杀赢。
Project Buffer分析
上一節(jié)對(duì)VSYNC進(jìn)行了理論分析,其實(shí)也引出了Project Buffer的三個(gè)關(guān)鍵點(diǎn):
核心關(guān)鍵:需要VSYNC定時(shí)中斷湘纵。
Triple Buffer:當(dāng)雙Buffer不夠使用時(shí)脂崔,該系統(tǒng)可分配第三塊Buffer。
另外梧喷,還有一個(gè)非常隱秘的關(guān)鍵點(diǎn):即將繪制工作都統(tǒng)一到VSYNC時(shí)間點(diǎn)上砌左。這就是Choreographer的作用。Choreographer是一個(gè)極富詩(shī)意的詞铺敌,意為舞蹈編導(dǎo)汇歹。在它的統(tǒng)一指揮下,應(yīng)用的繪制工作都將變得井井有條偿凭。
下面來(lái)看Project Buffer實(shí)現(xiàn)的細(xì)節(jié)产弹。
首先被動(dòng)刀的是SurfaceFlinger家族成員。目標(biāo)是提供VSYNC中斷笔喉。相關(guān)類圖如圖5所示:
由圖5可知:
HardwareComposer封裝了相關(guān)的HAL層取视,如果硬件廠商提供的HAL層實(shí)現(xiàn)能定時(shí)產(chǎn)生VSYNC中斷硝皂,則直接使用硬件的VSYNC中斷,否則HardwareComposer內(nèi)部會(huì)通過VSyncThread來(lái)模擬產(chǎn)生VSYNC中斷(其實(shí)現(xiàn)很簡(jiǎn)單,就是sleep固定時(shí)間,然后喚醒)识腿。
當(dāng)VSYNC中斷產(chǎn)生時(shí)(不管是硬件產(chǎn)生還是VSyncThread模擬的),VSyncHandler的onVSyncReceived函數(shù)將被調(diào)用贝或。所以,對(duì)VSYNC中斷來(lái)說(shuō)锐秦,VSyncHandler的onVSyncReceived咪奖,就是其中斷處理函數(shù)。
在SurfaceFlinger家族中酱床,VSyncHandler的實(shí)例是EventThread羊赵。下邊是EventThread類的聲明:
class EventThread : public Thread, public DisplayHardware::VSyncHandler
由EventThread定義可知,它本身運(yùn)行在一個(gè)單獨(dú)的線程中扇谣,并繼承了VSyncHandler昧捷。EventThread的核心處理在其線程函數(shù)threadLoop中完成,其處理邏輯主要是:
等待下一次VSYNC的到來(lái)罐寨,并派發(fā)該中斷事件給VSYNC監(jiān)聽者靡挥。
通過EventThread,VSYNC中斷事件可派發(fā)給多個(gè)該中斷的監(jiān)聽者去處理鸯绿。相關(guān)類如圖6所示:
由圖6可知:
SurfaceFlinger從Thread派生跋破,其核心功能單獨(dú)運(yùn)行在一個(gè)線程中。
SurfaceFlinger包括一個(gè)EventThread和一個(gè)MessageQueue對(duì)象瓶蝴。
對(duì)象通過mEvents成員指向一個(gè)IDisplayEventConnection類型的對(duì)象毒返。IDisplayEventConnection是一個(gè)純虛類,它代表VSYNC中斷的監(jiān)聽者舷手。其實(shí)體類是EventThread的內(nèi)部類Connection饿悬。
IDisplayEventConnection定義了一個(gè)getDataChannel函數(shù),該函數(shù)返回一個(gè)BitTube實(shí)例聚霜。這個(gè)實(shí)例提供的read/write方法,用于傳送具體的信號(hào)數(shù)據(jù)(其內(nèi)部實(shí)現(xiàn)為socketpair珠叔,可通過Binder實(shí)現(xiàn)進(jìn)程跨越)蝎宇。
EventThread最重要的一個(gè)VSYNC監(jiān)聽者就是MessageQueue的mEvents對(duì)象。當(dāng)然祷安,這一切都是為最大的后臺(tái)老板SurfaceFlinger服務(wù)的姥芥。來(lái)自EventThread的VSYNC中斷信號(hào),將通過MessageQueue轉(zhuǎn)化為一個(gè)REFRESH消息并傳遞給SurfaceFlinger的onMessageReceived函數(shù)處理汇鞭。
有必要指出凉唐,4.1中SurfaceFlinger onMessageReceived函數(shù)的實(shí)現(xiàn)僅僅是將4.0版本的SurfaceFlinger的核心函數(shù)挪過來(lái)罷了[②]庸追,并未做什么改動(dòng)。
以上是Project Buffer對(duì)SurfaceFlinger所做的一些改動(dòng)台囱。那么Triple Buffer是怎么處理的呢淡溯?幸好從Android 2.2開始,Display的Page Flip算法就不依賴Buffer的個(gè)數(shù)簿训,Buffer個(gè)數(shù)不過是算法的一個(gè)參數(shù)罷了咱娶。所以,Triple Buffer的引入强品,只是把Buffer的數(shù)目改成了3膘侮,而算法本身相對(duì)于4.0來(lái)說(shuō)并沒有變化。圖7為Triple Buffer的設(shè)置示意圖:
圖7? Layer.cpp中對(duì)Triple Buffer的設(shè)置
圖7所示的榛,為L(zhǎng)ayer.cpp中對(duì)Buffer個(gè)數(shù)的設(shè)置琼了。TARGET_DISABLE_TRIPLE_BUFFERING宏可設(shè)置Buffer的個(gè)數(shù)。對(duì)某些內(nèi)存/顯存并不是很大的設(shè)備夫晌,也可以選擇不使用Triple Buffer。
Choreographer是一個(gè)Java類慷丽。第一次看到這個(gè)詞時(shí),我很激動(dòng)要糊。一個(gè)小小的命名真的反應(yīng)出了設(shè)計(jì)者除coding之外的廣博的視界。試想局劲,如果不是對(duì)舞蹈有相當(dāng)了解或喜愛,一般人很難想到用這個(gè)詞來(lái)描述它鱼填。
Choreographer的定義和基本結(jié)構(gòu)如圖8所示:
圖8中:
Choreographer是線程單例的毅戈,而且必須要和一個(gè)Looper綁定,因?yàn)槠鋬?nèi)部有一個(gè)Handler需要和Looper綁定苇经。
DisplayEventReceiver是一個(gè)abstract class,其JNI的代碼部分會(huì)創(chuàng)建一個(gè)IDisplayEventConnection的VSYNC監(jiān)聽者對(duì)象扇单。這樣,來(lái)自EventThread的VSYNC中斷信號(hào)就可以傳遞給Choreographer對(duì)象了施流。由圖8可知响疚,當(dāng)VSYNC信號(hào)到來(lái)時(shí)瞪醋,DisplayEventReceiver的onVsync函數(shù)將被調(diào)用。
另外趟章,DisplayEventReceiver還有一個(gè)scheduleVsync函數(shù)。當(dāng)應(yīng)用需要繪制UI時(shí)宏侍,將首先申請(qǐng)一次VSYNC中斷,然后再在中斷處理的onVsync函數(shù)去進(jìn)行繪制谅河。
Choreographer定義了一個(gè)FrameCallback interface,每當(dāng)VSYNC到來(lái)時(shí)绷耍,其doFrame函數(shù)將被調(diào)用鲜侥。這個(gè)接口對(duì)Android Animation的實(shí)現(xiàn)起了很大的幫助作用褂始。以前都是自己控制時(shí)間描函,現(xiàn)在終于有了固定的時(shí)間中斷。
Choreographer的主要功能是舀寓,當(dāng)收到VSYNC信號(hào)時(shí),去調(diào)用使用者通過postCallback設(shè)置的回調(diào)函數(shù)必尼。目前一共定義了三種類型的回調(diào),它們分別是:
CALLBACK_INPUT:優(yōu)先級(jí)最高判莉,和輸入事件處理有關(guān)育谬。
CALLBACK_ANIMATION:優(yōu)先級(jí)其次骂租,和Animation的處理有關(guān)斑司。
CALLBACK_TRAVERSAL:優(yōu)先級(jí)最低,和UI等控件繪制有關(guān)宿刮。
優(yōu)先級(jí)高低和處理順序有關(guān)。當(dāng)收到VSYNC中斷時(shí)胡桃,Choreographer將首先處理INPUT類型的回調(diào),然后是ANIMATION類型翠胰,最后才是TRAVERSAL類型自脯。
此外之景,讀者在自行閱讀Choreographer相關(guān)代碼時(shí)膏潮,還會(huì)發(fā)現(xiàn)Android對(duì)Message/Looper類[③]也進(jìn)行了一番小改造,使之支持Asynchronous Message和Synchronization Barrier(參照Looper.java的postSyncBarrier函數(shù))焕参。其實(shí)現(xiàn)非常巧妙,這部分內(nèi)容就留給讀者自己理解并欣賞了刻帚。
相比SurfaceFlinger涩嚣,Choreographer是Android 4.1中的新事物崇众,下面將通過一個(gè)實(shí)例來(lái)簡(jiǎn)單介紹Choreographer的工作原理缓艳。
假如UI中有一個(gè)控件invalidate了,那么它將觸發(fā)ViewRootImpl的invalidate函數(shù)阶淘,該函數(shù)將最終調(diào)用ViewRootImpl的scheduleTraversals。其代碼如圖9所示:
圖9? ViewRootImpl scheduleTraversals函數(shù)的實(shí)現(xiàn)
由圖9可知坤塞,scheduleTraversals首先禁止了后續(xù)的消息處理功能澈蚌,這是由設(shè)置Looper的postSyncBarrier來(lái)完成的摹芙。一旦設(shè)置了SyncBarrier宛瞄,所有非Asynchronous的消息便將停止派發(fā)。
然后,為Choreographer設(shè)置了CALLBACK類型為TRAVERSAL的處理對(duì)象蝴簇,即mTraversalRunnable匆帚。
最后調(diào)用scheduleConsumeBatchedInput熬词,這個(gè)函數(shù)將為Choreographer設(shè)置了CALLBACK類型為INPUT的處理對(duì)象吸重。
Choreographer的postCallback函數(shù)將會(huì)申請(qǐng)一次VSYNC中斷(通過調(diào)用DisplayEventReceiver的scheduleVsync實(shí)現(xiàn))。當(dāng)VSYNC信號(hào)到達(dá)時(shí)颜矿,Choreographer doFrame函數(shù)被調(diào)用,內(nèi)部代碼會(huì)觸發(fā)回調(diào)處理或衡。代碼片段如圖10所示:
圖10? Choreographer doFrame函數(shù)片段
對(duì)ViewRootImpl來(lái)說(shuō)车遂,其TRAVERSAL回調(diào)對(duì)應(yīng)的處理對(duì)象,就是前面介紹的mTraversalRunnable舶担,它的代碼很簡(jiǎn)單,如圖11所示:
圖11 ?mTraversalRunnable的實(shí)現(xiàn)
doTraversal內(nèi)部實(shí)現(xiàn)和Android 4.0版本一致柄瑰。故相比于4.0來(lái)說(shuō),4.1只是把doTraversal調(diào)用位置放到VSYNC中斷處理中了教沾。
通過上邊的介紹译断,可知Choreographer確實(shí)做到了對(duì)繪制工作的統(tǒng)一安排授翻,不愧是個(gè)長(zhǎng)于統(tǒng)籌安排的“舞蹈編導(dǎo)”孙咪。