首先我們要判斷一個界面是否需要優化佈局,這個很簡單,標準上面已經說了,要儘量控制在16ms以內!
手機開發者選項—>GPU呈模式分析。 (測試機小米Max)
綠色是基線也就是16ms ,要儘量讓條形柱狀圖控制在綠色基線以下。可以看看國內做的比較好的應用QQ,基本都控制在基線以下。
判斷的標準有了,接下來我們將按照如下的方法進行優化:
1.佈局的選擇。
2.減少佈局的嵌套層次。
3.防止過度重繪。
4.使用Hierarchy View 工具進行佈局優化。
一、佈局的選擇:
android官方給我提供的佈局常用的要數LinearLayout、FrameLayout以及RelativeLayout用的是最多的也是最常用的,但是它們之間就沒有性能的上的差別?在能滿足佈局需求的情況又如何選擇?(比如實現子控件居中顯示)
<A xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!" />
</A>
如果A是LinearLayout只需要加上 android:gravity="center"即可;如果A是FrameLayout 那麼字View加上android:layout_gravity="center"也能完成;RelativeLayout的話,子View加上android:layout_centerInParent="true"。 然後我們需要測試下,在完成這樣一個需求的情況下,這樣一個佈局耗時與消耗內存的關係。
經測試的得數據:(金立手機 android 5.1系統 測試6次):
LinearLayout 平均耗時:908.7ms 平均消耗內存:3.227M
FrameLayout 平均耗時:950.8ms 平均消耗內存:3.504M
RelativeLayout 平均耗時:998.5ms 平均消耗內存:5.083M
從數據我們可以的出一個初步的結論(在同等條件下):它們3個的性能排行榜:LinearLayout>FrameLayout>RelativeLayout
二、減少佈局的嵌套:
衆所周知,android的視圖需要經過測量,佈局,以及繪製三個步驟。那麼你嵌套的層次越多,佈局需要遞歸查找子View的時間消耗就越多,最後整體完成measure,layout,draw的總時間就越長,那麼畫面將變得越卡頓。
<A xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<A xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
...
<View
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!" />
...
</A>
</A>
測試下多層嵌套的影響:
嵌套1層:
A=LinearLayout
View measure 2 layout 2 draw 1
A=FrameLayout
View measure 2 layout 2 draw 1
A=RelativeLayout
View measure 4 layout 2 draw 1
嵌套2層:
A=LinearLayout
View measure 2 layout 2 draw 1
A=FrameLayout
View measure 2 layout 2 draw 1
A=RelativeLayout
View measure 8 layout 2 draw 1
嵌套三層:A=LinearLayout
View measure 2 layout 2 draw 1
A=FrameLayout
View measure 2 layout 2 draw 1
A=RelativeLayout
View measure 16 layout 2 draw 1
由數據可以得出結論:LinearLayout和Framelayout無論嵌套多少層View的都只會measure 2次,layout 2次,draw 1次,而RelativeLayout 則是measure 2的(n+1)冪次方次,layout 2次,draw 1次:
也可初步得嵌套性能結論:LinearLayout=FrameLayout>RelativeLayout (尤其是RelativeLayout需要慎用)。
減少佈局的嵌套層次主要通過<include> 、<merge>(主)、 <ViewStub> 三大標籤以及控件的屬性來減少嵌套層次:
1.<inclue>標籤:這個沒什麼好講的主要是重用佈局,一定程度上減小apk的大小以及和<merge>標籤配合使用。但是需要注意的是如果,在使用<include>標籤時,如果給<include>標籤添加了屬性,那麼它會覆蓋被引入的佈局的根佈局的相同屬性。另外如給<include>標籤添加了id屬性那麼就不直接使用findViewById了,需要找到先inflate<include>標籤的跟佈局,然後通過這個View進行findViewById。
2.<merge>標籤:這個標籤是主要用來減少嵌套層次的,比如如下佈局:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<View
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!" />
</LinearLayout >
如果你直接把以上佈局通過<include>標籤添加到另外一個佈局中,那麼LinearLayout就會是讓<View>嵌套層次增加一層佈局,可是LinearLayout對於View而言並沒有什麼屬性依賴,這時我們就可以把LinearLayout 換成<merge>標籤了,這樣可以減少一層嵌套。當然你可能說上面的例子沒有什麼建樹,實際開發中誰會這麼傻。嗯哼,我們Activity中setContentView實際上是把我們的跟佈局設置到一個FrameLayout佈局裏面去了,這個時候如果我們自己寫的根佈局也是FrameLayout(沒有background屬性等一些特殊點的屬性的話)我們完全可以用<merge>標籤取代啊。下面說說<merge>標籤的使用場景:
①子控件沒有對根佈局有任何的屬性依賴,父佈局也沒有background等一些特殊屬性,對子控件而言完全就是個容器。
②在LinearLayout裏面嵌入一個佈局(或者視圖),而恰恰這個佈局(或者視圖)的根節點也是LinearLayout。
需要注意的是<merge />只能作爲XML佈局的根標籤使用。當Inflate以<merge />開頭的佈局文件時,必須指定一個父ViewGroup,並且必須設定attachToRoot爲true。
3.<ViewSub>標籤:這個標籤雖然不是用來減少嵌套層次的,但是卻能起到佈局優化的作用。ViewStub是一個輕量級的View,沒有尺寸,不繪製任何東西,因此繪製或者移除時更省時。(ViewStub不可見,大小爲0),實現View的延遲加載,避免資源的浪費,減少渲染時間,在需要的時候才加載View。在加載完後會被移除,或者說是被加載進來的layout替換掉了。需要注意的是:ViewStub所要替代的layout文件中不能有<merge>標籤。
<ViewStub
android:id="@+id/stub"
android:inflatedId="@+id/aim_layout”
android:layout="@layout/include_layout"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom" />
用ViewStub加載layout文件時,可以調用 setVisibility(View.VISIBLE) 或者 inflate()。
((ViewStub) findViewById(R.id.stub)).setVisibility(View.VISIBLE);
// or
View root = ((ViewStub) findViewById(R.id.stub)).inflate();
一旦ViewStub visible/inflated,則ViewStub將從視圖框架中移除,其id stub也會失效。ViewStub被繪製完成的layout文件取代,並且該layout文件的root view的id是android:inflatedId指定的id aim_layout.
4.儘量使用控件的屬性完成需求
比如要完成左右兩端是圖片中間是文字的佈局,使用組合控件組合能完成,但是這絕對是下下之策。這個需求使用TextView的 android:drawableLeft=""和android:drawableRight=""
同樣也是能完成的!同樣利用這些屬性能完成 Android 自定義自動清空EditText。
三、防止過度重繪:
多次對同一個區域進行onDraw重新繪製,同樣是比較消耗性能和時間的,因爲我們看見的視圖就像Photoshop視圖層一樣最上面的顏色會覆蓋下面的,如果沒有必要的話是需要減少重繪的。首先我們需要看怎樣纔是重繪?重繪的標準是什麼?
通過手機開發者選項可以看手機重回結果:手機開發者選項—>過度重繪調試。
可以看見百度地圖的重繪是控制在1次和2次之內的,很少有淡紅色和深紅色的。這是比較好的,也是我們要達到的目標,減少淡紅色和深紅色,防止過度重繪。
白色表示沒有重複繪製(只繪製了1次),淡藍色表示重繪1次(繪製了2次),淡綠色表示重繪2次(繪製了3次)淡紅色表示重繪製3次(繪製了4次),深紅色表示重繪4次活更多(繪製5次或者更多)。 PS:設置Activty的背景顏色爲空或者透明都可以減少過度重繪!
造成重繪的原因主要有 1.嵌套的過多 2.自定View沒有使用好invalidate或者requestLayout方法,主要從這2個方面進行優化!
四、Hierarchy View優化佈局:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/colorPrimary"
>
<TextView
android:id="@+id/first"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="使用Hierarchy View"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@id/first"
android:layout_marginTop="10dp"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Android Logo"/>
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:src="@mipmap/ic_launcher"/>
</LinearLayout>
</RelativeLayout>
接下來我們來看看Hierarchy View的使用:
Windows窗口下表示的當前的手機運行進程,加粗的表示當前正在執行的進程。
黃色框內的TreeView視圖可以查看每個佈局的詳細信息。
紅色方框的LayoutView視圖可以查看佈局的大致情況。
藍色方框的TreeOverview是所有佈局控件的縮略圖。拖動它,然後在TreeView查看詳細的信息。
TreeView中的綠色方框依次是保存成一張圖片,以及保存成photoshop形式的文件,點擊之後可以顯示每個佈局的詳細信息。如下圖:
可以看出佈局的measure、layout、draw的時間,下面的三個顏色圓圈點依次對應。綠色表示最快的前50%,黃色表示最慢的前50%,紅色表示那一層裏面最慢的view。顯然,紅色的部分是我們優先優化的對象。右下角的數據表示該View在同一層View中的序號,0表示至於它自己!可以看出上面的佈局加載依次的總耗時:0.07+0.011+0.990=1.071ms ,顯然我們的佈局裏面紅色的圓圈是可以優化的,RelativeLayout其實只是起到一個容器的作用,可以用LinearLayout替代。而裏面的那個LinearLayoutt同樣可以去除掉,這樣優化之後結果如下圖:
現在的總時間:0.051+0.009+0.956=1.016ms 這樣相對於優化前快了0.055ms 。
經過以上的優化方法,就能打造出一個比較高性能的佈局了,當然性能優化的方法遠不止這些,後續將會對性能優化進一步的探討。