Android開發 佈局優化

在開發過程中我們經常說性能優化,但性能優化是一個比較寬泛的概念。在Android開發中性能優化可能包括:Java代碼優化, 算法優化, SQLite優化, 佈局優化等。那麼這篇博客就來總結並分享下Android開發中的佈局優化。

佈局原則

在Android UI佈局過程中,通過遵守一些慣用、有效的佈局原則,我們可以製作出高效且複用性高的UI,概括來說包括如下幾點:

  • 儘量多使用RelativeLayout和LinearLayout, 不要使用絕對佈局AbsoluteLayout,在佈局層次一樣的情況下, 建議使用LinearLayout代替RelativeLayout, 因爲LinearLayout性能要稍高一點,但往往RelativeLayout可以簡單實現LinearLayout嵌套才能實現的佈局。

  • 將可複用的組件抽取出來並通過include標籤使用;

  • 使用ViewStub標籤來加載一些不常用的佈局;

  • 使用merge標籤減少佈局的嵌套層次;

RelativeLayout VS LinearLayout

第一條原則說了佈局層次一樣的情況下LinearLayout比RelativeLayout要好, 但往往RelativeLayout可以簡單實現LinearLayout嵌套才能實現的佈局。假如需要實現如下佈局:

用LinearLayout來實現xml代碼如下:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="?android:attr/listPreferredItemHeight"
    android:padding="6dip">
    
    <ImageView
        android:id="@+id/icon"
        android:layout_width="wrap_content"
        android:layout_height="fill_parent"
        android:layout_marginRight="6dip"
        android:src="@drawable/icon" />

    <LinearLayout
        android:orientation="vertical"
        android:layout_width="0dip"
        android:layout_weight="1"
        android:layout_height="fill_parent">

        <TextView
            android:layout_width="fill_parent"
            android:layout_height="0dip"
            android:layout_weight="1"
            android:gravity="center_vertical"
            android:text="My Application" />
            
        <TextView  
            android:layout_width="fill_parent"
            android:layout_height="0dip"
            android:layout_weight="1" 
            android:singleLine="true"
            android:ellipsize="marquee"
            android:text="Simple application that shows how to use RelativeLayout" />
            
    </LinearLayout>

</LinearLayout>

而用RelativeLayout實現代碼如下:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="?android:attr/listPreferredItemHeight"
    android:padding="6dip">
    
    <ImageView
        android:id="@+id/icon"
        android:layout_width="wrap_content"
        android:layout_height="fill_parent"
        android:layout_alignParentTop="true"
        android:layout_alignParentBottom="true"
        android:layout_marginRight="6dip"
        android:src="@drawable/icon" />

    <TextView  
        android:id="@+id/secondLine"
        android:layout_width="fill_parent"
        android:layout_height="26dip" 
        android:layout_toRightOf="@id/icon"
        android:layout_alignParentBottom="true"
        android:layout_alignParentRight="true"
        android:singleLine="true"
        android:ellipsize="marquee"
        android:text="Simple application that shows how to use RelativeLayout" />

    <TextView
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_toRightOf="@id/icon"
        android:layout_alignParentRight="true"
        android:layout_alignParentTop="true"
        android:layout_above="@id/secondLine"
        android:layout_alignWithParentIfMissing="true"
        android:gravity="center_vertical"
        android:text="My Application" />

</RelativeLayout>

可以看到用RelativeLayout實現,佈局層次明顯少了,所以大多數時候優先推薦使用RelativeLayout。

查看佈局層次

如何查看佈局層次呢?有兩種辦法:一是通過手機的開發者選項,4.0及以上Android版本可通過設置->開發者選項->顯示佈局邊界打開頁面佈局顯示,看看是否有不必要的節點和嵌套。第二種就是利用SDK自帶的UI性能檢測工具HierarchyViewer。 進入sdk目錄下的tools文件夾下,找到HierarchyViewer並運行(此時保持你的模擬器或真機正在運行需要進行分析的App),雙擊我們正在顯示的這個App所代表的進程。接下來便會進入hierarchyviewer的界面,我們可以在這裏很清晰看到正在運行的UI的佈局層次結構以及它們之間的關係。大概的顯示如下圖:

通過佈局圖我們可以看到根節點DecorView下包含一個LinearLayout, 這個LinearLayout就是包含Activity佈局和狀態欄的整個屏幕顯示的佈局父節點,這個LinearLayout有兩個子節點, 一個是FrameLayout, FrameLayout就是Activity佈局中默認的父佈局節點, 這個節點下面就包含了我們自己寫的xml佈局, 還有一個子節點就是ViewStub,關於這個節點我們在後面會詳細介紹。

< include />的使用

