注:本文是我在 Android 界面性能調(diào)優(yōu)知識(shí)的系統(tǒng)性總結(jié)化焕,純屬個(gè)人碎碎念乐严。秉持開源分享的原則發(fā)布本文出來坛芽,各位看官有需則取。原文見:https://androidtest.org/android-graphics-performance-pattens/
界面是 Android 應(yīng)用中直接影響用戶體驗(yàn)最關(guān)鍵的部分七嫌。如果代碼實(shí)現(xiàn)得不好,界面容易發(fā)生卡頓且導(dǎo)致應(yīng)用占用大量內(nèi)存苞慢。
我司這類做 ROM 的公司更不一樣诵原,預(yù)裝的應(yīng)用一定要非常流暢,這樣給客戶或用戶的第一感覺就是快挽放。又卡又慢的應(yīng)用體驗(yàn)绍赛,會(huì)影響客戶或用戶對(duì)產(chǎn)品的信心和評(píng)價(jià),所以不可忽視辑畦。
目錄
一. Android渲染知識(shí)
1.1 繪制原理
Android系統(tǒng)要求每一幀都要在 16ms 內(nèi)繪制完成,平滑的完成一幀意味著任何特殊的幀需要執(zhí)行所有的渲染代碼(包括 framework 發(fā)送給 GPU 和 CPU 繪制到緩沖區(qū)的命令)都要在 16ms 內(nèi)完成航闺,保持流暢的體驗(yàn)褪测。這個(gè)速度允許系統(tǒng)在動(dòng)畫和輸入事件的過程中以約 60 幀每秒( 1秒 / 0.016幀每秒 = 62.5幀/秒 )的平滑幀率來渲染。
如果你的應(yīng)用沒有在 16ms 內(nèi)完成這一幀的繪制潦刃,假設(shè)你花了 24ms 來繪制這一幀侮措,那么就會(huì)出現(xiàn)掉幀的情況。
系統(tǒng)準(zhǔn)備將新的一幀繪制到屏幕上乖杠,但是這一幀并沒有準(zhǔn)備好分扎,所有就不會(huì)有繪制操作,畫面也就不會(huì)刷新胧洒。反饋到用戶身上畏吓,就是用戶盯著同一張圖看了 32ms 而不是 16ms 墨状,也就是說掉幀發(fā)生了。
1.2 掉幀
掉幀是用戶體驗(yàn)中一個(gè)非常核心的問題菲饼。丟棄了當(dāng)前幀肾砂,并且之后不能夠延續(xù)之前的幀率,這種不連續(xù)的間隔會(huì)容易會(huì)引起用戶的注意宏悦,也就是我們常說的卡頓镐确、不流暢。
引起掉幀的原因非常多饼煞,比如:
花了非常多時(shí)間重新繪制界面中的大部分東西源葫,這樣非常浪費(fèi)CPU周期;
過度繪制嚴(yán)重砖瞧,在繪制用戶看不到的對(duì)象上花費(fèi)了太多的時(shí)間息堂;
有一大堆動(dòng)畫重復(fù)了一遍又一遍,消耗 CPU 块促、 GPU 資源储矩;
頻繁的觸發(fā)垃圾回收;
1.3 為什么是60Fps褂乍?
Android系統(tǒng)要求每一幀都要在 16ms 內(nèi)繪制完成持隧,那么1秒的幀率就是約 60 幀每秒( 1秒 / 0.016幀每秒 = 62.5幀/秒 ),那為什么要以 60 Fps來作為 App 性能的衡量標(biāo)準(zhǔn)呢逃片?這是因?yàn)槿搜酆痛竽X之間的協(xié)作無法感知到超過 60 Fps的畫面更新屡拨。
市面上絕大多數(shù)Android設(shè)備的屏幕刷新頻率是 60 HZ。當(dāng)然褥实,超過 60 Fps 是沒有意義的呀狼,人眼感知不到區(qū)別。24 Fps 是人眼能感知的連續(xù)線性的運(yùn)動(dòng)损离,所以是電影膠圈的常用幀率哥艇,因?yàn)檫@個(gè)幀率已經(jīng)足夠支撐大部分電影畫面所要表達(dá)的內(nèi)容,同時(shí)能最大限度地減少費(fèi)用支出僻澎。但是貌踏,低于 30 Fps 是無法順暢表現(xiàn)絢麗的畫面內(nèi)容的,此時(shí)就需要用到 60 Fps 來達(dá)到想要表達(dá)的效果窟勃。了解更多Fps知識(shí)詳見「 Wiki 」祖乳。
應(yīng)用的界面性能目標(biāo)就是保持 60 Fps,這意味著每一幀你只有 16 ms(1秒 / 60幀率)的時(shí)間來處理所有的任務(wù)秉氧。
1.4 垃圾回收
垃圾回收器是一個(gè)在應(yīng)用運(yùn)行期間自動(dòng)釋放那些不再引用的內(nèi)存的機(jī)制眷昆,常稱 GC 。頻繁的 GC 也是導(dǎo)致嚴(yán)重性能問題的罪魁禍?zhǔn)字弧?/p>
前面提到,平滑的完成一幀意味著所有渲染代碼都必須在 16ms 內(nèi)完成亚斋。頻繁的 GC 會(huì)嚴(yán)重限制一幀時(shí)間內(nèi)的剩余時(shí)間作媚,如果 GC 所做的工作超過了那些必須的工作,那么留給應(yīng)用平滑的幀率的時(shí)間就越少帅刊。越接近 16ms 纸泡,在垃圾回收事件觸發(fā)的時(shí)候,就越容易導(dǎo)致卡頓厚掷。
注意弟灼,Android4.4 引進(jìn)了新的 ART 虛擬機(jī)來取代 Dalvik 虛擬機(jī)级解。它們的機(jī)制大有不同冒黑,簡(jiǎn)單而言:
Dalvik 虛擬機(jī)的 GC 是非常耗資源的,并且在正常的情況下一個(gè)硬件性能不錯(cuò)的Android設(shè)備也會(huì)很容易耗費(fèi)掉 10 - 20 ms 的時(shí)間勤哗;
ART 虛擬機(jī)的GC會(huì)動(dòng)態(tài)提升垃圾回收的效率抡爹,在 ART 中的中斷,通常在 2 - 3 ms 間芒划。 比 Dalvik 虛擬機(jī)有很大的性能提升冬竟;
ART 虛擬機(jī)相對(duì)于 Dalvik 虛擬機(jī)來說的垃圾回收來說有一個(gè)很大的性能提升,但 2 - 3 ms 的回收時(shí)間對(duì)于超過16ms幀率的界限也是足夠的民逼。因此泵殴,盡管垃圾回收在 Android 5.0 之后不再是耗資源的行為,但也是始終需要盡可能避免的拼苍,特別是在執(zhí)行動(dòng)畫的情況下笑诅,可能會(huì)導(dǎo)致一些讓用戶明顯感覺的丟幀。
想了解更多詳細(xì)的 ART 和 Dalvik 虛擬機(jī)垃圾回收機(jī)制疮鲫,可「 戳我 」和「 我 」進(jìn)行深入了解吆你。
1.5 UI 線程
UI 線程是應(yīng)用的主線程,很多的性能和卡頓問題是由于我們?cè)谥骶€程中做了大量的工作俊犯。
所以妇多,所有耗資源的操作,比如 IO 操作燕侠、網(wǎng)絡(luò)操作者祖、SQL 操作、列表刷新等绢彤,都應(yīng)該用后臺(tái)進(jìn)程去實(shí)現(xiàn)咸包,不能占用主線程,主線程是 UI 線程杖虾,是保持程序流暢的關(guān)鍵烂瘫;
在 Android 5.0 版本里,Android 框架層引入了 “ Render Thread ” ,用于向 GPU 發(fā)送實(shí)際渲染的操作坟比。這個(gè)線程減輕了一些 UI 線程減少的操作芦鳍。但是輸入、滾動(dòng)和動(dòng)畫仍然在 UI thread葛账,因?yàn)?Thread 必須能夠響應(yīng)操作柠衅。
1.6 垂直同步
垂直同步是 Android4.1 通過 Project Butter 在 UI 架構(gòu)中引入的新技術(shù),同期引入的還有 Triple Buffer 和 HWComposer 等技術(shù)籍琳,都是為提高 UI 的流暢性而生菲宴。
舉個(gè)例子,你拍了一張照片趋急,然后旋轉(zhuǎn)5度再拍另外一張照片喝峦,將兩照片的中間剪開并拼接在一起,得到下圖:
中間這部分有明顯區(qū)別的部分呜达,等價(jià)于設(shè)備刷新率和幀速率不一致的結(jié)果谣蠢。
一般而言, GPU 的幀速率應(yīng)高于刷新率查近,才不會(huì)卡頓或掉幀眉踱。如果屏幕刷新率比幀速率還快,屏幕會(huì)在兩幀中顯示同一個(gè)畫面霜威,這種斷斷續(xù)續(xù)情況持續(xù)發(fā)生時(shí)谈喳,用戶將會(huì)很明顯地感覺到動(dòng)畫的卡頓或者掉幀,然后又恢復(fù)正常戈泼,我們常稱之為閃屏婿禽、跳幀、延遲矮冬。
應(yīng)用應(yīng)避免這些幀率下降的情況谈宛,以確保 GPU 能在屏幕刷新之前完成數(shù)據(jù)的獲取及寫入,保證動(dòng)畫流暢胎署。
1.7 UI 繪制機(jī)制與柵格化
絕大多數(shù)渲染操作都依賴兩個(gè)硬件: CPU 吆录、 GPU 。 CPU 負(fù)責(zé) Measure 琼牧、 layout 恢筝、 Record 、 Execute 的計(jì)算操作巨坊, GPU 負(fù)責(zé)柵格化( Rasterization )操作撬槽。 非必需的視圖組件會(huì)帶來多余的 CPU 計(jì)算操作,還會(huì)占用多余的 GPU 資源趾撵。
柵格化( Rasterization )能將 Button 侄柔、 Shape 共啃、 Path 、 Bitmap 等資源組件拆分到不同的像素上進(jìn)行顯示暂题。這個(gè)操作很費(fèi)時(shí)移剪,所以引入了 GPU 來加快柵格化的操作。
CPU 負(fù)責(zé)把 UI 組件計(jì)算成多邊形( Polygons )薪者,紋理( Texture )纵苛,然后交給 GPU 進(jìn)行柵格化渲染,再將處理結(jié)果傳到屏幕上顯示言津。
在 Android 里的那些資源組件的顯示(比如 Bitmaps 攻人、 Drawable ),都是一起打包到統(tǒng)一的紋理( Texture )當(dāng)中悬槽,然后再傳遞到 GPU 里面怀吻。
圖片的顯示,則是先經(jīng)過 CPU 的計(jì)算加載到內(nèi)存中陷谱,再傳給 GPU 進(jìn)行渲染烙博。
文字的顯示瑟蜈,則是先經(jīng)過 CPU 換算成紋理( Texture )烟逊,再傳給 GPU 進(jìn)行渲染,返回到 CPU 繪制單個(gè)字符的時(shí)候铺根,再重新引用經(jīng)過 GPU 渲染的內(nèi)容宪躯。
動(dòng)畫的顯示更加復(fù)雜,我們需要在 16 ms 內(nèi)處理完所有 CPU 和 GPU 的計(jì)算位迂、繪制访雪、渲染等操作,才能獲得應(yīng)用的流暢體驗(yàn)掂林。
二. To檢測(cè)和解決
2.1 檢測(cè)維度
根據(jù)業(yè)務(wù)的不同與所需要的測(cè)試粒度的不同臣缀,就會(huì)有不同的檢測(cè)維度。目前我所在業(yè)務(wù)所需的界面性能檢測(cè)維度如下:
界面過度繪制泻帮;(檢測(cè)過度繪制)
渲染性能精置;(檢測(cè)嚴(yán)格模式下的UI渲染性能呈現(xiàn))
布局邊界合理性;(檢測(cè)元素顯示的合理性)
還有專項(xiàng)測(cè)試中某些用戶場(chǎng)景可能還包含著另外一些隱形的檢測(cè)維度锣杂,比如:
OpenGL 跟蹤分析脂倦;
GPU 視圖更新合理性;
Flash 硬件層更新合理性元莫;
動(dòng)畫加 / 減速狀態(tài)問題點(diǎn)檢測(cè)赖阻;
……
2.2 調(diào)試工具
檢測(cè)和解決界面性能問題很大程度上依賴于你的應(yīng)用程序架構(gòu),幸運(yùn)的是踱蠢,Andorid 提供了很多調(diào)試工具火欧,知道并學(xué)會(huì)使用這些工具很重要,它們可以幫助我們調(diào)試和分析界面性能問題,以讓應(yīng)用擁有更好的性能體驗(yàn)苇侵。下面列舉Android常見的界面性能調(diào)試工具:
2.2.1 Hierarchy View
Hierarchy View 在Android SDK里自帶离陶,常用來查看界面的視圖結(jié)構(gòu)是否過于復(fù)雜,用于了解哪些視圖過度繪制衅檀,又該如何進(jìn)行改進(jìn)招刨。詳見官方使用教程(需要翻墻):「 戳我 」,官方介紹「 戳我 」哀军。
2.2.2 Lint
Lint 是 ADT 自帶的靜態(tài)代碼掃描工具沉眶,可以給 XML 布局文件和 項(xiàng)目代碼中不合理的或存在風(fēng)險(xiǎn)的模塊提出改善性建議。官方關(guān)于 Lint 的實(shí)際使用的提示杉适,列舉幾點(diǎn)如下:
包含無用的分支谎倔,建議去除;
包含無用的父控件猿推,建議去除片习;
警告該布局深度過深;
建議使用 compound drawables 蹬叭;
建議使用 merge
標(biāo)簽藕咏;
……
更多 Lint 的官方介紹「 戳我 」。
2.2.3 Systrace
Systrace 在Android DDMS 里自帶秽五,可以用來跟蹤 graphics 孽查、view 和 window 的信息,發(fā)現(xiàn)一些深層次的問題坦喘。很麻煩盲再,限制大,實(shí)際調(diào)試中我基本用不到瓣铣。官方介紹 「戳我」和 「我」答朋。
2.2.4 Track
Track 在 Android DDMS里自帶,是個(gè)很棒的用來跟蹤構(gòu)造視圖的時(shí)候哪些方法費(fèi)時(shí)棠笑,精確到每一個(gè)函數(shù)梦碗,無論是應(yīng)用函數(shù)還是系統(tǒng)函數(shù),我們可以很容易地看到掉幀的地方以及那一幀所有函數(shù)的調(diào)用情況腐晾,找出問題點(diǎn)進(jìn)行優(yōu)化叉弦。官方介紹 「戳我」。
2.2.5 OverDraw
通過在 Android 設(shè)備的設(shè)置 APP 的開發(fā)者選項(xiàng)里打開 “ 調(diào)試 GPU 過度繪制 ” 藻糖,來查看應(yīng)用所有界面及分支界面下的過度繪制情況淹冰,方便進(jìn)行優(yōu)化。官方介紹 「戳我」巨柒。
2.2.6 GPU 呈現(xiàn)模式分析
通過在 Android 設(shè)備的設(shè)置 APP 的開發(fā)者選項(xiàng)里啟動(dòng) “ GPU 呈現(xiàn)模式分析 ” 樱拴,可以得到最近 128 幀 每一幀渲染的時(shí)間柠衍,分析性能渲染的性能及性能瓶頸。官方介紹 「戳我」晶乔。
2.2.7 StrictMode
通過在 Android 設(shè)備的設(shè)置 APP 的開發(fā)者選項(xiàng)里啟動(dòng) “ 嚴(yán)格模式 ” 珍坊,來查看應(yīng)用哪些操作在主線程上執(zhí)行時(shí)間過長。當(dāng)一些操作違背了嚴(yán)格模式時(shí)屏幕的四周邊界會(huì)閃爍紅色正罢,同時(shí)輸出 StrictMode 的相關(guān)信息到 LOGCAT 日志中阵漏。
2.2.8 Animator duration scale
通過在 Android 設(shè)備的設(shè)置 APP 的開發(fā)者選項(xiàng)里打開 “ 窗口動(dòng)畫縮放 ” / “ 過渡動(dòng)畫縮放 ” / “ 動(dòng)畫程序時(shí)長縮放 ”,來加速或減慢動(dòng)畫的時(shí)間翻具,以查看加速或減慢狀態(tài)下的動(dòng)畫是否會(huì)有問題履怯。
2.2.9 Show hardware layer updates
通過在 Android 設(shè)備的設(shè)置 APP 的開發(fā)者選項(xiàng)里啟動(dòng) “ 顯示硬件層更新 ”,當(dāng) Flash 硬件層在進(jìn)行更新時(shí)會(huì)顯示為綠色裆泳。使用這個(gè)工具可以讓你查看在動(dòng)畫期間哪些不期望更新的布局有更新叹洲,方便你進(jìn)行優(yōu)化,以獲得應(yīng)用更好的性能工禾。實(shí)例《 Optimizing Android Hardware Layers 》(需要翻墻):「 戳我 」运提。
2.3 如何解決
前面提到過我司的目前所需的測(cè)試維度如下:
界面過度繪制;(檢測(cè)過度繪制)
渲染性能闻葵;(檢測(cè)嚴(yán)格模式下的UI渲染性能呈現(xiàn))
布局邊界合理性民泵;(檢測(cè)元素顯示的合理性)
故接下來將圍繞這三兩點(diǎn),分別從概念笙隙、追蹤洪灯、挖掘根源以及排查的工具來具體講述如何解決坎缭,以及給開發(fā)的優(yōu)化建議竟痰。
三. 界面過度繪制(OverDraw)
3.1 過度繪制概念
過渡繪制是一個(gè)術(shù)語,表示某些組件在屏幕上的一個(gè)像素點(diǎn)的繪制次數(shù)超過 1 次掏呼。
通俗來講坏快,繪制界面可以類比成一個(gè)涂鴉客涂鴉墻壁,涂鴉是一件工作量很大的事情憎夷,墻面的每個(gè)點(diǎn)在涂鴉過程中可能被涂了各種各樣的顏色莽鸿,但最終呈現(xiàn)的顏色卻只可能是 1 種。這意味著我們花大力氣涂鴉過程中那些非最終呈現(xiàn)的顏色對(duì)路人是不可見的拾给,是一種對(duì)時(shí)間祥得、精力和資源的浪費(fèi),存在很大的改善空間蒋得。繪制界面同理级及,花了太多的時(shí)間去繪制那些堆疊在下面的、用戶看不到的東西额衙,這樣是在浪費(fèi)CPU周期和渲染時(shí)間饮焦!
官方例子怕吴,被用戶激活的卡片在最上面,而那些沒有激活的卡片在下面,在繪制用戶看不到的對(duì)象上花費(fèi)了太多的時(shí)間县踢。
3.2 追蹤過度繪制
通過在 Android 設(shè)備的設(shè)置 APP 的開發(fā)者選項(xiàng)里打開 “ 調(diào)試 GPU 過度繪制 ” 转绷,來查看應(yīng)用所有界面及分支界面下的過度繪制情況,方便進(jìn)行優(yōu)化硼啤。
Android 會(huì)在屏幕上顯示不同深淺的顏色來表示過度繪制:
沒顏色:沒有過度繪制议经,即一個(gè)像素點(diǎn)繪制了 1 次,顯示應(yīng)用本來的顏色谴返;
藍(lán)色:1倍過度繪制爸业,即一個(gè)像素點(diǎn)繪制了 2 次;
綠色:2倍過度繪制亏镰,即一個(gè)像素點(diǎn)繪制了 3 次扯旷;
淺紅色:3倍過度繪制,即一個(gè)像素點(diǎn)繪制了 4 次索抓;
深紅色:4倍過度繪制及以上钧忽,即一個(gè)像素點(diǎn)繪制了 5 次及以上;
設(shè)備的硬件性能是有限的逼肯,當(dāng)過度繪制導(dǎo)致應(yīng)用需要消耗更多資源(超過了可用資源)的時(shí)候性能就會(huì)降低耸黑,表現(xiàn)為卡頓、不流暢篮幢、ANR 等大刊。為了最大限度地提高應(yīng)用的性能和體驗(yàn),就需要盡可能地減少過度繪制三椿,即更多的藍(lán)色色塊而不是紅色色塊缺菌。
實(shí)際測(cè)試,常用以下兩點(diǎn)來作為過度繪制的測(cè)試指標(biāo)搜锰,將過度繪制控制在一個(gè)約定好的合理范圍內(nèi):
應(yīng)用所有界面以及分支界面均不存在超過4X過度繪制(深紅色區(qū)域)伴郁;
應(yīng)用所有界面以及分支界面下,3X過度繪制總面積(淺紅色區(qū)域)不超過屏幕可視區(qū)域的1/4蛋叼;
3.3 過渡繪制的根源
過度繪制很大程度上來自于視圖相互重疊的問題焊傅,其次還有不必要的背景重疊。
官方例子狈涮,比如一個(gè)應(yīng)用所有的View都有背景的話狐胎,就會(huì)看起來像第一張圖中那樣,而在去除這些不必要的背景之后(指的是Window的默認(rèn)背景歌馍、Layout的背景握巢、文字以及圖片的可能存在的背景),效果就像第二張圖那樣骆姐,基本沒有過度繪制的情況镜粤。
3.4 不合理的xml布局對(duì)繪制的影響
當(dāng)布局文件的節(jié)點(diǎn)樹的深度越深捏题,XML 中的標(biāo)簽和屬性設(shè)置越多,對(duì)界面的顯示有災(zāi)難性影響肉渴。
一個(gè)界面要顯示出來公荧,第一步會(huì)進(jìn)行解析布局,在 requestLayout 之后還要進(jìn)行一系列的 measure 同规、 layout 循狰、 draw 操作,若布局文件嵌套過深券勺、擁有的標(biāo)簽屬性過于臃腫绪钥,每一步的執(zhí)行時(shí)間都會(huì)受到影響,而界面的顯示是進(jìn)行完這些操作后才會(huì)顯示的关炼,所以每一步操作的時(shí)間增長程腹,最終顯示的時(shí)間就會(huì)越長。
3.5 源碼相關(guān)
有能力且有興趣看源碼的童鞋儒拂,過度繪制的源碼位置在: /frameworks/base/libs/hwui/OpenGLRenderer.cpp 寸潦,有興趣的可以去研究查看。
left, mState.firstSnapshot()->getViewportHeight() - clip->bottom, clip->right - clip->left, clip->bottom - clip->top); // 1x overdraw mRenderState.stencil().enableDebugTest(2); drawColor(mCaches.getOverdrawColor(1), SkXfermode::kSrcOver_Mode); // 2x overdraw mRenderState.stencil().enableDebugTest(3); drawColor(mCaches.getOverdrawColor(2), SkXfermode::kSrcOver_Mode); // 3x overdraw mRenderState.stencil().enableDebugTest(4); drawColor(mCaches.getOverdrawColor(3), SkXfermode::kSrcOver_Mode); // 4x overdraw and higher mRenderState.stencil().enableDebugTest(4, true); drawColor(mCaches.getOverdrawColor(4), SkXfermode::kSrcOver_Mode); mRenderState.stencil().disable(); } }">
if (Properties::debugOverdraw && getTargetFbo() == 0) {
const Rect* clip = &mTilingClip;
mRenderState.scissor().setEnabled(true);
mRenderState.scissor().set(clip->left,
mState.firstSnapshot()->getViewportHeight() - clip->bottom,
clip->right - clip->left,
clip->bottom - clip->top);
// 1x overdraw
mRenderState.stencil().enableDebugTest(2);
drawColor(mCaches.getOverdrawColor(1), SkXfermode::kSrcOver_Mode);
// 2x overdraw
mRenderState.stencil().enableDebugTest(3);
drawColor(mCaches.getOverdrawColor(2), SkXfermode::kSrcOver_Mode);
// 3x overdraw
mRenderState.stencil().enableDebugTest(4);
drawColor(mCaches.getOverdrawColor(3), SkXfermode::kSrcOver_Mode);
// 4x overdraw and higher
mRenderState.stencil().enableDebugTest(4, true);
drawColor(mCaches.getOverdrawColor(4), SkXfermode::kSrcOver_Mode);
mRenderState.stencil().disable();
}
}
四. 渲染性能(Rendering)
4.1 渲染性能概念
渲染性能往往是掉幀的罪魁禍?zhǔn)咨缤矗@種問題很常見见转,讓人頭疼。好在 Android 給我們提供了一個(gè)強(qiáng)大的工具蒜哀,幫助我們非常容易追蹤性能渲染問題斩箫,看到究竟是什么導(dǎo)致你的應(yīng)用出現(xiàn)卡頓、掉幀撵儿。
4.2 追蹤渲染性能
通過在 Android 設(shè)備的設(shè)置 APP 的開發(fā)者選項(xiàng)里打開 “ GPU 呈現(xiàn)模式分析 ” 選項(xiàng)乘客,選擇 ” 在屏幕上顯示為條形圖 “ 。
這個(gè)工具會(huì)在Android 設(shè)備的屏幕上實(shí)時(shí)顯示當(dāng)前界面的最近 128 幀 的 GPU 繪制圖形數(shù)據(jù)统倒,包括 StatusBar 寨典、 NavBar 、 當(dāng)前界面的 GPU 繪制圖形柱狀圖數(shù)據(jù)房匆。我們一般只需關(guān)心當(dāng)前界面的 GPU 繪制圖形數(shù)據(jù)即可。
界面上一共有 128 個(gè)小柱狀圖报亩,代表的是當(dāng)前界面最近的 128 幀 GPU 繪制圖形數(shù)據(jù)浴鸿。一個(gè)小柱狀圖代表的這一幀畫面渲染的耗時(shí),柱狀圖越高代表耗時(shí)越長弦追。隨著界面的刷新岳链,柱狀圖信息也會(huì)實(shí)時(shí)滾動(dòng)刷新。
中間有一條綠線,代表 16 ms 糟描,保持動(dòng)畫流暢的關(guān)鍵就在于讓這些垂直的柱狀條盡可能地保持在綠線下面,任何時(shí)候超過綠線,你就有可能丟失一幀的內(nèi)容钧舌。
每一個(gè)柱狀圖都是由三種顏色構(gòu)成:藍(lán)叁扫、紅吼鱼、黃供鸠。
藍(lán)色代表的是這一幀繪制 Display List 的時(shí)間密末。通俗來說抢腐,就是記錄了需要花費(fèi)多長時(shí)間在屏幕上更新視圖摔癣。用代碼語言來說奴饮,就是執(zhí)行視圖的 onDraw 方法,創(chuàng)建或更新每一個(gè)視圖的 Display List 的時(shí)間择浊。
紅色代表的是這一幀 OpenGL 渲染 Display List 所需要的時(shí)間戴卜。通俗來說,就是記錄了執(zhí)行視圖繪制的耗時(shí)琢岩。用代碼語言來說投剥,就是 Android 用 OpenGL ES 的 API 接口進(jìn)行 2D 渲染 Display List 的時(shí)間。
黃色代表的是這一幀 CPU 等待 GPU 處理的時(shí)間担孔。通俗來說薇缅,就是 CPU 等待 GPU 發(fā)出接到命令的回復(fù)的等待時(shí)間。用代碼語言來說攒磨,就是這是一個(gè)阻塞調(diào)用泳桦。
實(shí)際測(cè)試,常用以下兩點(diǎn)來作為渲染性能的測(cè)試指標(biāo)娩缰,將渲染性能控制在一個(gè)約定好的合理范圍內(nèi):
執(zhí)行應(yīng)用的所有功能及分支功能灸撰,操作過程中涉及的柱狀條區(qū)域應(yīng)至少 90 % 保持到綠線下面;
從用戶體檢的角度主觀判斷應(yīng)用在 512 M 內(nèi)存的 Android 設(shè)備下所有操作過程中的卡頓感是否能接受拼坎,不會(huì)感覺突兀怪異浮毯;
4.3 渲染性能差的根源
當(dāng)你看到藍(lán)色的線較高的時(shí)候,可能是由于你的視圖突然無效了需要重新繪制泰鸡,或者是自定義的視圖過于復(fù)雜耗時(shí)過長债蓝。
當(dāng)你看到紅色的線較高的時(shí)候,可能是由于你的視圖重新提交了需要重新繪制導(dǎo)致的(比如屏幕從豎屏旋轉(zhuǎn)成橫屏后當(dāng)前界面重新創(chuàng)建)盛龄,或者是自定義的視圖很復(fù)雜饰迹,繪制起來很麻煩,導(dǎo)致耗時(shí)過長余舶。比如下面這種視圖:
當(dāng)你看到橙色的線較高的時(shí)候啊鸭,那就意味著你給 GPU 太多的工作,太多的負(fù)責(zé)視圖需要 OpenGL 命令去繪制和處理匿值,導(dǎo)致 CPU 遲遲沒等到 GPU 發(fā)出接到命令的回復(fù)赠制。
4.4 檢測(cè)說明
這個(gè)工具能夠很好地幫助你找到渲染相關(guān)的問題,幫助你找到卡頓的性能瓶頸挟憔,追蹤究竟是什么導(dǎo)致被測(cè)應(yīng)用出現(xiàn)卡頓钟些、變慢的情況烟号,以便在代碼層面進(jìn)行優(yōu)化。甚至讓負(fù)責(zé)產(chǎn)品設(shè)計(jì)的人去改善他的設(shè)計(jì)政恍,以獲得良好的用戶體驗(yàn)汪拥。
檢測(cè)渲染性能時(shí),常伴隨著開啟“ 嚴(yán)格模式 ” 查看應(yīng)用哪些情景在 UI 線程(主線程)上執(zhí)行時(shí)間過長抚垃。
另外有些強(qiáng)大但可能少用的工具在測(cè)試性能渲染時(shí)輔助分析喷楣,比如:
HierarchyViewer:這個(gè)工具常用來查看界面的視圖結(jié)構(gòu)是否過于復(fù)雜,用于了解哪些視圖過度繪制鹤树,又該如何進(jìn)行改進(jìn)铣焊;
Tracer for OpenGL:這個(gè)工具收集了所有UI界面發(fā)給GPU的繪制命令。常用于輔助開發(fā)人員 DEBUG 罕伯、定位一些 HierarchyViewer 工具定位不了的疑難渲染細(xì)節(jié)問題曲伊。
4.5 UI繪制機(jī)制的補(bǔ)充說明
如上面所說,布局和 UI 組件等都會(huì)先經(jīng)過 CPU 計(jì)算成 GPU 能夠識(shí)別并繪制的多邊形( Polygons )追他,紋理( Texture )坟募,然后交給 GPU 進(jìn)行柵格化渲染,再將處理結(jié)果傳到屏幕上顯示邑狸。 “ CPU 計(jì)算成 GPU 能夠識(shí)別并繪制的對(duì)象 ” 這個(gè)操作是在 DisplayList 的幫助下完成的懈糯。DisplayList 擁有要交給 GPU 柵格化渲染到屏幕上的數(shù)據(jù)信息。
DisplayList 會(huì)在某個(gè)視圖第一次需要渲染時(shí)創(chuàng)建单雾。當(dāng)該視圖有類似位置被移動(dòng)等變化而需要重新渲染這個(gè)視圖的時(shí)候赚哗,則只需 GPU 額外執(zhí)行一次渲染指令冰更新到屏幕上就夠了。但如果視圖中的繪制內(nèi)容發(fā)生變化時(shí)(比如不可見了)硅堆,那之間的 DisplayList 就無法繼續(xù)使用了屿储,這時(shí)系統(tǒng)就會(huì)重新執(zhí)行一次重新創(chuàng)建 DisplayList 、渲染DisplayList 并更新到屏幕上渐逃。這個(gè)流程的表現(xiàn)性能取決于該視圖的復(fù)雜程度够掠。
五. 布局邊界合理性
這一章節(jié)比較簡(jiǎn)單,考慮到應(yīng)該沒受眾茄菊,故不展開討論疯潭。
六. 給開發(fā)的界面優(yōu)化 Advice
6.1 優(yōu)化布局的結(jié)構(gòu)
布局結(jié)構(gòu)太復(fù)雜,會(huì)減慢渲染的速度买羞,造成性能瓶頸袁勺。我們可以通過以下這些慣用、有效的布局原則來優(yōu)化:
避免復(fù)雜的View層級(jí)畜普。布局越復(fù)雜就越臃腫,就越容易出現(xiàn)性能問題群叶,尋找最節(jié)省資源的方式去展示嵌套的內(nèi)容吃挑;
盡量避免在視圖層級(jí)的頂層使用相對(duì)布局 RelativeLayout
钝荡。相對(duì)布局 RelativeLayout
比較耗資源,因?yàn)橐粋€(gè)相對(duì)布局 RelativeLayout
需要兩次度量來確保自己處理了所有的布局關(guān)系舶衬,而且這個(gè)問題會(huì)伴隨著視圖層級(jí)中的相對(duì)布局 RelativeLayout
的增多埠通,而變得更嚴(yán)重;
布局層級(jí)一樣的情況建議使用線性布局 LinearLayout
代替相對(duì)布局 RelativeLayout
*逛犹,因?yàn)榫€性布局 LinearLayout
性能要更高一些端辱;確實(shí)需要對(duì)分支進(jìn)行相對(duì)布局 RelativeLayout
的時(shí)候,可以考慮更優(yōu)化的網(wǎng)格布局 GridLayout
虽画,它已經(jīng)預(yù)處理了分支視圖的關(guān)系舞蔽,可以避免兩次度量的問題;
相對(duì)復(fù)雜的布局建議采用相對(duì)布局 RelativeLayout
码撰,相對(duì)布局 RelativeLayout
可以簡(jiǎn)單實(shí)現(xiàn)線性布局 LinearLayout
嵌套才能實(shí)現(xiàn)的布局渗柿;
不要使用絕對(duì)布局 AbsoluteLayout
;
將可重復(fù)使用的組件抽取出來并用 </include>
標(biāo)簽進(jìn)行重用脖岛。如果應(yīng)用多個(gè)地方的 UI 用到某個(gè)布局朵栖,就將其寫成一個(gè)布局部件,便于各個(gè) UI 重用柴梆。官方詳解 「 戳我 」
使用 merge
標(biāo)簽減少布局的嵌套層次陨溅,官方詳解 「 戳我 」;
去掉多余的不可見背景绍在。有多層背景顏色的布局门扇,只留最上層的對(duì)用戶可見的顏色即可,其他用戶不可見的底層顏色可以去掉揣苏,減少無效的繪制操作悯嗓;
盡量避免使用 layout_weight 屬性。使用包含 layout_weight 屬性的線性布局 LinearLayout
每一個(gè)子組件都需要被測(cè)量兩次卸察,會(huì)消耗過多的系統(tǒng)資源脯厨。在使用 ListView
標(biāo)簽與 GridView
標(biāo)簽的時(shí)候,這個(gè)問題顯的尤其重要坑质,因?yàn)樽咏M件會(huì)重復(fù)被創(chuàng)建合武。平分布局可以使用相對(duì)布局 RelativeLayout
里一個(gè) 0dp 的 view 做分割線來搞定,如果不行涡扼,那就……稼跳;
合理的界面的布局結(jié)構(gòu)應(yīng)是寬而淺,而不是窄而深吃沪;
6.2 優(yōu)化處理邏輯
按需載入視圖汤善。某些不怎么重用的耗資源視圖,可以等到需要的時(shí)候再加載,提高UI渲染速度红淡;
使用 ViewStub
標(biāo)簽來加載一些不常用的布局不狮;
動(dòng)態(tài)地 inflation view 性能要比用 ViewStub
標(biāo)簽的 setVisiblity 性能要好,當(dāng)然某些功能的實(shí)現(xiàn)采用 ViewStub
標(biāo)簽更合適在旱;
盡量避免不必要的耗資源操作摇零,節(jié)省寶貴的運(yùn)算時(shí)間;
避免在 UI 線程進(jìn)行繁重的操作桶蝎。耗資源的操作(比如 IO 操作驻仅、網(wǎng)絡(luò)操作、SQL 操作登渣、列表刷新等)耗資源的操作應(yīng)用后臺(tái)進(jìn)程去實(shí)現(xiàn)噪服,不能占用 UI 線程,UI 線程是主線程绍豁,主線程是保持程序流暢的關(guān)鍵芯咧,應(yīng)該只操作那些核心的 UI 操作,比如處理視圖的屬性和繪制竹揍;
最小化喚醒機(jī)制敬飒。我們常用廣播來接收那些期望響應(yīng)的消息和事件,但過多的響應(yīng)超過本身需求的話芬位,會(huì)消耗多余的 Android 設(shè)備性能和資源无拗。所以應(yīng)該最小化喚醒機(jī)制,當(dāng)應(yīng)用不關(guān)心這些消失和事件時(shí)昧碉,就關(guān)閉廣播英染,并慎重選擇那些要響應(yīng)的 Intent 。
為低端設(shè)備考慮被饿,比如 512M 內(nèi)存四康、雙核 CPU 、低分辨率狭握,確保你的應(yīng)用可以滿足不同水平的設(shè)備闪金。
優(yōu)化應(yīng)用的啟動(dòng)速度。當(dāng)應(yīng)用啟動(dòng)一個(gè)應(yīng)用時(shí)论颅,界面的盡快反饋顯示可以給用戶一個(gè)良好的體驗(yàn)哎垦。為了啟動(dòng)更快,可以延遲加載一些 UI 以及避免在應(yīng)用 Application
層級(jí)初始化代碼恃疯。
6.3 擅用 DEBUG 工具
多使用Android提供的一些調(diào)試工具去追蹤應(yīng)用主要功能的性能情況漏设;
多使用Android提供的一些調(diào)試工具去追蹤應(yīng)用主要功能的內(nèi)存分配情況;