Android 優化之路(一)佈局優化

前言

  • Android開發中,性能優化策略十分重要。由於我認爲預防永遠比治癒有意義重要得多。咱們不該該等到一個問題已經發生了,而且到了必定程度纔想起來須要重構代碼或者進行性能優化,經過早早的學習性能優化的思惟和工具能避免不少問題,糾正一些不良的編碼習慣,對Coder的編碼能力提升具備很大的意義。
  • 本文主要講解佈局優化,但願對大家有幫助

目錄


1. 對性能的影響

主要影響Android應用中頁面顯示的速度。1個頁面經過遞歸 完成測量 & 繪製過程 = measure、layout 過程,而這個過程過長則會給用戶帶來卡頓的視覺效果。android


2.優化思路

佈局優化的思路其實很簡單,就是儘可能減小布局文件的層級。佈局的層級少了,這就意味着Android繪製時工做量少了,那麼程序的性能天然就高了。性能優化


3. 具體優化方案

3.1 刪除佈局中無用的控件和層級

3.2 選擇耗費性能較少的佈局

若是佈局中便可使用LinearLayout也可使用RelativeLayout,那就採用LinearLayout。由於RelativeLayout在繪製時須要對子View分別進行了豎直和水平方向的兩次測量,而Linearlayout在繪製時是根據咱們設置的方向分別調用不一樣的測量方法。注意一點若是LinearLayout中子View使用了layout_weight屬性時一樣須要對子View進行兩次測量以肯定最終大小(對此不瞭解的小夥伴們能夠查看源碼中onMeasureonLayout方法本文就很少貼源碼)。LinearLayoutFrameLayout都是一種性能耗費低的佈局。可是不少時候單純經過一個LinearLayoutFrameLayout沒法實現產品的效果,須要經過嵌套的方式來完成。這種狀況下建議使用RelativeLayout,由於嵌套就至關於增長了佈局的層級,一樣會下降程序的性能。
bash

評論屢次提到Constraintlayout,因爲筆者用的較少忘了說,疏忽了,疏忽了[手動哭笑]。面對複雜度高的佈局(比RelativeLayoutLinearLayout屢次嵌套)Constraintlayout確實更簡單,繪製時間更短。但面對複雜度較低的佈局,RelativeLayoutConstraintLayoutonMesaure階段快數倍。下圖爲Hierarchy Viewer的測試結果(裏面一個TextView,一個ImageView,關於Hierarchy Viewer的使用會在下文佈局調優工具中):服務器

  • 性能耗費低的佈局 = 功能簡單 = FrameLayout、LinearLayout
  • 性能耗費高的佈局 = 功能複雜 = RelativeLayout(ConstraintLayout)
  • 嵌套所耗費的性能 > 單個佈局自己耗費的性能

3.3 提升佈局的複用性(使用 <include> 佈局標籤)

使用 <include>標籤提取佈局間的公共部分,經過提升佈局的複用性從而減小測量 & 繪製時間網絡

<!--抽取出的公共佈局:include_title.xml-->
<?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"
    android:background="@color/colorAccent">

    <ImageView
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_alignParentLeft="true"
        android:paddingLeft="15dp"
        android:src="@mipmap/ic_titilebar_back"/>

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:text="@string/title"
        android:textColor="@color/white"
        android:textSize="18sp"/>

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_alignParentRight="true"
        android:gravity="center"
        android:padding="15dp"
        android:text="@string/more"
        android:textColor="@color/white"
        android:textSize="16sp"/>

</RelativeLayout>


<!--佈局:activity_main.xl引用公共佈局include_title.xml-->
<?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">

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

    <TextView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:text="Hello World!" />

</LinearLayout>

複製代碼

示例圖3.3

<include> 標籤只支持以android:layout_開頭的屬性(android:id除外),須要注意一點若是使用了android:layout_*這種屬性,那麼要求android:layout_width 和android:layout_height必須存在,不然其餘android:layout_*形式的屬性沒法生效,下面是一個指定了android:layout_*屬性的示例app

<include
    android:id="@+id/include_title"
    android:layout_width="match_parent"
    android:layout_height="48dp"
    layout="@layout/include_title"/>
複製代碼

3.4 減小布局的層級(使用 <merge> 佈局標籤)

<merge> 佈局標籤通常和 <include> 標籤一塊兒使用從而減小布局的層級。例如當前佈局是一個豎直方向的LinearLayout,這個時候若是被包含的佈局也採用了豎直方向的LinearLayout,那麼顯然被包含的佈局文件中的LinearLayout是多餘的,這時經過 <merge> 佈局標籤就能夠去掉多餘的那一層LinearLayout。以下所示:ide

<!--抽取出的公共佈局:include_title.xml-->
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android">
    
    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Button" />

    <Button
        android:id="@+id/button2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Button" />

