參考《Android性能優化之布局優化》 侵刪
繪制的原理
- Android需要把XML布局文件轉換成GPU能夠識別并繪制的對象。這個操作是在DisplayList的幫助下完成的。DisplayList持有所有將要交給GPU繪制到屏幕上的數據信息。
- CPU負責把UI組件計算成Polygons,Texture紋理,然后交給GPU進行柵格化渲染。
- GPU進行柵格化渲染。
- 硬件展示在屏幕上。
為了能夠使得APP流暢,我們需要在每一幀16ms以內完成所有的CPU與GPU計算,繪制,渲染等等操作。也就是幀率為60fps,為什么幀率要為60fps呢,因為人眼與大腦之間的協作無法感知超過60fps的畫面更新。這個流程的表現性能取決于View的復雜程度,View的狀態變化以及渲染管道的執行性能。
Overdraw(過度繪制):描述的是屏幕上的某個像素在同一幀的時間內被繪制了多次。在多層次的UI結構里面,如果不可見的UI也在做繪制的操作,就會導致某些像素區域被繪制了多次,浪費大量的CPU以及GPU資源。(可以通過開發者選項,打開Show GPU Overdraw的選項,觀察UI上的Overdraw情況)。
所以我們需要盡量減少Overdraw。
Android布局優化常用方法
綜上,布局的優化其實說白了就是減少層級,越簡單越好,減少overdraw,就能更好的突出性能。
1. 首先是善用相對布局RelativeLayout。布局里面有多個LinearLayout,如果可以使用RelativeLayout進行改變布局,則用RelativeLayout,減少不必要的層級,減少overdraw。這個可以使用Android IDE自帶的工具Hierarchy View去看看布局層級關系。
2. 使用抽象布局標簽include、merge、ViewStub
include標簽常用于將布局中的公共部分提取出來,比如我們要在activity_main.xml中需要上述LinearLayout的數據,那么就可以直接include進去了。
共用的一個comm_layout.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="horizontal">
<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:text="這個是MergeLayout"
android:textSize="16sp" />
</LinearLayout>
<?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">
<include layout="@layout/comm_layout" />
</RelativeLayout>
merge標簽是作為include標簽的一種輔助擴展來使用,它的主要作用是為了防止在引用布局文件時產生多余的布局嵌套。如上述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"
android:orientation="horizontal">
<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:text="這個是MergeLayout"
android:textSize="16sp"/>
</merge>
viewstub我們過去在布局里暫時不顯示的布局,都會用Visible.GONE來先隱藏,然后在業務代碼里將其顯示,這樣做的優點是比較靈活,但是缺點是耗費資源,即它仍會被Inflate,被實例化,設置屬性。而viewstub是view的子類,他是一個輕量級View, 隱藏的,沒有尺寸的View。他可以用來在程序運行時簡單的填充布局文件,去電是ViewStub指定的布局被Inflate后,就不能再通過ViewStude來控制它了。如下:
<?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">
<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沒那么靈活,限制比較多:
1. ViewStub只能Inflate一次,之后ViewStub對象會被置為空。
2. 某些布局屬性要加在ViewStub而不是實際的布局上面,才會起作用,比如上面用的android:layout_margin*系列屬性,如果加在TextView上面,則不會起作用,需要放在它的ViewStub上面才會起作用。而ViewStub的屬性在inflate()后會都傳給相應的布局。
ConstraintLayout允許你在不適用任何嵌套的情況下創建大型而又復雜的布局。它與RelativeLayout非常相似,所有的view都依賴于兄弟控件和父控件的相對關系。但是,ConstraintLayout比RelativeLayout更加靈活,目前在AndroidStudio中使用也十分方便,就和以前的拖拉控件十分相似。
一些Lint規則如下:
1. 使用組合控件: 包含了一個ImageView以及一個TextView控件的LinearLayout如果能夠作為一個組合控件將會被更有效的處理。
2. 合并作為根節點的幀布局(Framelayout) :如果一個幀布局時布局文件中的根節點,而且它沒有背景圖片或者padding等,更有效的方式是使用merge標簽替換該Framelayout標簽 。
3. 無用的葉子節點:通常來說如果一個布局控件沒有子視圖或者背景圖片,那么該布局控件時可以被移除(由于它處于 invisible狀態)。
4. 無用的父節點 :如果一個父視圖即有子視圖,但沒有兄弟視圖節點,該視圖不是ScrollView控件或者根節點,并且它沒有背景圖片,也是可以被移除的,移除之后,該父視圖的所有子視圖都直接遷移至之前父視圖的布局層次。同樣能夠使解析布局以及布局層次更有效。
5. 過深的布局層次:內嵌過多的布局總是低效率地。考慮使用一些扁平的布局控件,例如 RelativeLayout、GridLayout ,來改善布局過程。默認最大的布局深度為10 。