Android 性能優化(二)之佈局優化面面觀

1、初識佈局優化

經過《Android性能優化(一)之啓動加速35%》咱們得到了閃電般的App啓動速度,那麼在應用啓動完畢以後,UI佈局也會對App的性能產生比較大的影響,若是佈局寫得糟糕,顯而易見App的表現不可能流暢。android

那麼本文我一樣基於實際案例,針對應用的佈局進行優化進而提高App性能。性能優化

2、60fps VS 16ms

根據Google官方出品的Android性能優化典範60幀每秒是目前最合適的圖像顯示速度,事實上絕大多數的Android設備也是按照每秒60幀來刷新的。爲了讓屏幕的刷新幀率達到60fps,咱們須要確保在時間16ms(1000/60Hz)內完成單次刷新的操做(包括measure、layout以及draw),這也是Android系統每隔16ms就會發出一次VSYNC信號觸發對UI進行渲染的緣由。微信

若是整個過程在16ms內順利完成則能夠展現出流暢的畫面;然而因爲任何緣由致使接收到VSYNC信號的時候沒法完成本次刷新操做,就會產生掉幀的現象,刷新幀率天然也就跟着降低(假定刷新幀率由正常的60fps降到30fps,用戶就會明顯感知到卡頓)。markdown

Drop Frame Occur

做爲開發人員,咱們的目標只有一個:保證穩定的幀率來避免卡頓。

3、Avoid Overdraw

理論上一個像素每次只繪製一次是最優的,可是因爲重疊的佈局致使一些像素會被屢次繪製,Overdraw由此產生。網絡

咱們能夠經過調試工具來檢測Overdraw:設置——開發者選項——調試GPU過分繪製——顯示過分繪製區域。工具

overdraw

原色 – 沒有過分繪製 – 這部分的像素點只在屏幕上繪製了一次。
藍色 – 1次過分繪製– 這部分的像素點只在屏幕上繪製了兩次。
綠色 – 2次過分繪製 – 這部分的像素點只在屏幕上繪製了三次。
粉色 – 3次過分繪製 – 這部分的像素點只在屏幕上繪製了四次。
紅色 – 4次過分繪製 – 這部分的像素點只在屏幕上繪製了五次。oop

在實際項目中,通常認爲藍色便是能夠接受的顏色。佈局

咱們來看一個簡單卻隱藏了不少問題的界面,App的設置界面。在沒有優化以前打開Overdraw調試,能夠看到界面大多數是嚴重的紅色:見下圖。post

設置界面初始

