Android 性能優化之佈局優化

  性能優化是任何一個軟件最後都無法避開的一個話題。對於一個APP而言,創造出來不難,可是創造出一個高性能,各種順暢的APP還是相當有難度的。而Android性能優化是一個比較大的話題,本文就從Android性能優化的最初也是最原始開始講起。提到性能優化,往往都是去查內存泄漏,去查bitmap是否recycle,以及是否有資源永遠無法釋放。可是,我想Android性能優化的第一步應該是佈局的優化,因爲我們創造APP的時候,我們的第一步通常就是編寫佈局XML,可以說佈局優化非常的重要。Android的屏幕刷新(渲染)是60幀每秒的,平均下來,一個界面的佈局measure,layout,draw總時間必須在16ms內完成纔不會讓用戶感覺到卡頓!

 首先我們要判斷一個界面是否需要優化佈局,這個很簡單,標準上面已經說了,要儘量控制在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 。

 經過以上的優化方法,就能打造出一個比較高性能的佈局了,當然性能優化的方法遠不止這些,後續將會對性能優化進一步的探討。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章