安卓布局優(yōu)化

我們在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)和渲染時間。

image.png

注意:
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時間,如下圖所示:

image.png

可以看到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,我們可以看到下面的圖像


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持有的。當我們的自定義布局時又添加了一張背景圖或者設(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)化(二),減少過度繪制

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容