天天看點

android性能優化--布局優化

1、抽象布局标簽

(1) 标簽

include标簽常用于将布局中的公共部分提取出來供其他layout共用,以實作布局子產品化,這在布局編寫友善提供了大大的便利。

下面以在一個布局main.xml中用include引入另一個布局foot.xml為例。main.mxl代碼如下:

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

    <ListView
        android:id="@+id/simple_list_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_marginBottom="@dimen/dp_80" />

    <include layout="@layout/foot.xml" />

</RelativeLayout>
           

其中include引入的foot.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>
           

标簽唯一需要的屬性是layout屬性,指定需要包含的布局檔案。可以定義android:id和android:layout_*屬性來覆寫被引入布局根節點的對應屬性值。注意重新定義android:id後,子布局的頂結點i就變化了。

(2) 标簽

viewstub标簽同include标簽一樣可以用來引入一個外部布局,不同的是,viewstub引入的布局預設不會擴張,即既不會占用顯示也不會占用位置,進而在解析layout時節省cpu和記憶體。

viewstub常用來引入那些預設不會顯示,隻在特殊情況下顯示的布局,如進度布局、網絡失敗顯示的重新整理布局、資訊出錯出現的提示布局等。

下面以在一個布局main.xml中加入網絡錯誤時的提示頁面network_error.xml為例。main.mxl代碼如下:

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

    ……
    <ViewStub
        android:id="@+id/network_error_layout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout="@layout/network_error" />

</RelativeLayout>
           

其中network_error.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/network_setting"
        android:layout_width="@dimen/dp_160"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:text="@string/network_setting" />

    <Button
        android:id="@+id/network_refresh"
        android:layout_width="@dimen/dp_160"
        android:layout_height="wrap_content"
        android:layout_below="@+id/network_setting"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="@dimen/dp_10"
        android:text="@string/network_refresh" />

</RelativeLayout>
           

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

private View networkErrorView;

private void showNetError() {
    // not repeated infalte
    if (networkErrorView != null) {
        networkErrorView.setVisibility(View.VISIBLE);
        return;
    }

    ViewStub stub = (ViewStub)findViewById(R.id.network_error_layout);
    networkErrorView = stub.inflate();
    Button networkSetting = (Button)networkErrorView.findViewById(R.id.network_setting);
    Button refresh = (Button)findViewById(R.id.network_refresh);
}

private void showNormal() {
    if (networkErrorView != null) {
        networkErrorView.setVisibility(View.GONE);
    }
}
           

在上面showNetError()中展開了ViewStub,同時我們對networkErrorView進行了儲存,這樣下次不用繼續inflate。這就是後面第三部分提到的減少不必要的infalte。

viewstub标簽大部分屬性同include标簽類似。

上面展開ViewStub部分代碼

ViewStub stub = (ViewStub)findViewById(R.id.network_error_layout);
networkErrorView = stub.inflate();
           

也可以寫成下面的形式

View viewStub = findViewById(R.id.network_error_layout);
viewStub.setVisibility(View.VISIBLE);   // ViewStub被展開後的布局所替換
networkErrorView =  findViewById(R.id.network_error_layout); // 擷取展開後的布局
           

(3) 标簽

在使用了include後可能導緻布局嵌套過多,多餘不必要的layout節點,進而導緻解析變慢,不必要的節點和嵌套可通過hierarchy viewer(下面布局調優工具中有具體介紹)或設定->開發者選項->顯示布局邊界檢視。

merge标簽可用于兩種典型情況:

a. 布局頂結點是FrameLayout且不需要設定background或padding等屬性,可以用merge代替,因為Activity内容試圖的parent view就是個FrameLayout,是以可以用merge消除隻剩一個。

b. 某布局作為子布局被其他布局include時,使用merge當作該布局的頂節點,這樣在被引入時頂結點會自動被忽略,而将其子節點全部合并到主布局中。

以(1) 标簽的示例為例,用hierarchy viewer檢視main.xml布局如下圖:

android性能優化--布局優化

可以發現多了一層沒必要的RelativeLayout,将foot.xml中RelativeLayout改為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" >

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

運作後再次用hierarchy viewer檢視main.xml布局如下圖:

android性能優化--布局優化

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

2、去除不必要的嵌套和View節點

(1) 首次不需要使用的節點設定為GONE或使用viewstub

(2) 使用RelativeLayout代替LinearLayout 大約在Android4.0之前,建立工程的預設main.xml中頂節點是LinearLayout,而在之後已經改為RelativeLayout,因為RelativeLayout性能更優,且可以簡單實作LinearLayout嵌套才能實作的布局。

4.0及以上Android版本可通過設定->開發者選項->顯示布局邊界打開頁面布局顯示,看看是否有不必要的節點和嵌套。4.0以下版本可通過hierarchy viewer檢視。

3、減少不必要的infalte

(1) 對于inflate的布局可以直接緩存,用全局變量代替局部變量,避免下次需再次inflate 如上面ViewStub示例中的

if (networkErrorView != null) {
    networkErrorView.setVisibility(View.VISIBLE);
    return;
}
           

(2) ListView提供了item緩存,adapter getView的标準寫法,如下:

@Override
public View getView(int position, View convertView, ViewGroup parent) {
    ViewHolder holder;
    if (convertView == null) {
        convertView = inflater.inflate(R.layout.list_item, null);
        holder = new ViewHolder();
        ……
        convertView.setTag(holder);
    } else {
        holder = (ViewHolder)convertView.getTag();
    }
}

/**
 * ViewHolder
 * 
 * @author [email protected] 2013-08-01
 */
private static class ViewHolder {

    ImageView appIcon;
    TextView  appName;
    TextView  appInfo;
}
           

4、其他點

(1) 用SurfaceView或TextureView代替普通View

SurfaceView或TextureView可以通過将繪圖操作移動到另一個單獨線程上提高性能。

普通View的繪制過程都是在主線程(UI線程)中完成,如果某些繪圖操作影響性能就不好優化了,這時我們可以考慮使用SurfaceView和TextureView,他們的繪圖操作發生在UI線程之外的另一個線程上。

因為SurfaceView在正常視圖系統之外,是以無法像正常試圖一樣移動、縮放或旋轉一個SurfaceView。TextureView是Android4.0引入的,除了與SurfaceView一樣在單獨線程繪制外,還可以像正常視圖一樣被改變。

(2) 使用RenderJavascript

RenderScript是Adnroid3.0引進的用來在Android上寫高性能代碼的一種語言,文法給予C語言的C99标準,他的結構是獨立的,是以不需要為不同的CPU或者GPU定制代碼代碼。

(3) 使用OpenGL繪圖

Android支援使用OpenGL API的高性能繪圖,這是Android可用的最進階的繪圖機制,在遊戲類對性能要求較高的應用中得到廣泛使用。

Android 4.3最大的改變,就是支援OpenGL ES 3.0。相比2.0,3.0有更多的緩沖區對象、增加了新的着色語言、增加多紋理支援等等,将為Android遊戲帶來更出色的視覺體驗。

(4) 盡量為所有分辨率建立資源

減少不必要的硬體縮放,這會降低UI的繪制速度,可借助Android asset studio

5、布局調優工具

(1) hierarchy viewer

hierarchy viewer可以友善的檢視Activity的布局,各個View的屬性、measure、layout、draw的時間,如果耗時較多會用紅色标記,否則顯示綠色。

hierarchy viewer.bat位于/tools/目錄下。

(2) layoutopt

layoutopt是一個可以提供layout及其層級優化提示的指令行,在sdk16以後已經被lint取代,在Windows->Show View->Other->Android->Lint Warnings檢視lint優化提示