Android性能優化之布局優化


  Android性能優化方面也有很多文章了,這里就做一個總結,從原理到方法,工具等做一個簡單的了解,從而可以慢慢地改變編碼風格,從而提高性能。

一、Android系統是如何處理UI組件的更新操作的

  既然和布局相關,那么我們需要了解Android系統是如何處理UI組件的更新操作的。

  1、Android需要把XML布局文件轉換成GPU能夠識別並繪制的對象。這個操作是在DisplayList的幫助下完成的。DisplayList持有所有將要交給GPU繪制到屏幕上的數據信息。

  2、CPU負責把UI組件計算成Polygons,Texture紋理,然后交給GPU進行柵格化渲染。

  3、GPU進行柵格化渲染。

  4、硬件展示在屏幕上。

  那么什么是柵格化呢?Resterization柵格化是繪制那些Button,Shape,Path,String,Bitmap等組件最基礎的操作,它把那些組件拆分到不同的像素上進行顯示,如上圖所示,手機的屏幕其實都是有一個一個小格子組成的圖片,而當這些小格子非常非常小的時候,那么就看起來像上圖第一個@中所示一樣了。這是一個很費時的操作,因此這也是為什么要引入GPU原因。
  每次從CPU轉移到GPU是一件很麻煩的事情,所幸的是OpenGL ES可以把那些需要渲染的文理Hold在GPU的Memory里面,在下次需要渲染的時候直接進行操作。
Android里面那些由主題所提供的資源,例如Bitmap,Drawables都是一起打包到統一的Texture文理當中, 然后再傳遞到GPU里面,這意味着每次你需要使用這些資源的時候,都是直接從文理里面進行獲取渲染的。當然隨着UI組件的越來越豐富,有了更多演變的形態。例如顯示圖片的時候,需要先經過CPU的計算加載到內存中,然后傳遞到GPU進行渲染。文字的顯示更加復雜,需要先經過CPU換算成紋理,然后再交給GPU進行渲染,回到CPU繪制單個字符的時候,再重新引用經過GPU渲染的內容。動畫則是一個更加復雜的流程。

  為了能夠使得APP流暢,我們需要在每一幀16ms以內完成所有的CPU與GPU計算,繪制,渲染等等操作。也就是幀率為60fps,為什么幀率要為60fps呢,因為人眼與大腦之間的協作無法感知超過60fps的畫面更新。開發app的性能目標就是保持60fps,這意味着每一幀你只有16ms=1000/60的時間來處理所有的任務。這里需要了解下刷新率和幀率:
  Refresh Rate:代表了屏幕在一秒內刷新屏幕的次數,這取決於硬件的固定參數,例如60HZ。

  Frame Rate:代表了GPU在一秒內揮之操作的幀數,例如30fps,60fps。
  此外這里引入了VSYNC的機制,Android就是通過VSYNC信號來同步UI繪制和動畫,使得它們可以獲得一個達到60fps的固定的幀率。GPU會獲取圖形數據進行渲染,然后硬件負責把渲染后的內容呈現到屏幕上,他們兩者不停地進行協作。不幸的是,刷新頻率和幀率並不是總能保持相同的節奏。如果發生幀率與刷新頻率不一致的情況,就會容易出現顯示內容發生斷裂,重疊。
  幀率超過刷新率只是理想的狀況,在超過60fps的情況下,GPU所產生的幀數據會因為等待VSYNC的刷新信息而被Hold住,這樣能夠保持每次刷新都有實際的新的數據可以顯示。


  但是我們遇到更多的情況是幀率小於刷新頻率。在這種情況下,某些幀顯示的畫面內容就會與上一幀的畫面相同。糟糕的事情是,幀率從超過60fps突然掉到60fps以下,這樣就會發生LAG,JANK,HITCHING等卡頓停頓的不順滑的情況,這也是用戶感受不好的原因所在。
  在某個View第一次需要被渲染時,DisplayList會因此而被創建,當這個View要顯示到屏幕上時,我們會執行GPU的繪制指令來進行渲染。如果你在后續有執行類似移動這個view的位置等操作而需要再次渲染這個View時,我們就僅僅需要額外操作一次渲染指令就夠了。然而如果修改了View中的某些可見組件,那么之前的DisplayList就無法繼續使用了,我們需要回頭重新創建一個DisplayList並且重新執行渲染指令並更新到屏幕上。
  需要注意的是:任何時候View中的繪制內容發生變化時,都會重新執行創建DisplayList,渲染DisplayList,更新到屏幕上等一系列操作。這個流程的表現性能取決於View的復雜程度,View的狀態變化以及渲染管道的執行性能。
  所以我們需要盡量減少Overdraw。
  Overdraw(過度繪制):描述的是屏幕上的某個像素在同一幀的時間內被繪制了多次。在多層次的UI結構里面,如果不可見的UI也在做繪制的操作,就會導致某些像素區域被繪制了多次,浪費大量的CPU以及GPU資源。(可以通過開發者選項,打開Show GPU Overdraw的選項,觀察UI上的Overdraw情況)。


  藍色、淡綠、淡紅,深紅代表了4種不同程度的Overdraw的情況,我們的目標就是盡量減少紅色Overdraw,看到更多的藍色區域。