貼出這個佈局的代碼性能

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#F1F0F0"
    android:orientation="vertical">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        android:background="@color/white"
        android:orientation="vertical">

        <RelativeLayout
            android:id="@+id/update_phone"
            android:layout_width="match_parent"
            android:layout_height="50dp"
            android:background="@color/white"
            android:paddingLeft="10dp"
            android:paddingRight="10dp">

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_centerVertical="true"
                android:background="@color/white"
                android:text="修改手機號"
                android:textColor="#FF555555"
                android:textSize="14sp" />

            <ImageView
                android:id="@+id/update_phone_iv"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_alignParentRight="true"
                android:layout_centerVertical="true"
                android:src="@drawable/arrow_right" />

            <ImageView
                android:id="@+id/update_phone_dot"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_centerVertical="true"
                android:layout_marginRight="10dp"
                android:layout_toLeftOf="@id/update_phone_iv"
                android:src="@drawable/message_logo_red" />
        </RelativeLayout>

        <View
            android:layout_width="match_parent"
            android:layout_height="0.5dp"
            android:layout_marginLeft="10dp"
            android:background="#FFDDDDDD" />

        <RelativeLayout
            android:id="@+id/setting_lv_forgetPassword"
            android:layout_width="match_parent"
            android:layout_height="50dp"
            android:background="@color/white"
            android:paddingLeft="10dp"
            android:paddingRight="10dp">

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_centerVertical="true"
                android:background="@color/white"
                android:text="找回密碼"
                android:textColor="#FF555555"
                android:textSize="14sp" />

            <ImageView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_alignParentRight="true"
                android:layout_centerVertical="true"
                android:src="@drawable/arrow_right" />
        </RelativeLayout>
    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        android:background="@color/white"
        android:orientation="vertical">

        <RelativeLayout
            android:id="@+id/privacy_setting"
            android:layout_width="match_parent"
            android:layout_height="50dp"
            android:background="@color/white"
            android:paddingLeft="10dp"
            android:paddingRight="10dp">

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_centerVertical="true"
                android:background="@color/white"
                android:text="隱私設置"
                android:textColor="#FF555555"
                android:textSize="14sp" />

            <ImageView
                android:id="@+id/privacy_setting_iv"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_alignParentRight="true"
                android:layout_centerVertical="true"
                android:src="@drawable/arrow_right" />

            <ImageView
                android:id="@+id/privacy_setting_dot"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_centerVertical="true"
                android:layout_marginRight="10dp"
                android:layout_toLeftOf="@id/privacy_setting_iv"
                android:src="@drawable/message_logo_red" />
        </RelativeLayout>

        <View
            android:layout_width="match_parent"
            android:layout_height="0.5dp"
            android:layout_marginLeft="10dp"
            android:background="#FFDDDDDD" />

        <RelativeLayout
            android:id="@+id/setting_lv_messageSetting"
            android:layout_width="match_parent"
            android:layout_height="50dp"
            android:background="@color/white"
            android:paddingLeft="10dp"
            android:paddingRight="10dp">

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_centerVertical="true"
                android:background="@color/white"
                android:text="@string/accountSetting_messageSetting"
                android:textColor="#FF555555"
                android:textSize="14sp" />

            <CheckBox
                android:id="@+id/setting_checkbox_c_messageSetting"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_alignParentRight="true"
                android:layout_centerVertical="true"
                android:checked="true" />
        </RelativeLayout>

    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        android:background="@color/white"
        android:orientation="vertical">

        <RelativeLayout
            android:id="@+id/setting_lv_feedback_m"
            android:layout_width="match_parent"
            android:layout_height="50dp"
            android:background="@color/white"
            android:paddingLeft="10dp"
            android:paddingRight="10dp">

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_centerVertical="true"
                android:background="@color/white"
                android:text="@string/accountSetting_feedback"
                android:textColor="#FF555555"
                android:textSize="14sp" />

            <ImageView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_alignParentRight="true"
                android:layout_centerVertical="true"
                android:src="@drawable/arrow_right" />
        </RelativeLayout>

        <View
            android:layout_width="match_parent"
            android:layout_height="0.5dp"
            android:layout_marginLeft="10dp"
            android:background="#FFDDDDDD" />

        <RelativeLayout
            android:id="@+id/setting_lv_score"
            android:layout_width="match_parent"
            android:layout_height="50dp"
            android:background="@color/white"
            android:paddingLeft="10dp"
            android:paddingRight="10dp">

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_centerVertical="true"
                android:background="@color/white"
                android:text="@string/accountSetting_score"
                android:textColor="#FF555555"
                android:textSize="14sp" />

            <ImageView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_alignParentRight="true"
                android:layout_centerVertical="true"
                android:src="@drawable/arrow_right" />
        </RelativeLayout>

        <View
            android:layout_width="match_parent"
            android:layout_height="0.5dp"
            android:layout_marginLeft="10dp"
            android:background="#FFDDDDDD" />

        <RelativeLayout
            android:id="@+id/setting_lv_aboutus"
            android:layout_width="match_parent"
            android:layout_height="50dp"
            android:background="@color/white"
            android:paddingLeft="10dp"
            android:paddingRight="10dp">

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_centerVertical="true"
                android:background="@color/white"
                android:text="@string/about_us"
                android:textColor="#FF555555"
                android:textSize="14sp" />

            <ImageView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_alignParentRight="true"
                android:layout_centerVertical="true"
                android:src="@drawable/arrow_right" />
        </RelativeLayout>

    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        android:background="@color/white"
        android:orientation="vertical">

        <RelativeLayout
            android:id="@+id/setting_lv_changeStatus"
            android:layout_width="match_parent"
            android:layout_height="50dp"
            android:background="@color/white"
            android:paddingLeft="10dp"
            android:paddingRight="10dp">

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_centerVertical="true"
                android:background="@color/white"
                android:text="我要招人"
                android:textColor="#FF555555"
                android:textSize="14sp" />

            <ImageView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_alignParentRight="true"
                android:layout_centerVertical="true"
                android:src="@drawable/arrow_right" />
        </RelativeLayout>
    </LinearLayout>

    <Button
        android:id="@+id/setting_btn_exitLogin"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:layout_marginTop="30dp"
        android:background="@color/white"
        android:gravity="center"
        android:text="@string/me_exitbtn"
        android:textColor="#FFFF5A5A"
        android:textSize="16sp" />

