多數(shù)情況下,卡頓發(fā)生的根本原因,是渲染問題拆宛,即系統(tǒng)無法及時的完成復(fù)雜界面的渲染操作。系統(tǒng)會嘗試每個16ms對UI進行渲染讼撒,如果每次都渲染成功浑厚,這樣畫面就是流暢的(達到了60fps);否則根盒,就會發(fā)生丟幀現(xiàn)象钳幅,丟幀越多,用戶感受到的卡頓情況就越嚴重炎滞。
為了獲得更平滑的動畫敢艰,就必須保證幀率不低于60fps——意味著每幀只能花費16毫秒的時間。
1册赛、一些概念
1钠导、刷新率vs幀率
- 刷新率:每秒屏幕刷新次數(shù),手機屏幕的刷新率是60HZ
- 幀率:GPU在一秒內(nèi)繪制的幀數(shù)
2森瘪、撕裂vs掉幀
-
撕裂
因為屏幕的刷新過程是自上而下牡属、自左向右的,如果幀率>刷新率扼睬,當屏幕還沒有刷新n-1幀的數(shù)據(jù)時逮栅,就開始生成第n幀的數(shù)據(jù)了,從上到下,覆蓋第n-1幀措伐。如果此時刷新屏幕特纤,就會出現(xiàn)圖像的上半部分是第n幀的,下半部分是第n幀的現(xiàn)象废士。CPU/GPU一直都在渲染
-
丟幀
Android系統(tǒng)每隔16ms發(fā)出VSYNC信號叫潦,觸發(fā)GPU對UI進行渲染,如果你的某個操作花費時間是24ms官硝,系統(tǒng)在得到VSYNC信號的時候由于還沒有準備好,就無法進行更新任何內(nèi)容短蜕,那么用戶在32ms內(nèi)看到的會是同一幀畫面(卡頓現(xiàn)象)氢架,即丟幀現(xiàn)象。
3朋魔、單緩存 vs VSYNC vs 雙緩存 vs 三緩存
- 單緩存(沒有引入VSync )
GPU向緩存中寫入數(shù)據(jù)岖研,屏幕從緩存中讀取數(shù)據(jù),刷新后顯示警检。由于刷新率和幀率并不總是一致的孙援,很可能導(dǎo)致撕裂的現(xiàn)象。為了解決單緩存的畫面撕裂問題扇雕,出現(xiàn)了雙緩存和 VSync 拓售。
-
VSYNC 和 雙緩存
雙緩存使用了兩個緩存區(qū): Back Buffer 、 Frame Buffer镶奉。當寫入下一幀時础淤,GPU會先填充 Back Buffer 中,當刷新屏幕時哨苛,屏幕從 Frame Buffer 中讀數(shù)據(jù)鸽凶。VSYNC 主要是完成幀的復(fù)制,開始下一幀的渲染建峭。
當幀率大于刷新頻率時玻侥,通過使幀率被迫跟刷新頻率保持同步,從而避免畫面撕裂的現(xiàn)象(只有當 VSync 信號產(chǎn)生時亿蒸, CPU/GPU 才會開始繪制)凑兰。當VSync 信號產(chǎn)生時,先完成Back Buffer 到 Frame Buffer的復(fù)制操作(通過交換內(nèi)存地址)祝懂,然后通知 CPU/GPU 繪制下一幀圖像票摇。也只有VSync 信號發(fā)生時,才繪制下一幀砚蓬。
當刷新頻率>幀率時矢门,此時刷新屏幕,發(fā)出VSYNC 信號,由于CPU/GPU的渲染操作還沒有完成祟剔,就不把Back Buffer的數(shù)據(jù)復(fù)制到 Frame Buffer隔躲,此時就從Frame Buffer去取舊數(shù)據(jù),這樣在兩個刷新周期里物延,顯示的是同一幀數(shù)據(jù)宣旱, 掉幀
-
三重緩存
雙重緩存的缺陷在于:當 CPU/GPU 繪制一幀的時間超過 16 ms 時,會產(chǎn)生 Jank叛薯。更要命的是浑吟,產(chǎn)生 Jank 的那一幀的顯示期間,GPU/CPU 都是在閑置的耗溜。
如下圖组力,A、B 和 C 都是 Buffer抖拴。
如果有第三個 Buffer 能讓 CPU/GPU 在這個時候繼續(xù)工作燎字,那就完全可以避免第二個 Jank 的發(fā)生了!
2阿宅、渲染是怎么實現(xiàn)的
如何把布局渲染成用戶可以識別的圖片候衍?它的核心是通過柵格化操作,將一些按鈕洒放、文字等布局對象拆分到像素點進行顯示蛉鹿。由于柵格化是非常費時的,因此引入了GPU加快柵格化過程拉馋。
整個處理過程是:CUP負把把UI對象轉(zhuǎn)變GPU可以識別的成圖元(多邊形榨为、紋理),然后上傳到GPU進行柵格化過程煌茴。
1随闺、在GPU中保存圖元
但是由于這個轉(zhuǎn)換、上傳都是耗時的蔓腐,所以為了減少時間矩乐,需要減少它們的數(shù)量,慶幸的是Open GL API回论,允許你將這些圖元保存到GPU散罕,當下次需要一個按鈕的時候,只要參考GPU中已經(jīng)存在的圖元傀蓉,告訴GPU如何去繪制 它欧漱。優(yōu)化渲染性能就意味著盡可能多且快的將更多的數(shù)據(jù)上傳到GPU,然后留在GPU葬燎,盡可能長時間的不去修改它误甚。
2、display list窑邦,CPU擅威、GPU溝通的橋梁
將圖元從CPU上傳到GPU,這個操作是在DisplayList的幫助下完成的冈钦。DisplayList持有GPU渲染時需要的信息郊丛,包括了一些圖元和Open GL命令列表。
CPU直接與GPU通信瞧筛,而是通過中間的一個圖形驅(qū)動層(Graphics Driver)來連接這兩部分厉熟。 圖形驅(qū)動維護了一個隊列,CPU把display list添加到隊列里驾窟,GPU從這個隊列取出數(shù)據(jù)進行繪制庆猫。
Display List,在View第一次需要被渲染時被創(chuàng)建绅络,當這個View要顯示到屏幕上時,我們會執(zhí)行GPU的繪制指令來執(zhí)行這個Display List嘁字。
整體流程是:CUP負把把UI對象轉(zhuǎn)變GPU可以識別的成圖元(多邊形恩急、紋理),并存儲進display list列表纪蜒;GPU執(zhí)行繪圖指令來執(zhí)行display list衷恭,取出相應(yīng)的圖元信息,進行柵格化渲染
3纯续、引起的原因和檢測方法
主要有以下幾點随珠,可能引起渲染的性能問題:
- 布局Layout過于復(fù)雜,無法在16ms內(nèi)完成渲染猬错。
- 同一時間動畫執(zhí)行的次數(shù)過多窗看,導(dǎo)致CPU或GPU負載過重。
- View過度繪制倦炒,導(dǎo)致某些像素在同一幀時間內(nèi)被繪制多次显沈。
- UI線程中做了稍微耗時的操作。
可以通過如下工具進行檢測
HierarchyViewer(布局盡量扁平化)逢唤、Show GPU Overdraw拉讯、Profile GPU Rendering等等
1、Profile GPU Rendering
1鳖藕、打開手機里面的開發(fā)者選項魔慷,選擇Profile GPU Rendering,選中On screen as bars的選項
通過Profile GPU Rendering著恩,可以在屏幕上實時顯示渲染每一幀圖像花費的時間院尔。渲染時間用柱狀圖表示蜻展,每一條柱狀圖都由3部分組成,藍色召边、紅色和黃色铺呵,代表渲染的3個不同的階段,通過分析這三個階段的時間就可以找到渲染時的性能瓶頸隧熙。
2片挂、使用命令
adb shell dumpsys gfxinfo yourpackagename
將得到的數(shù)據(jù)保存為txt,之后導(dǎo)出到excel(數(shù)據(jù)-導(dǎo)入數(shù)據(jù))贞盯,生成圖表
Draw:表示在Java中創(chuàng)建顯示列表部分中音念,OnDraw()方法占用的時間。
Process:表示渲染引擎執(zhí)行顯示列表所花的時間躏敢,view越多闷愤,時間就越長
Execute:表示把一幀數(shù)據(jù)發(fā)送到屏幕上排版顯示實際花費的時間。其實是實際顯示幀數(shù)據(jù)的后臺緩存區(qū)與前臺緩沖區(qū)交換后并將前臺緩沖區(qū)的內(nèi)容顯示到屏幕上的時間件余。所以這個時間讥脐,一般都很短。
Draw + Process + Execute = 完整顯示一幀 啼器,這個時間要小于16ms才能保存每秒60幀(即1000秒/60幀)旬渠。
將數(shù)據(jù)復(fù)制到 excel中,然后將數(shù)據(jù)生成“柱形圖”來觀察結(jié)果端壳。
3告丢、通過Android Studio的GPU Monitor查看
4、分析
隨著界面的刷新损谦,界面上會滾動顯示垂直的柱狀圖來表示每幀畫面所需要渲染的時間岖免,柱狀圖越高表示花費的渲染時間越長。中間有一根綠色的橫線照捡,代表16ms颅湘,我們需要確保每一幀花費的總時間都低于這條橫線菲宴,這樣才能夠避免出現(xiàn)卡頓的問題置鼻。
每一條柱狀線都都包含三部分
- 藍色代表測量繪制Display List的時間,是CPU將UI對象轉(zhuǎn)變成圖元说莫,并緩存在display list的時間
視圖(display list )突然無效了 - 紅色是GPU執(zhí)行渲染指令术羔,使用OpenGL執(zhí)行Display List的時間
view過于復(fù)雜或繪制多次 - 黃色代表CPU等待GPU處理的時
GPU做了太多的工作
總之赢赊,布局層次過深,view過于復(fù)雜都會導(dǎo)致時間過長
Swap Buffers(黃色):CPU等待GPU的時間
Command Issue(紅色):OpenGL繪制display list的時間
Sync/upload(淺藍色):代表上傳bitmap到GPU的時間级历,如果值比較大释移,是因為花費了相當多的時間價值大量圖片。
Draw(藍色):創(chuàng)建和更新display list的時間
Misc Time/Vsync Delay(深綠):在兩個連續(xù)的幀之間執(zhí)行的操作寥殖,如果很大玩讳,可能因為在UI線程做了太多的工作涩蜘。
Input Handling(淺綠):處理用戶的輸入事件,花費的時間
Animation(淺淺綠):在當前幀中的動畫 花費了的時間
2熏纯、過度繪制
過度繪制(Overdraw)是屏幕上的某個像素在同一幀的時間內(nèi)被繪制了多次同诫。可以在開發(fā)者選項中打開 Show GPU Overdraw樟澜,觀察過度繪制情況误窖。
如果沒有顏色,代表沒有過度繪制秩贰,藍色霹俺,淡綠,淡紅毒费,深紅代表了4種不同程度的Overdraw情況丙唧,分別表示了對應(yīng)的像素被多繪制了1、2觅玻、3想际、4次,應(yīng)當盡量減少紅色區(qū)域溪厘。
過度繪制可能是因為組件的互相重疊或者不必要的背景重疊
3桩匪、HierarchyViewer
1、打開Hierarchy Viewer
點擊 Tools>Android>Android Device Monitor友鼻,選擇 Hierarchy Viewer傻昙,如下圖:
但是Hierachy Viewer默認是無法連接真機調(diào)試,只能使用模擬器了彩扔。但是要APP先運行起來再使用Android Device Monitor妆档。
2虫碉、分析頁面布局性能
選中一個節(jié)點贾惦,點擊 右上角,就可以獲取到布局繪制的時間敦捧,如圖
從左到右依次须板,代表View的Measure, Layout和Draw的性能,不同顏色代表不同的性能等級:
- 綠:該View的此項性能比該View Tree中超過50%的View都要快
- 黃:該View的此項性能比該View Tree中超過50%的View都要慢
- 紅:該View的此項性能是View Tree中最慢的
4.2 測量結(jié)果分析
紅色節(jié)點是代表應(yīng)用性能慢的一個潛在問題兢卵,下面是幾個例子习瑰,如何來分析和解釋紅點的出現(xiàn)原因?
1)如果在葉節(jié)點或者ViewGroup中秽荤,只有極少的子節(jié)點甜奄,這可能反映出一個問題柠横,應(yīng)用可能在設(shè)備上運行并不慢,但是你需要指導(dǎo)為什么這個節(jié)點是紅色的课兄,可以借助Systrace或者Traceview工具牍氛,獲取更多額外的信息;
2)如果一個視圖組里面有許多的子節(jié)點烟阐,并且測量階段呈現(xiàn)為紅色搬俊,則需要觀察下子節(jié)點的繪制情況;
3)如果視圖層級結(jié)構(gòu)中的根視圖曲饱,Messure階段為紅色悠抹,Layout階段為紅色,Draw階段為黃色扩淀,這個是比較常見的楔敌,因為這個節(jié)點是所有其它視圖的父類;
4)如果視圖結(jié)構(gòu)中的一個葉子節(jié)點驻谆,有20個視圖是紅色的Draw階段卵凑,這是有問題的,需要檢查代碼里面的onDraw方法胜臊,不應(yīng)該在那里調(diào)用勺卢。
- 布局常見問題與優(yōu)化建議
1)沒有用的父布局時指沒有背景繪制或者沒有大小限制的父布局,這樣的布局不會對UI效果產(chǎn)生任何影響象对。我們可以把沒有用的父布局黑忱,通過<merge/>標簽合并來減少UI的層次;
2)使用線性布局LinearLayout排版導(dǎo)致UI層次變深勒魔,如果有這類問題甫煞,我們就使用相對布局RelativeLayout代替LinearLayout,減少UI的層次;
3)不常用的UI被設(shè)置成GONE,比如異常的錯誤頁面冠绢,如果有這類問題抚吠,我們需要用<ViewStub/>標簽,代替GONE提高UI性能弟胀。
參考:Android 性能模式 第一季楷力、Android性能優(yōu)化典范 - 第1季、Android性能優(yōu)化之渲染篇孵户、Android性能優(yōu)化系列——Profile GPU Rendering萧朝、Profile GPU Rendering Walkthrough、Android 顯示原理簡介延届、Android 4.4 Graphic系統(tǒng)詳解(2) VSYNC的生成剪勿、理解 VSync、了解Android 4.1方庭,之三:黃油項目 —— 運作機理及新鮮玩意
厕吉、Hierarchy Viewer使用詳解