Android佈局優化
安卓佈局優化
在進行Android應用的介面編寫時,如果建立的佈局層次結構比較複雜,View樹巢狀的層次比較深,那麼將會使得介面展現的時間比較長,導致應用執行起來越來越慢。Android佈局的優化是實現應用響應靈敏的基礎。遵循一些通用的編碼準則有利於實現這個目標。
include 標籤共享佈局
在使用XML檔案編寫Android應用的介面時,經常會遇到在不同的頁面中需要實現相同的佈局,這時候就會寫出重複的程式碼。例如由於幾乎每個頁面都有標題欄,因此,在每個頁面中都要實現XML程式碼。這種方式顯然是不建議的,最佳實踐是將通用的佈局抽取出來,獨立成一個XML檔案,然後在需要用到的頁面中使用include便籤引入進來,不僅減少了程式碼量,而且在需要修改標題欄的時候,只需修改這個獨立的檔案,而不是到每個頁面中進行重複的修改勞動。假設我們抽取出來的標題欄佈局名為layout_common_titile.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"
>
<!--此處只是演示,實際專案程式碼要比這個複雜一些-->
<TextView
android:layout_width ="wrap_content"
android:layout_height="wrap_content"
android:text="標題欄"
android:layout_centerInParent="true"
/>
</RelativeLayout>
在頁面MainActivity的佈局中,使用到這個標題欄,引入的方式如下。
<?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.okii.layoutoptimize.MainActivity">
<!--引入外部共享佈局 layout_common_title-->
<include layout="@layout/layout_common_title"
android:layout_height="56dp"
android:layout_width="match_parent"
/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"/>
</RelativeLayout>
ViewStu標籤實現延遲載入
ViewStub 是一種不可視並且大小為0的檢視,它可以延遲到執行時才填充(inflate)佈局資源。當 ViewStub 設定為可見或者被inflate之後,就會填充佈局資源,之後這個ViewStub就會被填充的檢視所代替,跟普通的檢視不再有任何區別。ViewStub 的使用場景在於某個頁面需要根據使用者互動或者其他條件動態調整顯示的檢視,例如某個頁面是從服務端獲取資料進行展現的,正常情況下會展示獲取到的資料,但在網路不可用的情況下,需要展示一張不可用的圖片和相關提示資訊,大多數情況下,這個不可用的檢視是不需要顯示出來的,如果不使用ViewStub,只是把不可用檢視隱藏,那麼它還是會被inflate並佔用資源,如果使用ViewStub,則可以在需要顯示的時候才會進行檢視的填充,從而實現延遲載入的目的。假設頁面載入資料失敗時會展示一張網路出錯的圖片,這個圖片的佈局如下所示,它就是我們的ViewStub 會顯示的檢視。
<?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:id="@+id/error_view"
>
<!--此處只是為了演示,實際專案程式碼要增加其他提示資訊-->
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:src="@drawable/network_error"
/>
</RelativeLayout>
頁面使用ViewStub載入這個佈局的程式碼如下。
<?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.okii.layoutoptimize.MainActivity">
<ViewStub
android:id="@+id/error_stub"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout="@layout/layout_network_error"
android:layout_centerInParent="true"
android:inflatedId="@+id/error_view"
/>
</RelativeLayout>
其中的android:inflatedId
的值是在Java
程式碼中呼叫 ViewStub
的 inflate()
或者 setVisibility()
方法時返回的ID
,這個ID
就是被填充的View
的ID
。
merge 標籤減少佈局層次
merge
標籤在某些場景下可以用來減少佈局的層次,由於 Activity
的ContentView
的最外層容器是一個 FrameLayout
,因此, 當一個獨立的佈局檔案最外層是FrameLayout
, 且這個佈局不需要設定背景 android:background
或者 android:padding
等屬性時,可以使用<merge>
標籤來代替<FrameLayout>
標籤,從而減少一層多餘的 FrameLayout
佈局。另外一種可以使用<merge>
標籤的情況是當前佈局作為另外一個佈局的子佈局,使用<include>
標籤引入時,我們使用前面include一節的例子,可以考慮把layout_common_title.xml 檔案中的RelativeLayout 替換成merge,程式碼如下。
<?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"
>
<!--此處只是演示,實際專案程式碼要比這個複雜一些-->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="標題欄"
android:layout_centerInParent="true"
/>
</RelativeLayout>
儘量使用CompoundDrawable
在LinearLayout佈局中,如果存在相鄰的ImageView和TextView,語句如下。
<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.okii.layoutoptimize.MainActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"/>
<ImageView
android:id="@+id/image_view"
android:layout_marginTop="10dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@mipmap/ic_launcher"
/>
</RelativeLayout>
那麼一般來說可以使用compound drawable 合二為一成為一個TextView, ImageView中的圖片變成TextView的如下屬性之一:drawableTop,drawableLeft,drawableRight 或者drawableBottom。原來兩個View之間的間隔使用TextView的屬性drawablePadding來代替,語句如下。
<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.okii.layoutoptimize.MainActivity">
<TextView
android:id="@+id/text_view"
android:layout_width="wrap_content"
android:drawablePadding="10dp"
android:drawableBottom="@mipmap/ic_launcher"
android:layout_height="wrap_content"
android:text="Hello World!"/>
</RelativeLayout>
使用Lint
Android Lint 除了可以對Java程式碼進行靜態檢查之外,也可以用來為檢查應用的佈局是否存在可優化的地方。Lint的如下規則是專為為優化佈局設定的:
AndroidLintUseCompoundDrawables: 就是前面介紹的儘量使用CompoundDrawable。
MergeRootFrame: 就是前面介紹的merge標籤減少佈局層次。
TooManyViews: 單個佈局中存在太多的View,預設情況下,單個佈局中View的個數最多隻能是80個,可以考慮使用CompoundDrawables 等來減少View的個數。
TooDeepLayout: 避免過深的佈局巢狀,預設情況下,單個佈局中最多層級是10,可以考慮使用RelativeLayout來減少佈局的層次。
UselessParent: 當一個佈局滿足以下條件時,可以將它移除,並將它的子View移動到它的父容器中,以得到更扁平的佈局層次:(1)這個佈局中只有一個子View,也就是子View不存在兄弟View; (2) 這個佈局中不是一個ScrollView或者根佈局;(3) 這個佈局沒有設定背景android:background。
NestedWeights: android:layout_weight 屬性會使得View控制元件被測量兩次,當一個LinearLayout擁有非0dp值的android:layout_weight屬性,這時如果將它巢狀在另一個擁有非0dp的android:layout_weight的LinearLayout,那麼這時測量的次數將呈指數級別增加。
UselessLeaf: 一個佈局如果既沒有子View也沒有設定背景,那麼它將是不可見的,通常可以移除它,這樣可以得到更扁平和高效的佈局層級。
InefficientWeight: 當LinearLayout中只有一個子View定義了android:layout_weight屬性,更高效能的做法是使用0dp的android:layout_height或者android:layout_width來替換它,這樣這個子View就不需要測量它自身對應的大小。