安卓佈局優化
我們在XML資原始檔上一頓<RelativeLayout>、<ImageView>等操作,就可以完成檢視的佈局。
安卓中的view呈樹狀結構分佈,最頂層是window、docorView,然後是我們自定義的view。
每一個view都要經過measure, layout ,draw三個步驟後才會被渲染到螢幕上。
安卓裝置大多數都是一秒鐘重新整理60次,也就是每一個檢視應該在1/60s=16ms的時間內完成measure,layout,draw。
如果我們不恰當地佈局檢視,如:view層級太高,使用過多複雜的view等,有可能會造成檢視不能在16ms之內完成試圖內所有view的measure,layout,draw。這樣會造成操作不流暢的不良後果。
一、使用Hierarchy Viewer檢視檢視結構和渲染時間
我們可以使用Hierarchy Viewer來檢視檢視的結構和渲染時間。

image.png
注意:
1.在真機上使用Hierarchy Viewer有些問題,我使用的是模擬器。
2.需要使用一個庫 ViewServer二、佈局優化-檢視結構扁平化
我們需要減少佈局的層級,使佈局結構更加扁平化。
我們首先來看一個極端的反例。
<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時間,如下圖所示:

image.png
可以看到measure消耗的時間是20多ms,這已經大於了每個檢視的最大渲染時間了(16ms),這是因為我們嵌套了多個RelativeLayout,每個RelativeLayout都會對子view測量兩次,這樣就造成了measure時間隨層級的加深呈指數型增長 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(是太快了已經不能計算了?還是哪裡出了錯誤?感覺這個測量時間很不穩定,我繼續學習一下)

三、佈局優化-減少過度繪製
在多層次重疊的UI結構裡面,如果不可見的UI也在做繪製操作,會導致某些畫素區域被繪製了多次,這樣就會造成CPU和GPU資源的浪費。
我們可以通過手機自帶的“除錯GPU過度繪製”功能來診斷是否有過度繪製的情況。
開啟手機上的GPU過度繪製除錯工具
1.點選進入“設定”;
2.點選進入“開發者選項”
3.選中“除錯GPU過度繪製”
4.選中“顯示過度繪製區域”
這時候你會發現手機出現了奇怪的顏色,這不是手機壞了。
執行過度繪製反例
<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有重疊部分,重疊部分肯定出現了過度繪製。
執行APP,我們可以看到下面的影象

image.png
螢幕上不同的顏色表示過度繪製的程度:
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的顏色不變,沒有過度繪製。

image.png
避免過度繪製的方法
1.選擇合適的Layout。LinearLayout的消耗較小,但是表達能力有效,RelativeLayout的表達能力很好,但是效能不高。在合適的情況選擇合適的Layout。
2.去掉window的預設背景。當我們使用了Android自帶的一些主題時,window會被預設新增一個純色的背景,這個背景是被DecorView持有的。當我們的自定義佈局時又添加了一張背景圖或者設定背景色,那麼DecorView的background此時對我們來說是無用的,但是它會產生一次Overdraw,帶來繪製效能損耗。去掉window的背景可以在onCreate()中setContentView()之後呼叫getWindow().setBackgroundDrawable(null);或者在theme中新增android:windowbackground="null"。
3.使用viewStub佔位。我們經常會遇到這樣的情況,執行時動態根據條件來決定顯示哪個View或佈局。常用的做法是把View都寫在上面,先把它們的可見性都設為View.GONE,然後在程式碼中動態的更改它的可見性。這樣的做法的優點是邏輯簡單而且控制起來比較靈活。但是它的缺點就是,耗費資源。雖然把View的初始可見View.GONE但是在Inflate佈局的時候View仍然會被Inflate,也就是說仍然會建立物件,會被例項化,會被設定屬性。也就是說,會耗費記憶體等資源。推薦的做法是使用android.view.ViewStub,ViewStub是一個輕量級的View,它一個看不見的,不佔佈局位置,佔用資源非常小的控制元件。可以為ViewStub指定一個佈局,在Inflate佈局的時候,只有ViewStub會被初始化,然後當ViewStub被設定為可見的時候,或是呼叫了ViewStub.inflate()的時候,ViewStub所向的佈局就會被Inflate和例項化,然後ViewStub的佈局屬性都會傳給它所指向的佈局。這樣,就可以使用ViewStub來方便的在執行時,要還是不要顯示某個佈局。
四、總結
手機資源是有限和寶貴的,分配給每個APP的資源更是有限的,我們在佈局的時候,可以通過減少檢視層級、避免過度繪製來達到較少資源消耗的目的。同時可以使用Hierarchy Viewer、GPU過度繪製調製等工具幫助我們分析佈局。