</merge>

<!--佈局:activity_main.xl引用公共佈局include_title.xml-->
<?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">

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

</LinearLayout>

複製代碼

示例圖3.4

3.5 減小初次測量 & 繪製時間

3.5.1使用 <ViewStub> 標籤

ViewStub繼承了View,它很是輕量級且寬和高都爲0,所以它自己不參與任何的繪製過程,避免資源的浪費,減小渲染時間,在須要的時候才加載View。所以ViewStub的意義在於按需求加載所需的佈局,在實際開發中,不少佈局在正常狀況下不會顯示,好比加載數據暫無數據,網絡異常等界面,這個時候就不必在整個界面初始化的時候將其加載進來,經過ViewStub就能夠作到在使用時在加載,提升了程序初始化時的性能。以下一個ViewStub的示例:工具

<!--暫無數據頁:empty_data.xml-->
<?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:gravity="center"
    android:orientation="vertical">

    <ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@mipmap/ic_empty_order"/>

    <TextView
        android:layout_below="@+id/iv_empty"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="暫無數據"/>

</LinearLayout>

<!--佈局activity_main.xml-->
<?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:gravity="center"
    android:orientation="vertical">

    //view_stub是ViewStub的id,empty是empty_data.xml這個佈局根元素的id
    <ViewStub
        android:id="@+id/view_stub"            
        android:inflatedId="@+id/empty"         
        android:layout="@layout/empty_data"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        />

</LinearLayout>

<!--MainActivity-->
@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ButterKnife.bind(this);
        //加載ViewStub中的佈局的兩種方式setVisibility或inflate
        mViewStub.setVisibility(View.VISIBLE);
        mViewStub.inflate();
        
    }

複製代碼

示例圖3.5.1

使用ViewStub需注意:當ViewStub經過setVisibility或inflate方法加載後,ViewStub就會被它內部的佈局替換掉,這個時候ViewStub就再也不是佈局結構中的的一部分。目前ViewStub中的layout還不支持使用<merge> 標籤。佈局

3.5.2儘量少用佈局屬性 wrap_content

佈局屬性 wrap_content 會增長佈局測量時計算成本,應儘量少用post

3.6 減小控件的使用(善用控件屬性)

在繪製佈局中,某些狀況下咱們能夠省去部分控件的使用。下文介紹幾種常見的狀況:

3.6.1 TextView文字加圖片

示例圖3.6.1
上圖佈局,一般想到的是一個相對佈局裏包含一個TextView和兩個ImageView。事實上咱們只須要一個TextView就能夠實現

<TextView
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:layout_marginTop="20dp"
        android:paddingLeft="16dp"
        android:paddingRight="16dp"
        android:gravity="center_vertical"
        android:drawableLeft="@mipmap/icon_my_unlock"   // 設置左邊顯示的icon  
        android:drawablePadding="10dp"                  // 設置icon和文本的間距  
        android:drawableRight="@mipmap/icon_right"      // 設置右邊顯示的icon
        android:text="@string/account_unlock"
        />
複製代碼

3.6.2 LinearLayout分割線

示例圖3.6.2
上圖佈局,一般是用高爲1dp的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"
    android:divider="@drawable/divider_line"
    android:dividerPadding="16dp"
    android:showDividers="middle"
    android:orientation="vertical">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:paddingLeft="16dp"
        android:paddingRight="16dp"
        android:gravity="center_vertical"
        android:drawableLeft="@mipmap/icon_my_unlock"
        android:drawablePadding="10dp"
        android:drawableRight="@mipmap/icon_right"
        android:background="@color/color_FFFFFF"
        android:text="@string/account_unlock"
        />
    <TextView
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:paddingLeft="16dp"
        android:paddingRight="16dp"
        android:gravity="center_vertical"
        android:drawableLeft="@mipmap/icon_my_unlock"
        android:drawablePadding="10dp"
        android:drawableRight="@mipmap/icon_right"
        android:background="@color/color_FFFFFF"
        android:text="@string/account_unlock"
        />

    <TextView
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:paddingLeft="16dp"
        android:paddingRight="16dp"
        android:gravity="center_vertical"
        android:drawableLeft="@mipmap/icon_my_unlock"
        android:drawablePadding="10dp"
        android:drawableRight="@mipmap/icon_right"
        android:background="@color/color_FFFFFF"
        android:text="@string/account_unlock"
        />

<!--Shape:divider_line.xml-->
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">

    <solid android:color="@color/colorPrimary"/>
    <size android:height="1dp"/>

</shape>

</LinearLayout>
複製代碼