</LinearLayout>複製代碼

分析佈局可知:多層佈局重複設置了背景色致使Overdraw。
那麼咱們結合產品的需求(任何不結合具體場景優化都是耍流氓):

  • 去掉每行RelativeLayout的背景色;
  • 去掉每行TextView的背景色;

備註:一個容易忽略的點是咱們的Activity使用的Theme可能會默認的加上背景色,不須要的狀況下能夠去掉。

去掉背景色以後再看一下Overdraw;

設置界面優化後

對比一下優化後的佈局的顏色,能夠看出Overdraw降到了能夠接受的程度。

備註:有些過分繪製都是不可避免的,須要結合具體的佈局場景具體分析。

4、減小嵌套層次及控件個數

  • Android的佈局文件的加載是LayoutInflater利用pull解析方式來解析,而後根據節點名經過反射的方式建立出View對象實例;
  • 同時嵌套子View的位置受父View的影響,類如RelativeLayout、LinearLayout等常常須要measure兩次才能完成,而嵌套、相互嵌套、深層嵌套等的發生會使measure次數呈指數級增加,所費時間呈線性增加;

由此獲得結論:那麼隨着控件數量越多、佈局嵌套層次越深,展開佈局花費的時間幾乎是線性增加,性能也就越差。

幸運的是,咱們有Hierarchy Viewer這個方即可視化的工具,能夠獲得:樹形結構總覽、佈局view、每個View(包含子View)繪製所花費的時間及View總個數

備註: Hierarchy Viewer不能鏈接真機的問題能夠經過ViewServer這個庫解決;

設置界面初始狀態

設置界面初始狀態View個數及繪製時間

使用Hierarchy Viewer來看查看一下設置界面,能夠從下圖中獲得設置界面的一些數據及存在的問題:

  • 嵌套共計7層(僅setContentView設置的佈局),佈局嵌套過深;
  • measure時間1.569ms,layout時間0.120ms,draw時間16.128ms,合計共計耗時17.871ms;
  • 共繪製85個View,5個多餘定位,以及若干個無用佈局。

優化方案:

  • 將以前使用RelativeLayout來作的能夠替換的行換爲TextView;
  • 去掉以前多餘的無用佈局;

如今咱們再使用Hierarchy Viewer來檢測一下:

優化以後的佈局層次

優化以後的View個數及繪製時間

優化後:
1. 控件數量從85個減小到26個,減小69%;
2. 繪製時間從17.8ms減小到14.756ms,下降17%;

總結:
1. 一樣的UI效果可使用不一樣的佈局來完成,咱們須要考慮使用少的嵌套層次以及控件個數來完成,例如設置界面的普通一行,能夠像以前同樣使用RelativeLayout嵌套TextView以及ImageView來實現,可是明顯只使用TextView來作:嵌套層次、控件個數都更少。
2. 優化過程當中使用低端手機更易發現瓶頸;

5、Profiling GPU Rendering

根據Android性能優化典範,打開設備的GPU配置渲染工具——》在屏幕上顯示爲條形圖,能夠協助咱們定位UI渲染問題。

GPU呈現模式分析

從Android M版本開始,GPU Profiling工具把渲染操做拆解成以下8個詳細的步驟進行顯示。

