我們在XML資源文件上一頓<RelativeLayout>、<ImageView>等操作,就可以完成視圖的布局。
安卓中的view呈樹狀結(jié)構(gòu)分布,最頂層是window、docorView,然后是我們自定義的view。
每一個view都要經(jīng)過measure,layout,draw三個步驟后才會被渲染到屏幕上。
安卓設(shè)備大多數(shù)都是一秒鐘刷新60次,也就是每一個視圖應(yīng)該在1/60s=16ms的時間內(nèi)完成measure,layout,draw。
如果我們不恰當?shù)夭季忠晥D,如:view層級太高,使用過多復(fù)雜的view等,有可能會造成視圖不能在16ms之內(nèi)完成試圖內(nèi)所有view的measure,layout,draw。這樣會造成操作不流暢的不良后果。
一、使用Hierarchy Viewer查看視圖結(jié)構(gòu)和渲染時間
我們可以使用Hierarchy Viewer來查看視圖的結(jié)構(gòu)和渲染時間。

注意:
1.在真機上使用Hierarchy Viewer有些問題,我使用的是模擬器。
2.需要使用一個庫ViewServer
二、布局優(yōu)化-視圖結(jié)構(gòu)扁平化
我們需要減少布局的層級,使布局結(jié)構(gòu)更加扁平化。
我們首先來看一個極端的反例。
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.tinymonster.hierarchyviewertest.MainActivity">
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
>
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
>
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
>
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
>
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
>
<TextView
android:id="@+id/text1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"/>
<TextView
android:id="@+id/text2"
android:layout_below="@id/text1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"/>
</RelativeLayout>
</RelativeLayout>
</RelativeLayout>
</RelativeLayout>
</RelativeLayout>
</LinearLayout>
上面的布局中,嵌套了多個無用的RelativeLayout,實際顯示的只有兩個TextView,我們可以查看一下這個布局的measure,layout,drawlayout時間,如下圖所示:

可以看到measure消耗的時間是20多ms,這已經(jīng)大于了每個視圖的最大渲染時間了(16ms),這是因為我們嵌套了多個RelativeLayout,每個RelativeLayout都會對子view測量兩次,這樣就造成了measure時間隨層級的加深呈指數(shù)型增長RelativeLayout性能分析。
下面,我們把多余的ViewGroup取消,使布局層級扁平化,如下所示
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.tinymonster.hierarchyviewertest.MainActivity">
<TextView
android:id="@+id/text1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!" />
<TextView
android:id="@+id/text2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/text1"
android:text="Hello World!" />
</LinearLayout>
在此查看測量時間,測量時間變成了0ms(是太快了已經(jīng)不能計算了?還是哪里出了錯誤?感覺這個測量時間很不穩(wěn)定,我繼續(xù)學(xué)習一下)

三、布局優(yōu)化-減少過度繪制
在多層次重疊的UI結(jié)構(gòu)里面,如果不可見的UI也在做繪制操作,會導(dǎo)致某些像素區(qū)域被繪制了多次,這樣就會造成CPU和GPU資源的浪費。
我們可以通過手機自帶的“調(diào)試GPU過度繪制”功能來診斷是否有過度繪制的情況。
開啟手機上的GPU過度繪制調(diào)試工具
1.點擊進入“設(shè)置”;
2.點擊進入“開發(fā)者選項”
3.選中“調(diào)試GPU過度繪制”
4.選中“顯示過度繪制區(qū)域”
這時候你會發(fā)現(xiàn)手機出現(xiàn)了奇怪的顏色,這不是手機壞了。
運行過度繪制反例
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.tinymonster.hierarchyviewertest.MainActivity">
<ImageView
android:layout_width="match_parent"
android:layout_height="500dp"
/>
<ImageView
android:layout_width="match_parent"
android:layout_height="400dp"
/>
<ImageView
android:layout_width="match_parent"
android:layout_height="300dp"
/>
<ImageView
android:layout_width="match_parent"
android:layout_height="200dp"
/>
</RelativeLayout>
上面的布局中,多個imageView有重疊部分,重疊部分肯定出現(xiàn)了過度繪制。
運行APP,我們可以看到下面的圖像

屏幕上不同的顏色表示過度繪制的程度:
1.原色:沒有過度繪制,只繪制了一次
2.藍色:一次過度繪制
3.綠色:兩次過度繪制
4.粉色:三次過度繪制
5.紅色,三次以上的過度繪制
我們修改布局,去掉ImageView的重疊
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.tinymonster.hierarchyviewertest.MainActivity">
<ImageView
android:layout_width="match_parent"
android:layout_height="200dp"
/>
<ImageView
android:layout_width="match_parent"
android:layout_height="100dp"
/>
<ImageView
android:layout_width="match_parent"
android:layout_height="100dp"
/>
<ImageView
android:layout_width="match_parent"
android:layout_height="100dp"
/>
</RelativeLayout>
運行程序,可以看到view的顏色不變,沒有過度繪制。

避免過度繪制的方法
1.選擇合適的Layout。LinearLayout的消耗較小,但是表達能力有效,RelativeLayout的表達能力很好,但是性能不高。在合適的情況選擇合適的Layout。
2.去掉window的默認背景。當我們使用了Android自帶的一些主題時,window會被默認添加一個純色的背景,這個背景是被DecorView持有的。當我們的自定義布局時又添加了一張背景圖或者設(shè)置背景色,那么DecorView的background此時對我們來說是無用的,但是它會產(chǎn)生一次Overdraw,帶來繪制性能損耗。去掉window的背景可以在onCreate()中setContentView()之后調(diào)用getWindow().setBackgroundDrawable(null);或者在theme中添加android:windowbackground="null"。
3.使用viewStub占位。我們經(jīng)常會遇到這樣的情況,運行時動態(tài)根據(jù)條件來決定顯示哪個View或布局。常用的做法是把View都寫在上面,先把它們的可見性都設(shè)為View.GONE,然后在代碼中動態(tài)的更改它的可見性。這樣的做法的優(yōu)點是邏輯簡單而且控制起來比較靈活。但是它的缺點就是,耗費資源。雖然把View的初始可見View.GONE但是在Inflate布局的時候View仍然會被Inflate,也就是說仍然會創(chuàng)建對象,會被實例化,會被設(shè)置屬性。也就是說,會耗費內(nèi)存等資源。推薦的做法是使用android.view.ViewStub,ViewStub是一個輕量級的View,它一個看不見的,不占布局位置,占用資源非常小的控件??梢詾閂iewStub指定一個布局,在Inflate布局的時候,只有ViewStub會被初始化,然后當ViewStub被設(shè)置為可見的時候,或是調(diào)用了ViewStub.inflate()的時候,ViewStub所向的布局就會被Inflate和實例化,然后ViewStub的布局屬性都會傳給它所指向的布局。這樣,就可以使用ViewStub來方便的在運行時,要還是不要顯示某個布局。
四、總結(jié)
手機資源是有限和寶貴的,分配給每個APP的資源更是有限的,我們在布局的時候,可以通過減少視圖層級、避免過度繪制來達到較少資源消耗的目的。同時可以使用Hierarchy Viewer、GPU過度繪制調(diào)制等工具幫助我們分析布局。
五、參考文獻
Hierarchy Viewer使用詳解
Android中RelativeLayout和LinearLayout性能分析
Android布局優(yōu)化(二),減少過度繪制