核心代碼就是對LinearLayout設置divider

  • divider 設置分割線樣式,需注意不能簡單隻給個顏色值,好比#f00或者@color/xxx這樣,drawable必定要是個有長、寬概念的drawable,固然你也能夠直接一張圖片當divider。
  • dividerPadding 設置分割線兩邊的間距
  • showDividers 設置分割線顯示位置。其中middle控件之間顯示;beginning第一個控件上面顯示分割線;end最後一個控件下面顯示分割線,none不顯示分割線

3.6.3 TextView的行間距和佔位符的使用

示例圖3.6.3
上圖佈局,一般想到的是一個線性佈局裏包含3個TextView。事實上咱們只須要一個TextView就能夠實現

<TextView
        android:id="@+id/text"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:lineSpacingExtra="10dp"
        android:text="@string/test_text"
        android:textSize="20sp"
        android:textColor="@color/colorPrimary"/>
複製代碼

經過lineSpacingExtra設置行間隔,test_text內容爲:

<string name="test_text">標題:%1$s\n時間:%2$s\n內容:%3$s</string>
複製代碼

在MainActivity中的使用

mText.setText(String.format(getResources().getString(R.string.test_text), "測試", "2018-8-9","測試測試"));
複製代碼

佔位符的使用方法:%n表示第n位要被替換的,$s表示字符串類型佔位符,$d表示整型佔位符,$f表示浮點型佔位符


4. 佈局調優工具

在實際開發中哪怕注意了上述的優化方案,不免仍是會出現佈局性能的問題。這時咱們可以使用佈局調優工具來分析問題。本文將介紹經常使用的幾種工具。

4.1 Lint

Lint 是Android Studio 提供的 代碼掃描分析工具,它能夠幫助咱們發現代碼結構/質量問題,同時提供一些解決方案,並且這個過程不須要咱們手寫測試用例。 Lint 的使用路徑: 工具欄 -> Analyze -> Inspect Code

示例圖4.1.1

默認是檢查整個項目,咱們能夠點擊 Custom scope 自定義檢查範圍

  • Project Files:全部項目文件
  • Project Production Files:項目的代碼文件
  • Project Test Files:項目的測試文件
  • OpenFiles:當前打開的文件
  • Module ‘app’:主要的 app 模塊
  • Current File:當前文件

示例圖4.1.2

固然你也能夠選擇特定的類進行檢查點擊下圖紅色箭頭標識的地方

示例圖4.1.3

點擊「+」號新增一個檢查範圍:

  • Local:只能當前項目使用
  • Shared:其餘 Android Studio 項目也可使用

示例圖4.1.4
選擇Shared,默認按項目顯示,檢查的文件數爲 0 。下圖紅色框中4個按鈕表示要操做的類型

  • Include:包括當前文件夾內的文件,但不包括他的子文件夾
  • Include Recursively:包括當前文件夾以及它的子文件夾內全部的文件夾,遞歸添加
  • Exclude:移除當前文件夾,不包括子文件夾
  • Exclude Recursively:移除當前文件夾及全部子文件夾

示例圖4.1.4

咱們左擊想掃描的文件,點擊右邊對應的按鈕。能夠看到文件邊色了,紅框顯示需掃描24個文件,點擊OK→OK

示例圖4.1.5

稍等一下子,會彈出 Inspection 對話框,顯示檢查結果。

示例圖4.1.6

Lint 的警告嚴重程度有如下幾種:

  • Unused Entry:沒有使用的屬性,灰色,很不起眼
  • Typo:拼寫錯誤,綠色波浪下劃線,也不太起眼
  • Server Problem:服務器錯誤?好像不是
  • Info:註釋文檔,綠色,比較顯眼
  • Weak Warning:比較弱的警告,提示比較弱
  • Warning:警告,略微顯眼一點
  • Error:錯誤,最顯眼的一個
    示例圖4.1.7

本文對Lint的介紹就到此若是對Lint想了解更多的小夥伴能夠點擊Lint

4.2 Hierarchy Viewer

Hierarchy Viewer 是Android Studio 提供的UI性能檢測工具。可得到UI佈局設計結構 & 各類屬性信息,幫助咱們優化佈局設計 。

使用Hierarchy Viewer ,您的設備必須運行Android 4.1或更高版本。若是您是使用真機的話需注意如下兩點:

  • 在您的設備上啓用開發者選項
  • 在開發計算機上設置環境變量 ANDROID_HVPROTO=ddm
    此變量告訴Hierarchy Viewer使用ddm 協議鏈接到設備,該協議與DDMS協議相同。須要注意的是,主機上只能有一個鏈接到設備的進程,所以您必須終止任何其餘DDMS會話才能運行Hierarchy Viewer。

Hierarchy Viewer 的使用路徑: 工具欄 ->Tools->Android->Android Device Monitor(默認是顯示DDMS窗口,更改可點擊Open Perspective->Hierarchy Viewer,也能夠直接點擊 Hierarchy Viewer Button),以下圖所示:

示例圖4.2.1

更改後顯示Hierarchy Viewer的窗口以下圖所示:

示例圖4.2.2

若是您看的的視圖排列不同,可選擇 Window->Reset Perspective 返回默認佈局。下面介紹下基本窗口:

  • Window(左上角)顯示設備信息,app以包名顯示,雙擊選中。
  • View Properties(左上角)顯示視圖的屬性
  • Tree View(中心):顯示視圖層次結構的樹視圖。您可使用鼠標拖動樹和縮放樹,並在底部使用縮放控件。每一個節點都指示它的View類名和ID名稱。
  • Tree Overview(右上角):使您能夠鳥瞰應用程序的完整視圖層次結構。移動灰色矩形以更改樹視圖中可見的視口。
  • Layout View(右下角):顯示佈局的線框視圖。當前所選視圖的輪廓爲紅色,其父視圖爲淺紅色。單擊此處的視圖也會在樹視圖中選擇它,反之亦然。
    示例圖4.2.3
    示例圖4.2.4

上圖圖標的使用左到右介紹:

  • 從新加載視圖層次結構
  • 該視圖彈窗
  • 使佈局無效
  • 請求佈局
  • 從新加載視圖層次結構
  • 分析視圖,顯示視圖的耗時

示例圖4.2.5
所選的節點上方會有個小窗口顯示子View數和 MeasureLayoutDraw繪製所需時間。
所選節點的每一個子視圖都有三個點,能夠是綠色,黃色或紅色。

  • 左點表示渲染管道的繪製過程。
  • 中間點表明佈局階段。
  • 右點表示執行階段。

這些點大體對應於處理管道的度量,佈局和繪製階段。點的顏色表示該節點相對於本地系列中全部其餘配置節點的相對性能。

  • 綠色 表示視圖渲染速度比其餘視圖中的至少一半快。
  • 黃色 表示視圖呈現的速度比其餘視圖的下半部分快。
  • 紅色 表示視圖是最慢的一半視圖之一。

所測量的是每一個節點相對於兄弟視圖的性能,所以配置文件中始終存在紅色節點,除非全部視圖執行相同,而且它並不必定意味着紅色節點執行得不好(僅限於它是最慢的視圖)在本地視圖組中)。

本文對Hierarchy Viewer的介紹就到此了,想了解更多的小夥伴能夠點擊Hierarchy Viewer

4.3 開發者選項(調試GPU過分繪製)

開發者選項是Android 系統爲開發者提供的一個APP驗證、調試、優化等各類功能的入口(需Android 4.1以上)。本文主要對GPU過分繪製菜單介紹,對其餘菜單功能有興趣的小夥伴能夠參考探索Android手機開發者選項

GPU過分繪製是指在屏幕上某個像素在同一幀的時間內被繪製屢次,產生的主要緣由有兩個:

  • 在XML佈局中,控件有重疊卻都設置背景
  • View的OnDraw中同一區域繪製屢次

4.3.1 開啓開發者選項

不一樣手機開發商都對手機界面作了定製化處理,打開手機開發者選項的方式各不相同,但基本的功能菜單都是相似的。因爲筆者如今用的是小米5s Plus,所以打開方式是設置->個人設備->所有參數->連續點擊MIUI版本,提示開發者模式開啓便可(不一樣MIUI版本開啓路徑也不同)。而後設置->更多設置,高級設置或者其餘設置等等菜單中便可看到開發者選項的菜單了。

4.3.2 開啓GPU過分繪製

在開發者選項中點擊調試GPU過分繪製,以下圖所示

示例圖4.3.2.1
示例圖4.3.2.2

這時屏幕會出現各類顏色,每種顏色的定義爲:

  • 原色: 沒有過分繪製 – 每一個像素在屏幕上繪製了一次。
  • 藍色: 一次過分繪製 – 每一個像素點在屏幕上繪製了兩次。
  • 綠色: 兩次過分繪製 – 每一個像素點在屏幕上繪製了三次。
  • 粉色: 三次過分繪製 – 每一個像素點在屏幕上繪製了四次。
  • 紅色: 四次或四次以上過分繪製 – 每一個像素點在屏幕上繪製了五次或者五次以上。

以上就是本文對佈局調優工具的介紹,固然還有其餘佈局調優工具如Systrace本文就不一一介紹了。


結尾

本文主要講解Android 性能優化中的 佈局優化。如對小夥伴們有幫助,麻煩給個喜歡,不足之處請留言私聊點出謝謝!
對Flutter有興趣的可參考Flutter學習之路(一)Flutter簡介及Window下開發環境搭建

相關文章
相關標籤/搜索