渲染八步驟

  1. Swap Buffers:表示處理任務的時間,也能夠說是CPU等待GPU完成任務的時間,線條越高,表示GPU作的事情越多;
  2. Command Issue:表示執行任務的時間,這部分主要是Android進行2D渲染顯示列表的時間,爲了將內容繪製到屏幕上,Android須要使用Open GL ES的API接口來繪製顯示列表,紅色線條越高表示須要繪製的視圖更多;
  3. Sync & Upload:表示的是準備當前界面上有待繪製的圖片所耗費的時間,爲了減小該段區域的執行時間,咱們能夠減小屏幕上的圖片數量或者是縮小圖片的大小;
  4. Draw:表示測量和繪製視圖列表所須要的時間,藍色線條越高表示每一幀須要更新不少視圖,或者View的onDraw方法中作了耗時操做;
  5. Measure/Layout:表示佈局的onMeasure與onLayout所花費的時間,一旦時間過長,就須要仔細檢查本身的佈局是否是存在嚴重的性能問題;
  6. Animation:表示計算執行動畫所須要花費的時間,包含的動畫有ObjectAnimator,ViewPropertyAnimator,Transition等等。一旦這裏的執行時間過長,就須要檢查是否是使用了非官方的動畫工具或者是檢查動畫執行的過程當中是否是觸發了讀寫操做等等;
  7. Input Handling:表示系統處理輸入事件所耗費的時間,粗略等於對事件處理方法所執行的時間。一旦執行時間過長,意味着在處理用戶的輸入事件的地方執行了複雜的操做;
  8. Misc Time/Vsync Delay:表示在主線程執行了太多的任務,致使UI渲染跟不上vSync的信號而出現掉幀的狀況;出現該線條的時候,能夠在Log中看到這樣的日誌:

備註:GPU配置渲染工具雖然能夠定位出問題發生在某個步驟,可是並不能定位到具體的某一行;當咱們定位到某個步驟以後可使用工具TraceView進行更加詳細的定位。TraceView的使用能夠參照《Android性能優化(一)之啓動加速35%》

6、Use Tags

merge標籤

merge能夠用來合併佈局,減小布局的層級。merge多用於替換頂層FrameLayout或者include佈局時,用於消除由於引用佈局致使的多餘嵌套。
例如:須要顯示一個Button,佈局以下;

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Merge標籤演示" /> </LinearLayout>複製代碼

咱們經過UiAutoMatorViewer(無需root,相比Hierarchy Viewer只能查看佈局層次,不能獲得繪製時間)看一下佈局的層次

頂級視圖下多了LinearLayout

咱們使用Merge標籤對代碼進行修改;

<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Merge標籤演示" /> </merge>複製代碼

再看下佈局的層次:

使用Merge以後少了LinearLayout嵌套

能夠看到使用Merge標籤進行優化以後佈局嵌套就少了一層,Button做爲父視圖第三層FrameLayout的直接子視圖。

注意:merge標籤經常使用於減小布局嵌套層次,可是隻能用於根佈局。

ViewStub標籤

推遲建立對象、延遲初始化,不只能夠提升性能,也能夠節省內存(初始化對象不被建立)。Android定義了ViewStub類,ViewStub是輕量級且不可見的視圖,它沒有大小,沒有繪製功能,也不參與measure和layout,資源消耗很是低。
一、

<ViewStub
        android:id="@+id/mask"
        android:layout="@layout/b_me_mask"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />複製代碼
ViewStub viewStub = (ViewStub)view.findViewById(R.id.mask);
viewStub.inflate();複製代碼

App裏常見的視圖如蒙層、小紅點,以及網絡錯誤、沒有數據等公共視圖,使用頻率並不高,若是每一次都參與繪製實際上是浪費資源的,均可以藉助ViewStub標籤進行延遲初始化,僅當使用時纔去初始化。

include標籤

include標籤和佈局性能關係不大,主要用於佈局重用,通常和merge標籤配合使用,因和本文主題關聯不大,此處不展開討論。

7、其它

  1. 自定義控件時,注意在onDraw不能進行復雜運算;以及對待三方UI庫選擇高性能;
  2. 內存對佈局的影響:如同Misc Time/Vsync Delay步驟產生的影響,在以後內存優化的篇章詳細講。

8、總結

佈局優化的通用套路

  1. 調試GPU過分繪製,將Overdraw下降到合理範圍內;
  2. 減小嵌套層次及控件個數,保持view的樹形結構儘可能扁平(使用Hierarchy Viewer能夠方便的查看),同時移除全部不須要渲染的view;
  3. 使用GPU配置渲染工具,定位出問題發生在具體哪一個步驟,使用TraceView精準定位代碼;
  4. 使用標籤,Merge減小嵌套層次、ViewStub延遲初始化。

通過這幾步的優化以後,通常就不會再有佈局的性能問題,同時仍是要強調:優化是一個長期的工做,同時也必須結合具體場景:有取有舍!

參考:Android性能優化典範

歡迎關注微信公衆號:按期分享Java、Android乾貨!

歡迎關注
相關文章
相關標籤/搜索