在實際開發中,我們經常會遇到一些共用的UI組件,比如帶返回按鈕的導航欄,如果爲每一個xml文件都設置這部分佈局,一是重複的工作量大,二是如果有變更,那麼每一個xml文件都得修改。還好,Android爲我們提供了include標籤,顧名思義,通過它,我們可以將這些共用的組件抽取出來單獨放到一個xml文件中,然後使用include標籤導入共用佈局,這樣,前面提到的兩個問題都解決了。下面以在一個佈局main.xml中用include引入另一個佈局header.xml爲例。

header.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" >

    <Button
        android:id="@+id/button"
        android:layout_width="match_parent"
        android:layout_height="@dimen/dp_40"
        android:layout_above="@id/text"/>

    <TextView
        android:id="@+id/text"
        android:layout_width="match_parent"
        android:layout_height="@dimen/dp_40"
        android:layout_alignParentBottom="true"
        android:text="@string/app_name" />

</RelativeLayout>

然後我們在需要引入footer的佈局xml中通過include導入這個共用佈局。

main.xml文件

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="hello world" />

    <RelativeLayout 
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_gravity="center" >

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

    </RelativeLayout>
</FrameLayout>

通過這種方式,我們既能提高UI的製作和複用效率,也能保證製作的UI佈局更加規整和易維護。

< merge />的使用

merge標籤的作用是合併UI佈局,使用該標籤能降低UI佈局的嵌套層次。merge標籤可用於兩種典型情況:

  • 佈局根結點是FrameLayout且不需要設置background或padding等屬性,可以用merge代替,因爲Activity內容佈局的parent view就是個FrameLayout,所以可以用merge消除只剩一個,這一點可以從上圖中看到。

  • 某佈局作爲子佈局被其他佈局include時,使用merge當作該佈局的頂節點,這樣在被引入時頂結點會自動被忽略,而將其子節點全部合併到主佈局中。

以第一種情況爲例,main.xml佈局就可以優化如下:

<merge xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <FrameLayout 
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="hello world" />

        <RelativeLayout 
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_gravity="center" >

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

        </RelativeLayout>
    </FrameLayout>
</merge>

以第二種情況爲例,header.xml佈局可以優化如下:

<?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:id="@+id/button"
        android:layout_width="match_parent"
        android:layout_height="@dimen/dp_40"
        android:layout_above="@id/text"/>
 
    <TextView
        android:id="@+id/text"
        android:layout_width="match_parent"
        android:layout_height="@dimen/dp_40"
        android:layout_alignParentBottom="true"
        android:text="@string/app_name" />
 
</merge>

這樣就不會有多餘的FrameLayout和RelativeLayout節點了。

ViewStub標籤

viewstub標籤同include標籤一樣可以用來引入一個外部佈局,不同的是,viewstub引入的佈局默認不會擴張,即既不會佔用顯示也不會佔用位置,從而在解析layout時節省cpu和內存。 viewstub常用來引入那些默認不會顯示,只在特殊情況下顯示的佈局,如進度佈局、網絡失敗顯示的刷新佈局、信息出錯出現的提示佈局等。

我們新建一個xml文件用來顯示一個網絡錯誤時提示信息error.xml:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" >

   <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:background="@android:color/white"
        android:padding="10dip"
        android:text="Message"
        android:textColor="@android:color/black" />

</RelativeLayout>

然後在main.xml裏面加入ViewStub的標籤引入上面的佈局:

<merge xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:background="@android:color/darker_gray"
    android:layout_height="match_parent" >

    ...

    <ViewStub
        android:id="@+id/error_layout"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:layout="@layout/error" />

</merge>

在java中通過(ViewStub)findViewById(id)找到ViewStub,通過stub.inflate()展開ViewStub,然後得到子View,如下:

private View errorView;
 
private void showError() {
    // not repeated infalte
    if (errorView != null) {
        errorView.setVisibility(View.VISIBLE);
        return;
    }
 
    ViewStub stub = (ViewStub)findViewById(R.id.error_layout);
    errorView = stub.inflate();
}
 
private void showContent() {
    if (errorView != null) {
        errorView.setVisibility(View.GONE);
    }
}

在上面showError()中展開了ViewStub,同時我們對errorView進行了保存,這樣下次不用繼續inflate。

總結

這篇Blog沒有詳細介紹HierarchyViewer工具的使用,相信如果對佈局原則比較熟練之後,對工具的依賴大大減少,開發效率也會大大的提升。除這些佈局原則之外,還需要大家對Android各個組件的屬性很熟悉,比如如果要做這麼一個佈局, 一個圖片和一個文本的佈局,新手們往往會用一個Layout嵌套ImageView和TextView來做, 但是當我們知道TextView有drawableLeft, drawableRight等屬性時,那麼實現這樣的一個佈局是非常快速高效的。總之,且學且實踐!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章