什么是過度渲染
GPU過渡繪制的概念:GPU過度繪制指的是在屏幕一個像素上繪制多次(超過一次),比如一個TextView后有背景,那么顯示文本的像素至少繪了兩次,一次是背景,一次是文本
如果你粉刷過一個房間或一所房子,就會知道給墻壁涂上顏色需要做大量的工作。假如你還要重新粉刷一次的話,第二次粉刷的顏色會覆蓋住第一次的顏色,第一次的顏色就永遠(yuǎn)不可見了,等于你第一次粉刷做的大量工作就完全被浪費掉。這太可怕了。
同樣的道理,如果在你的應(yīng)用程序中浪費精力去繪制一些東西同樣會產(chǎn)生性能問題。過度繪制這個名詞就是用來描述屏幕上一個像素在單個幀中被重繪了多少次。
過度繪制分析
- view tree的繪制過程:
View本身大小多少,這由onMeasure()決定,在viewtree 上遞歸調(diào)用該函數(shù)來計算每個view大小。
View在ViewGroup中的位置如何,這由onLayout()決定,在viewtree 上遞歸調(diào)用該函數(shù)來計算每個view在父view中的位置。
繪制View,onDraw()定義了如何繪制這個View。在viewtree 上遞歸調(diào)用該函數(shù)來繪制每個view。
顯然view tree 層級越深,過程越長,越可能產(chǎn)生卡頓
不同的顏色代表什么?

- 原始色(即沒有發(fā)生改變):代表屏幕上該像素塊被繪制了一次。
- 藍(lán)色(過度繪制1x):代表屏幕上該像素塊被繪制了2次。
- 綠色(過度繪制2x):代表屏幕上該像素塊被繪制了3次。
- 粉色(過度繪制3x):代表屏幕上該像素塊被繪制了4次。
- 紅色(過度繪制4x):代表屏幕上該像素塊被繪制了5次。
過度繪制的檢測和檢驗
-
手機(jī)檢測工具
開發(fā)者選項開啟GPU過度繪制調(diào)試:按照以下步驟打開Show GPU Overrdraw的選項:設(shè)置 -> 開發(fā)者選項 -> 調(diào)試GPU過度繪制 -> 顯示GPU過度繪制
image.png
image.png -
Android Studio檢測工具
Tools-> Android -> Android Device Monitor
image.png
HierarchyViewer
進(jìn)行UI布局復(fù)雜程度及冗余等分析,要使用這個工具需要最好在虛擬機(jī)上才能work,不然會報
Unable to get view server version from device
如果需要真機(jī)上使用這個功能Android SDK開發(fā)團(tuán)隊提供了VIewServer開源庫,項目地址https://github.com/romainguy/ViewServer
需要在Activity的onCreate、onDestroy、onResume三個生命周期方法中調(diào)用ViewServer的對應(yīng)方法即可在真機(jī)環(huán)境下使用HierarchyView工具.
由于布局過于復(fù)雜,這里截取其中一部分
image.png
一個Activity的View樹,通過這個樹可以分析出View嵌套的冗余層級,左下角可以輸入View的id直接自動跳轉(zhuǎn)到中間顯示;Save as PNG用來把左側(cè)樹保存為一張圖片;Capture Layers用來保存psd的PhotoShop分層素材;右側(cè)劇中顯示選中View的當(dāng)前屬性狀態(tài);右下角顯示當(dāng)前View在Activity中的位置等;左下角三個進(jìn)行切換;Load View Hierarchy用來手動刷新變化(不會自動刷新的)。當(dāng)我們選擇一個View后會如下圖所示:
image.png
類似上圖可以很方便的查看到當(dāng)前View的許多信息;上圖最底那三個彩色原點代表了當(dāng)前View的性能指標(biāo),從左到右依次代表測量、布局、繪制的渲染時間,紅色和黃色的點代表速度渲染較慢的View(當(dāng)然了,有些時候較慢不代表有問題,譬如ViewGroup子節(jié)點越多、結(jié)構(gòu)越復(fù)雜,性能就越差)。
當(dāng)然了,在自定義View的性能調(diào)試時,HierarchyViewer上面的invalidate Layout和requestLayout按鈕的功能更加強(qiáng)大,它可以幫助我們debug自定義View執(zhí)行invalidate()和requestLayout()過程,我們只需要在代碼的相關(guān)地方打上斷點就行了,接下來通過它觀察繪制即可。
不過有時候布局嵌套過深通過這個來看其實有點不現(xiàn)實,這時候需要結(jié)合另外一個工具
布局分析器