二、Android布局優化常用方法

  綜上,布局的優化其實說白了就是減少層級,越簡單越好,減少overdraw,就能更好的突出性能。

下面介紹幾種布局優化的方式:

1、首先是善用相對布局Relativelayout

  在RelativeLayout和LinearLayout同時能夠滿足需求時,盡量使用RelativeLayout,這一點可以從我們MainActivity默認布局就可以看出,默認是RelativeLayout,因為可以通過扁平的RelativeLayout降低LinearLayout嵌套所產生布局樹的層級。
  Android提供了幾種方便的布局管理器,大多數時候,你只需要這些布局的一部分基本特性去實現UI。 一般情況下用LinearLayout的時候總會比RelativeLayout多一個View的層級。而每次往應用里面增加一個View,或者增加一個布局管理器的時候,都會增加運行時對系統的消耗,因此這樣就會導致界面初始化、布局、繪制的過程變慢。還是舉個例子吧,先看一下布局圖。
  首先用LinearLayout方式來實現:

<?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">

    <ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="10dp"
        android:src="@mipmap/ic_launcher" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginLeft="10dp"
            android:layout_marginTop="16dp"
            android:text="這個是LinearLayout"
            android:textSize="16sp" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginLeft="10dp"
            android:layout_marginTop="10dp"
            android:text="這個是LinearLayout,這個是LinearLayout"
            android:textSize="12sp" />
    </LinearLayout>

</LinearLayout>

 

  接着是RelativeLayout方式:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ImageView
        android:id="@+id/iv_image"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="10dp"
        android:src="@mipmap/ic_launcher" />

    <TextView
        android:id="@+id/tv_title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="10dp"
        android:layout_marginTop="16dp"
        android:layout_toRightOf="@+id/iv_image"
        android:text="這個是LinearLayout"
        android:textSize="16sp" />

    <TextView
        android:id="@+id/tv_content"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@+id/tv_title"
        android:layout_marginLeft="10dp"
        android:layout_marginTop="10dp"
        android:layout_toRightOf="@+id/iv_image"
        android:text="這個是LinearLayout,這個是LinearLayout"
        android:textSize="12sp" />

</RelativeLayout>

 

 

  很明顯Relativelayout的層級比linearlayout的層級少了一層,這個界面比較簡單,但是如果界面很復雜的情況下,那么怎么來優化或者看到一目了然的層級樹呢?別擔心,Android自帶了工具,下面就介紹下Hierarchy View的簡單使用吧。
  為了考慮安全問題,真機上不好嘗試,雖然也有辦法可以解決,但是現在模擬器還是挺快的,也不用折騰那么多了,就可以直接用模擬器來實現好了,接着模擬器運行app,打開需要獲取view層級的那個界面。
  然后依次點擊菜單Tools -> Android -> Android Device Monitor。


打開Android Device Monitor后,選中Hierarchy View,然后通過Hierarchy View來獲取當前的View的分級圖。

  
  接着我們就可以來看一下兩個分級的不同了。這里主要截取兩個不同的地方:
  首先是LinearLayout:


  接着是RelativeLayout:


  很明顯的可以看出來RelativeLayout比LinearLayout少了一個層級,當然渲染的時間也是大大減少了。

2、布局優化的另外一種手段就是使用抽象布局標簽include、merge、ViewStub

2.1、首先是include標簽

  include標簽常用於將布局中的公共部分提取出來,比如我們要在activity_main.xml中需要上述LinearLayout的數據,那么就可以直接include進去了。

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.jared.layoutoptimise.MainActivity">

    <include layout="@layout/item_test_linear_layout" />
    
</RelativeLayout>

 2.2、merge標簽:

   merge標簽是作為include標簽的一種輔助擴展來使用,它的主要作用是為了防止在引用布局文件時產生多余的布局嵌套。
  Android渲染需要消耗時間,布局越復雜,性能就越差。如上述include標簽引入了之前的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">

    <ImageView
        android:id="@+id/iv_image"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="10dp"
        android:src="@mipmap/ic_launcher" />

    <TextView
        android:id="@+id/tv_title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="10dp"
        android:layout_marginTop="16dp"
        android:layout_toRightOf="@+id/iv_image"
        android:text="這個是MergeLayout"
        android:textSize="16sp" />

    <TextView
        android:id="@+id/tv_content"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@+id/tv_title"
        android:layout_marginLeft="10dp"
        android:layout_marginTop="10dp"
        android:layout_toRightOf="@+id/iv_image"
        android:text="這個是MergeLayout,這個是MergeLayout"
        android:textSize="12sp" />

</merge>

  activity_main就可以直接include了。

<RelativeLayout 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"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.jared.layoutoptimise.MainActivity">

    <include layout="@layout/item_merge_layout" />

</RelativeLayout>

  然后看下層級:

2.3、viewstub標簽:

  viewstub是view的子類。他是一個輕量級View, 隱藏的,沒有尺寸的View。他可以用來在程序運行時簡單的填充布局文件。接着簡單試用下viewstub吧。首先修改activity_main.xml文件如下:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.jared.layoutoptimise.MainActivity">

    <include
        android:id="@+id/layout_merge"
        layout="@layout/item_merge_layout" />

    <Button
        android:id="@+id/btn_view_show"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        android:text="顯示ViewStub"
        android:textAllCaps="false"
        android:layout_below="@+id/tv_content"/>

    <Button
        android:id="@+id/btn_view_hide"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        android:layout_marginLeft="50dp"
        android:layout_toRightOf="@+id/btn_view_show"
        android:text="隱藏ViewStub"
        android:textAllCaps="false"
        android:layout_below="@+id/tv_content"/>

    <ViewStub
        android:id="@+id/vs_test"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout="@layout/item_test_linear_layout"
        android:layout_below="@+id/btn_view_show"
        android:layout_marginTop="10dp" />

</RelativeLayout>

  這里的ViewStub控件的layout指定為item_test_linear_layout。當點擊button隱藏的時候不會顯示item_test_linear_layout,而點擊button顯示的時候就會用item_test_linear_layout替代ViewStub。

3、Android最新的布局方式ConstaintLayout  

  ConstraintLayout允許你在不適用任何嵌套的情況下創建大型而又復雜的布局。它與RelativeLayout非常相似,所有的view都依賴於兄弟控件和父控件的相對關系。但是,ConstraintLayout比RelativeLayout更加靈活,目前在AndroidStudio中使用也十分方便,就和以前的拖拉控件十分相似。那么怎么使用呢?
  首先是安裝Constaintlayout了。Android SDK -> SDK Tools -> Support Repository中的ConstrainLayout for Android和Solver for ConstaintLayout。

 

  然后build.gradle中添加:

compile 'com.android.support.constraint:constraint-layout:1.0.0-beta4'

  然后同步下就可以正常使用ConstaintLayout了。

  接着我們來實現上述的布局文件,首先把布局按照constraintLayout的方式來布。效果如下,其實很像ios的布局,具體怎么用的就不一一介紹了:

  看下代碼是怎樣的:

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
    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:id="@+id/lay_root"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ImageView
        android:id="@+id/iv_image"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="10dp"
        android:src="@mipmap/ic_launcher"
        android:layout_marginStart="16dp"
        app:layout_constraintLeft_toLeftOf="parent"
        android:layout_marginLeft="16dp"
        android:layout_marginTop="16dp"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/tv_title"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_toRightOf="@+id/iv_image"
        android:text="這個是ConstraintLayout"
        android:textSize="16sp"
        app:layout_constraintLeft_toRightOf="@+id/iv_image"
        android:layout_marginStart="20dp"
        android:layout_marginLeft="20dp"
        android:layout_marginTop="20dp"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/tv_content"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_below="@+id/tv_title"
        android:layout_toRightOf="@+id/iv_image"
        android:text="這個是ConstraintLayout,這個是RelativeLayout"
        android:textSize="12sp"
        app:layout_constraintTop_toBottomOf="@+id/tv_title"
        android:layout_marginTop="16dp"
        app:layout_constraintLeft_toLeftOf="@+id/tv_title" />

</android.support.constraint.ConstraintLayout>

   最后運行之后看下布局的層級如下:

  這個是簡單的布局,如果是復雜的布局的話,那用Constaintlayout的話最多就兩個層級了,不像Relative和Linear一樣一層嵌套一層的。

4、利用Android Lint工具尋求可能優化布局的層次

  一些Lint規則如下:
  1、使用組合控件: 包含了一個ImageView以及一個TextView控件的LinearLayout如果能夠作為一個組合控件將會被更有效的處理。
  2、合並作為根節點的幀布局(Framelayout) :如果一個幀布局時布局文件中的根節點,而且它沒有背景圖片或者padding等,更有效的方式是使用merge標簽替換該Framelayout標簽 。
  3、無用的葉子節點:通常來說如果一個布局控件沒有子視圖或者背景圖片,那么該布局控件時可以被移除(由於它處於 invisible狀態)。
  4、無用的父節點 :如果一個父視圖即有子視圖,但沒有兄弟視圖節點,該視圖不是ScrollView控件或者根節點,並且它沒有背景圖片,也是可以被移除的,移除之后,該父視圖的所有子視圖都直接遷移至之前父視圖的布局層次。同樣能夠使解析布局以及布局層次更有效。
  5、過深的布局層次:內嵌過多的布局總是低效率地。考慮使用一些扁平的布局控件,例如 RelativeLayout、GridLayout ,來改善布局過程。默認最大的布局深度為10 。

 

github代碼

  

By Hoolay-Android : 東月之神

 

參考:

  http://www.oschina.net/news/60157/android-performance-patterns

  https://developer.android.com/training/improving-layouts/index.html

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM