本篇文章已授權(quán)微信公眾號(hào) Android訂閱 發(fā)布
簡(jiǎn)介
我們?cè)陂_(kāi)發(fā)的過(guò)程中辙喂,可能經(jīng)常會(huì)遇到測(cè)試的一些反饋,就是APP運(yùn)行卡頓的問(wèn)題哟沫。我們通常所講的卡頓問(wèn)題都是因?yàn)殇秩镜魩膯?wèn)題引起視覺(jué)上的卡頓感饺蔑。所以了解渲染機(jī)制,我們?cè)陧?xiàng)目的開(kāi)發(fā)過(guò)程中嗜诀,可以有意識(shí)的少挖坑猾警。同時(shí)要打造一款精品的應(yīng)用,注意渲染優(yōu)化也是非常重要的一件事情隆敢。
當(dāng)然目前我們好多同學(xué)在開(kāi)發(fā)的工程中发皿,經(jīng)常會(huì)忽略渲染優(yōu)化這一塊,主要的原因可能是
- 項(xiàng)目沒(méi)要求拂蝎,能滿(mǎn)足功能則可
- 缺少意識(shí)穴墅,沒(méi)有做性能優(yōu)化的意識(shí)
- 缺少用工具分析,主觀感受不強(qiáng)
- 需求的苦海温自,無(wú)法脫身(有多少童鞋戳中淚點(diǎn))
不管如何封救,我們都需要對(duì)自己有所要求。盡量在開(kāi)發(fā)的過(guò)程中注意捣作,少挖坑。對(duì)已上線(xiàn)的項(xiàng)目能夠進(jìn)行優(yōu)化分析鹅士,打造精品券躁。
接下來(lái)我們將介紹渲染的底層機(jī)制,并針對(duì)性地進(jìn)行優(yōu)化分析掉盅。
渲染機(jī)制
視覺(jué)感官
我們都可能聽(tīng)過(guò)Android的屏幕刷新頻率是60fps 也就是16ms需要完成一幀的刷新也拜。
首先我們理解一下幀的概念。
每一幀都是靜止的圖象趾痘,快速連續(xù)地顯示幀便形成了運(yùn)動(dòng)的假象慢哈,因此高的幀率可以得到更流暢、更逼真的動(dòng)畫(huà)永票。
當(dāng)物體在快速運(yùn)動(dòng)時(shí), 當(dāng)人眼所看到的影像消失后卵贱,人眼仍能繼續(xù)保留其影像1/24秒左右的圖像滥沫,這種現(xiàn)象被稱(chēng)為視覺(jué)暫留現(xiàn)象。是人眼具有的一種性質(zhì)键俱。人眼觀看物體時(shí)兰绣,成像于視網(wǎng)膜上,并由視神經(jīng)輸入人腦编振,感覺(jué)到物體的像缀辩。但當(dāng)物體移去時(shí),視神經(jīng)對(duì)物體的印象不會(huì)立即消失踪央,而要延續(xù)1/24秒左右的時(shí)間臀玄,人眼的這種性質(zhì)被稱(chēng)為“眼睛的視覺(jué)暫留”。
所以以前我們看膠卷電影的時(shí)候刷新的頻率就是24fps畅蹂。我們看起來(lái)就是連續(xù)的一個(gè)視覺(jué)效果健无。當(dāng)然這里越高的幀率,我們可以得到更流暢魁莉、逼真的畫(huà)面睬涧。
VSYNC
Android系統(tǒng)每隔16ms發(fā)出VSYNC信號(hào),觸發(fā)對(duì)UI進(jìn)行渲染旗唁, 如果每次渲染都成功畦浓,這樣就能夠達(dá)到流暢的畫(huà)面所需要的60fps,為了能夠?qū)崿F(xiàn)60fps检疫,這意味著程序的大多數(shù)操作都必須在16ms內(nèi)完成讶请。如果超過(guò)了16ms那么可能就出現(xiàn)丟幀的情況。
VSYNC有兩個(gè)概念
- Refresh Rate:屏幕在一秒時(shí)間內(nèi)刷新屏幕的次數(shù)----由硬件的參數(shù)決定屎媳,比如60HZ.
- Frame Rate:GPU在一秒內(nèi)繪制操作的幀數(shù)夺溢,比如:60fps。
通常來(lái)說(shuō)烛谊,幀率超過(guò)刷新頻率只是一種理想的狀況风响,在超過(guò)60fps的情況下,GPU所產(chǎn)生的幀數(shù)據(jù)會(huì)因?yàn)榈却齎SYNC的刷新信息而被Hold住丹禀,這樣能夠保持每次刷新都有實(shí)際的新的數(shù)據(jù)可以顯示状勤。但是我們遇到更多的情況是幀率小于刷新頻率。在這種情況下双泪,某些幀顯示的畫(huà)面內(nèi)容就會(huì)與上一幀的畫(huà)面相同,造成卡頓的現(xiàn)象持搜。
簡(jiǎn)單來(lái)說(shuō),VSYNC也叫垂直刷新焙矛,是一個(gè)信號(hào)葫盼。會(huì)觸發(fā)渲染。這個(gè)過(guò)程需要我們屏幕的刷新頻率(一般60fps)和我們GPU所產(chǎn)生的幀數(shù)能夠進(jìn)行同步村斟,那么UI的渲染就能流暢贫导。如果我們自己定義的布局或者自定義控件的渲染時(shí)間超過(guò)了16ms每幀抛猫,那么就可能導(dǎo)致屏幕刷新的時(shí)候,我們的GPU還不能產(chǎn)生新的幀脱盲,用戶(hù)看的還是舊的幀邑滨。這就造成了我們視覺(jué)上的卡頓,影響用戶(hù)體驗(yàn)钱反。
渲染管線(xiàn)
我們定義好了一個(gè)xml的布局界面后掖看,是怎樣最終呈現(xiàn)在我們的手機(jī)屏幕上的呢?
這里我們借助Google官方的性能優(yōu)化的一張示例圖來(lái)說(shuō)明面哥。
CPU負(fù)責(zé)把UI組件計(jì)算成Polygons哎壳,Texture紋理,然后交給GPU進(jìn)行柵格化渲染尚卫。最終在屏幕進(jìn)行顯示归榕。
這個(gè)地方CPU主要是將我們的布局文件的View Tree進(jìn)行測(cè)量和繪制,最后形成Ploygons(多邊形)及Texture(紋理貼圖)
柵格化是繪制那些Button吱涉,Shape刹泄,Path,String怎爵,Bitmap等組件最基礎(chǔ)的操作特石。它把那些組件拆分到不同的像素上進(jìn)行顯示。這是一個(gè)很費(fèi)時(shí)的操作鳖链,GPU的引入就是為了加快柵格化的操作
Android在性能優(yōu)化已經(jīng)做了很多工作姆蘸。在CPU將Ploygons和Texture傳遞到GPU是一個(gè)很耗時(shí)的過(guò)程。所以Android將Bitmaps芙委,Drawables都是一起打包到統(tǒng)一的Texture紋理當(dāng)中逞敷,然后再傳遞到 GPU里面,這意味著每次你需要使用這些資源的時(shí)候灌侣,都是直接從紋理里面進(jìn)行獲取渲染的推捐。
Tip
項(xiàng)目里曾經(jīng)遇到一個(gè)問(wèn)題,對(duì)一個(gè)圖標(biāo)染色了侧啼。然后其他使用到改圖標(biāo)的地方也同樣變成染色后的圖標(biāo)了玖姑。這個(gè)地方就是因?yàn)镚PU有緩存的緣故。還有遇到過(guò)另外一個(gè)坑就是染色后的圖標(biāo)再紅米的一個(gè)手機(jī)上無(wú)效慨菱,估計(jì)這個(gè)地方不同的硬件緩存的機(jī)制可能還不一樣。所以如果項(xiàng)目中有用到圖標(biāo)的染色需要注意戴甩。
如何在我們的項(xiàng)目中進(jìn)行渲染優(yōu)化符喝?
知道了我們的渲染的機(jī)制,我們知道整一個(gè)渲染的的流程甜孤,基本都是系統(tǒng)在處理协饲,流程我們沒(méi)辦法進(jìn)行干預(yù)畏腕。那么我們就需要針對(duì)渲染的原理,進(jìn)行一些針對(duì)性的優(yōu)化操作茉稠,減少我們每一幀的渲染時(shí)間描馅,使應(yīng)用更加流暢。所以平時(shí)除了我們都知道的阻塞UI線(xiàn)程導(dǎo)致卡頓而线,其實(shí)對(duì)于CPU及內(nèi)存的不合理使用铭污,也同樣會(huì)造成我們的卡頓。接下來(lái)我們來(lái)一一進(jìn)行分析膀篮。
內(nèi)存優(yōu)化
程序在任意幀內(nèi)執(zhí)行GCs所用的時(shí)間越多嘹狞,消除少于16毫秒的呈像障礙,所必需的時(shí)間就會(huì)變少誓竿,如果有許多GCs或一大串指令一個(gè)接一個(gè)地操作磅网,幀象時(shí)間很可能會(huì)超過(guò)16毫秒的呈像障礙,這會(huì)導(dǎo)致隱形的碰撞或閃躲筷屡。內(nèi)存在斷時(shí)間的抖動(dòng)也會(huì)造成我們的卡頓現(xiàn)象涧偷。
所以如果要減少任意幀內(nèi)啟動(dòng)GC的次數(shù),需要著重優(yōu)化程序的內(nèi)存使用量毙死。
我們?cè)趯?shí)際的項(xiàng)目中了已通過(guò)Monitor進(jìn)行內(nèi)存的抖動(dòng)分析燎潮,再通過(guò)分析源碼來(lái)看是否在某一時(shí)刻重復(fù)創(chuàng)建大量的對(duì)象,導(dǎo)致GC的回收规哲。
Tip
- 避免在循環(huán)里面重復(fù)創(chuàng)建對(duì)象
- 操作大量的字符跟啤,慎用String進(jìn)行+=,多使用StringBuilder及StringBuffer
- 多用池進(jìn)行進(jìn)行對(duì)象的復(fù)用
計(jì)算優(yōu)化
這是一個(gè)很淺顯的道理唉锌,我們知道渲染的過(guò)程需要CPU參與Ploygons與Texture的生成隅肥,假如我們將CPU的使用率長(zhǎng)時(shí)間壓榨得很高,自然就會(huì)影響我們的渲染袄简,造成UI卡頓腥放。
那么怎么來(lái)分析我們的計(jì)算優(yōu)化呢?
首先一個(gè)很簡(jiǎn)單绿语,可以看看是否在執(zhí)行某個(gè)操作的時(shí)候秃症,過(guò)分的壓榨了CPU的使用率,我們通過(guò)Android Monitor可以看到瞬時(shí)的CPU的使用率吕粹。
觀察到CPU使用率的異常后种柑,我們可以通過(guò)Traceview工具來(lái)查找并確定哪些是阻礙應(yīng)用程序性能問(wèn)題的代碼。
同樣開(kāi)DDMS視圖選擇我們要分析的應(yīng)用匹耕,這里箭頭所指向看上去像是三面箭頭聚请,上面有紅色的圓點(diǎn),如果按這些按鈕,會(huì)出現(xiàn)一些提示驶赏,說(shuō)將開(kāi)始進(jìn)行方法分析炸卑。這是TraceView的啟動(dòng)方法,我們點(diǎn)擊它煤傍。將出現(xiàn)一個(gè)彈出窗口盖文,提示有兩種方法來(lái)分析你的應(yīng)用程序。你可以記錄每個(gè)方法的輸入和輸出蚯姆,他們對(duì)資源的要求很高五续,或者,你也利用示例進(jìn)行一些分析蒋失。其含義是返帕,默認(rèn)情況下分析程序,將會(huì)每1000毫秒偵測(cè)一次你的應(yīng)用程序篙挽,以發(fā)現(xiàn)和記錄實(shí)際上在運(yùn)行的功能荆萤,現(xiàn)在,讓我們來(lái)使用這些默認(rèn)設(shè)置铣卡。我點(diǎn)擊一下OK链韭,既然分析程序已經(jīng)在繼續(xù),我們就與你的應(yīng)用程序進(jìn)行交互煮落,看能否記錄一些動(dòng)作敞峭。
我們來(lái)看跟蹤視圖,跟蹤視圖有兩個(gè)主要組成部分蝉仇。上方窗格的名稱(chēng)是timeline面板旋讹,下方窗格內(nèi)有很多的信息,稱(chēng)為profile面板轿衔。這個(gè)時(shí)間線(xiàn)能夠很好的顯示代碼的執(zhí)行情況沉迹,這里顯示的每一行,實(shí)際上對(duì)應(yīng)于一個(gè)線(xiàn)程害驹。顯示的每一個(gè)顏色鞭呕,對(duì)應(yīng)于一個(gè)正在運(yùn)行的特定方法。例如宛官,我們可以看到葫松,主線(xiàn)程的所有活動(dòng),我們可以看到方法啟動(dòng)和停止時(shí)間點(diǎn)底洗,更有用的是放大這里腋么,找到特定的方法,了解他們是如何執(zhí)行的亥揖。它們會(huì)以這種U型模式顯示出來(lái)党晋。這里的條形表示,方法的啟動(dòng)時(shí)間。右側(cè)的條形表示未玻,方法的停止時(shí)間。條形的寬度表示方法執(zhí)行所用的時(shí)間『兀現(xiàn)在扳剿,我們選擇一個(gè)特定的方法,我們跳轉(zhuǎn)到跟蹤視圖窗口的底部昼激,這里庇绽,我們看到一些分析數(shù)據(jù)顯示出來(lái)。我們可以看到哪些方法調(diào)用了我們選定的方法橙困。
底部面板的一些字段含義如下:
列名 | 作用 |
---|---|
Name | 該進(jìn)程運(yùn)行過(guò)程中所調(diào)用的函數(shù)名 |
Incl Cpu Time | 函數(shù)占用的CPU時(shí)間瞧掺,包含內(nèi)部調(diào)用其它函數(shù)的CPU時(shí)間 |
Excl Cpu Time | 函數(shù)占用的CPU時(shí)間,但不包含內(nèi)部調(diào)用其它函數(shù)所占用的CPU時(shí)間 |
Incl Real Time | 函數(shù)運(yùn)行的真實(shí)時(shí)間(以毫秒為單位)凡傅,內(nèi)含調(diào)用其它函數(shù)所占用的真實(shí)時(shí)間 |
Excl Real Time | 函數(shù)運(yùn)行的真實(shí)時(shí)間(以毫秒為單位)辟狈,不包含調(diào)用其它函數(shù)所占用的真實(shí)時(shí)間 |
Calls+Recur Calls/Total | 函數(shù)被調(diào)用次數(shù)以及遞歸調(diào)用占總調(diào)用次數(shù)的百分比 |
Cpu Time/Call | 函數(shù)調(diào)用CPU時(shí)間與調(diào)用次數(shù)的比(該函數(shù)平均執(zhí)行時(shí)間) |
Real Time/Call | 同CPU Time/Call類(lèi)似,只不過(guò)統(tǒng)計(jì)單位換成了真實(shí)時(shí)間 |
Tip
- 優(yōu)化一些計(jì)算的算法,例如遞歸等
- 使用線(xiàn)程池技術(shù)夏跷,避免過(guò)度壓榨CPU
- 使用批處理及緩存哼转,優(yōu)化CPU計(jì)算
CPU優(yōu)化
我們知道CPU在渲染的過(guò)程,主要需要處理Ploygons和Texture槽华。在CPU方面壹蔓,最常見(jiàn)的性能問(wèn)題是不必要的布局和失效,這些內(nèi)容必須在視圖層次結(jié)構(gòu)中進(jìn)行測(cè)量猫态、清除并重新創(chuàng)建佣蓉,引發(fā)這種問(wèn)題通常有兩個(gè)原因:一是重建顯示列表的次數(shù)太多,二是花費(fèi)太多時(shí)間作廢視圖層次并進(jìn)行不必要的重繪亲雪,這兩個(gè)原因在更新顯示列表或者其他緩存GPU資源時(shí)導(dǎo)致CPU工作過(guò)度勇凭。
引用Google官方示例圖。
所以我們需要進(jìn)行優(yōu)化的點(diǎn)有:
- 減少不必要布局元素
- 減少過(guò)多的布局嵌套
那么如何來(lái)知道匆光,我們的布局是否因?yàn)镃PU過(guò)度工作導(dǎo)致我們的渲染卡頓呢套像?
我們可以通過(guò)DDMS里面的Hierarchy Viewer 來(lái)進(jìn)行我們的布局分析。
1)通過(guò)AS的Tools-Android-Android Device Monitor調(diào)起
這個(gè)時(shí)候APP運(yùn)行到我們需要檢測(cè)的界面终息,這個(gè)點(diǎn)擊藍(lán)色的按鈕夺巩,就可以顯示當(dāng)前界面的View Tree
2)我們可以通過(guò)圖2箭頭指向來(lái)觀察我們的View布局、繪制周崭、渲染的時(shí)間
- 箭頭1為我們當(dāng)前View節(jié)點(diǎn)的界面柳譬,我們可以觀察當(dāng)前節(jié)點(diǎn)的渲染時(shí)間
- 箭頭2為觸發(fā)檢測(cè)渲染性能的按鈕
- 箭頭3為渲染性能的顯示,有綠续镇、黃美澳、紅三種顏色
三個(gè)圓點(diǎn)分別代表:測(cè)量、布局、繪制三個(gè)階段的性能表現(xiàn)制跟。
- 綠色:渲染的管道階段舅桩,這個(gè)視圖的渲染速度快于至少一半的其他的視圖。
- 黃色:渲染速度比較慢的50%雨膨。
- 紅色:渲染速度非常慢擂涛。
所以我們可以根據(jù)分析查看自己的布局,層次是否很深以及渲染比較耗時(shí)聊记,然后想辦法能否減少層級(jí)以及優(yōu)化每一個(gè)View的渲染時(shí)撒妈。
Tip
- 避免過(guò)來(lái)無(wú)用的布局嵌套,特別是ViewGroup層級(jí)盡量最小化
- 使用<merge>標(biāo)簽排监,減少布局嵌套
- 使用懶加載布局 ViewStub狰右,盡量減少使用View的GONE方式
- 注意一些自定義的View的性能,可通過(guò)工具的綠黃紅分析
GPU優(yōu)化
通過(guò)上面的流程我們知道舆床,GPU主要干的事情就是柵格化棋蚌,所以我們需要盡量盡量避免過(guò)度繪制(overdraw)。
我們?cè)陂_(kāi)發(fā)的過(guò)程中,經(jīng)常會(huì)遇到牛逼的設(shè)計(jì)峭弟,需要完善絢麗的UI附鸽。高性能和完美的設(shè)計(jì),往往會(huì)碰到一種性能問(wèn)題瞒瘸,即過(guò)度繪制坷备。過(guò)度繪制是一個(gè)術(shù)語(yǔ),指的是屏幕上的某個(gè)像素點(diǎn)在同一幀的時(shí)間內(nèi)被繪制了多次情臭。假如我們有一堆重疊的UI卡片省撑,最接近用戶(hù)的卡片在最上面,其余卡片都藏在下面俯在,也就是說(shuō)我們花大力氣繪制的那些下面的卡片基本都是不可見(jiàn)的竟秫。
我們借助Google官方的一個(gè)圖來(lái)進(jìn)行說(shuō)明
Android在屏幕上使用不同顏色,標(biāo)記過(guò)度繪制的區(qū)域跷乐,如果某個(gè)像素點(diǎn)只渲染了一次肥败,我們看到的是它原來(lái)的顏色,隨著過(guò)度繪制的增多愕提,標(biāo)記顏色也會(huì)逐漸加深馒稍,例如1倍過(guò)度繪制會(huì)被標(biāo)記為藍(lán)色,2倍浅侨、3倍纽谒、4倍過(guò)度繪制遵循同樣的模式。所以當(dāng)我們調(diào)試應(yīng)用程序的用戶(hù)界面時(shí)如输,目標(biāo)就是盡可能的減少過(guò)度繪制鼓黔,將紅色區(qū)塊轉(zhuǎn)變成藍(lán)色區(qū)塊央勒。
1)通過(guò)開(kāi)發(fā)者選項(xiàng)打開(kāi)過(guò)度繪制檢測(cè)
2)開(kāi)啟后就可以查看應(yīng)用的繪制情況
這里拿了百度網(wǎng)盤(pán)來(lái)做例子,還是優(yōu)化得不錯(cuò)澳化。
首先我們要從視圖中清除那些崔步,不必要的背景和圖片,他們不會(huì)在最終渲染圖像中顯示缎谷,這些都會(huì)影響性能刷晋。其次,對(duì)視圖中重疊的屏幕區(qū)域進(jìn)行定義慎陵,從而降低CPU和GPU的消耗。
Tip
- 由于我們布局設(shè)置了背景喻奥,同時(shí)用到的MaterialDesign的主題會(huì)默認(rèn)給一個(gè)背景席纽。可以在Activity設(shè)置getWindow().setBackgroundDrawable(null);
- 盡量保持你的布局只有一層擁有Background撞蚕,避免給過(guò)多的ViewGroup設(shè)置背景
- 如果是自定義控件可以通過(guò)裁剪來(lái)處理(Canvas.clipRect)润梯。
總結(jié)
- 盡量了解渲染的機(jī)制,在開(kāi)始做項(xiàng)目的時(shí)候就少挖坑
- 盡量動(dòng)手給自己現(xiàn)在的項(xiàng)目進(jìn)行優(yōu)化甥厦,這樣可以更深刻的理解
- 渲染優(yōu)化是一個(gè)苦逼的體力活纺铭,掌握了方法以后,我們需要花時(shí)間去一個(gè)個(gè)調(diào)優(yōu)