解決方案
-
太多重疊的背景
這個問題其實最容易解決,建議就是檢查你在布局和代碼中設(shè)置的背景,有些背景是被隱藏在底下的,它永遠(yuǎn)不可能顯示出來,這種沒必要的背景一定要移除,因為它很可能會嚴(yán)重影響到app的性能。
去掉window的默認(rèn)背景:
當(dāng)我們使用了Android自帶的一些主題時,window會被默認(rèn)添加一個純色的背景,這個背景是被DecorView持有的。當(dāng)我們的自定義布局時又添加了一張背景圖或者設(shè)置背景色,那么DecorView的background此時對我們來說是無用的,但是它會產(chǎn)生一次Overdraw,帶來繪制性能損耗。
查看Android源碼里的Theme主題,如下:
<style name="Theme">
...
<!-- Window attributes -->
<item name="windowBackground">@drawable/screen_background_selector_dark</item>
...
</style>
去掉window的背景可以在onCreate()中setContentView()之后調(diào)用
getWindow().setBackgroundDrawable(null);
或者在theme中添加
android:windowbackground="null";
去掉其他不必要的背景
有時候為了方便會先給Layout設(shè)置一個整體的背景,再給子View設(shè)置背景,這里也會造成重疊,如果子View寬度mach_parent,可以看到完全覆蓋了Layout的一部分,這里就可以通過分別設(shè)置背景來減少重繪。再比如如果采用的是selector的背景,將normal狀態(tài)的color設(shè)置為“@android:color/transparent”,也同樣可以解決問題。這里只簡單舉兩個例子,我們在開發(fā)過程中的一些習(xí)慣性思維定式會帶來不經(jīng)意的Overdraw,所以開發(fā)過程中我們?yōu)槟硞€View或者ViewGroup設(shè)置背景的時候,先思考下是否真的有必要,或者思考下這個背景能不能分段設(shè)置在子View上,而不是圖方便直接設(shè)置在根View上。
-
clipRect的使用
我們可以通過canvas.clipRect()來 幫助系統(tǒng)識別那些可見的區(qū)域。這個方法可以指定一塊矩形區(qū)域,只有在這個區(qū)域內(nèi)才會被繪制,其他的區(qū)域會被忽視。這個API可以很好的幫助那些有多組重疊 組件的自定義View來控制顯示的區(qū)域。同時clipRect方法還可以幫助節(jié)約CPU與GPU資源,在clipRect區(qū)域之外的繪制指令都不會被執(zhí)行,那些部分內(nèi)容在矩形區(qū)域內(nèi)的組件,仍然會得到繪制。
3. 太多重疊的view
套路一:ViewStub
ViewStub稱之為“延遲化加載”,在很多數(shù)情況下,程序無需顯示ViewStub所指向的布局文件,只有在特定的某些較少條件下,此時ViewStub所指向的布局文件才需要被inflate,且此布局文件直接將當(dāng)前ViewStub替換掉,具體是通過viewStub.infalte()或viewStub.setVisibility(View.VISIBLE)來完成;常見的比如網(wǎng)絡(luò)加載布局
套路二:Merge標(biāo)簽
MMerge標(biāo)簽可以干掉一個view層級。Merge的作用很明顯,但是也有一些使用條件的限制。有兩種情況下我們可以使用Merge標(biāo)簽來做容器控件。第一種子視圖不需要指定任何針對父視圖的布局屬性,就是說父容器僅僅是個容器,子視圖只需要直接添加到父視圖上用于顯示就行。另外一種是假如需要在LinearLayout里面嵌入一個布局(或者視圖),而恰恰這個布局(或者視圖)的根節(jié)點也是LinearLayout,這樣就多了一層沒有用的嵌套,無疑這樣只會拖慢程序速度。而這個時候如果我們使用merge根標(biāo)簽就可以避免那樣的問題。另外Merge只能作為XML布局的根標(biāo)簽使用,當(dāng)Inflate以開頭的布局文件時,必須指定一個父ViewGroup,并且必須設(shè)定attachToRoot為true。不常用的UI被設(shè)置成GONE,比如異常的錯誤頁面,如果有這類問題,我們需要用標(biāo)簽,代替GONE提高UI性能。,畢竟visible/gone是會引起布局重繪的
套路三:inlcude標(biāo)簽
標(biāo)簽?zāi)軌蛑赜貌季治募?/p>
還有一些其他避免過度繪制的方法建議總結(jié):
- 盡量多使用RelativeLayout和LinearLayout,不要使用絕對布局AbsoluteLayout。
在布局層次一樣的情況下,建議使用LinearLayout代替RelativeLayout,因為LinearLayout性能要稍高一些。
在完成相對較復(fù)雜的布局時,建議使用RelativeLayout,RelativeLayout可以簡單實現(xiàn)LinearLayout嵌套才能實現(xiàn)的布局。 - 將可復(fù)用的組件抽取出來并通過include標(biāo)簽使用
- 使用ViewStub標(biāo)簽來加載一些不常用的布局
- 動態(tài)地inflation view性能要比SetVisiblity性能要好,當(dāng)然ViewStub是最好的選擇。
- 使用merge標(biāo)簽減少布局的嵌套層次。
- 去掉多余的背景顏色。
- 對于有多層背景顏色的Layout來說,留最上面的一層的顏色即可,其他底層的顏色都可以去掉。
- 對于使用Selector當(dāng)背景的Layout(比如ListView的Item,會使用Selector來標(biāo)記點擊,選擇等不同的狀態(tài)),可以將normal狀態(tài)的color設(shè)置成"@android:color/transparent",來解決對應(yīng)的問題。
內(nèi)嵌使用包含layout_weight屬性的LinearLayout會在繪制時花費昂貴的系統(tǒng)資源,因為每一個子組件都需要被測量兩次。在使用ListView與GridView的時候,這個問題顯得尤為重要,因為子組件會重復(fù)被創(chuàng)建,所以要盡量避免使用layout_weight。 - 使得Layout寬而淺,而不是窄而深(在Hierarchy Viewer的Tree視圖里面體現(xiàn))。




