Android中,如何提升Layout的性能?


Layout 是 Android 應用中直接影響用戶體驗的關鍵部分。如果實現的不好,你的 Layout 會導致程序非常占用內存並且 UI 運行緩慢。Android SDK 帶有幫助你找到 Layout 性能問題的工具。

主題一:優化Layout層級

一個常見的誤區是,用最基礎的Layout結構可以提高Layout的性能。然而,因為程序的每個組件和Layout都需要經過初始化、布局和繪制的過程,如果布局嵌套導致層級過深,上面的初始化、布局和繪制操作就更加耗時。例如,使用嵌套的LinearLayout可能會使得View的層級結構過深,此外嵌套使用了 layout_weight參數的LinearLayout的計算量會尤其大,因為每個子元素都需要被測量兩次。這對需要多次重復inflate的Layout尤其需要注意,比如嵌套在 ListView或GridView時。

In this lesson you'll learn to use Hierarchy Viewer and Layoutopt to examine and optimize your layout. 使用兩個工具:Hierarchy Viewer和Layoutopt。

如何審視自己設計的Layout?

Android SDK 工具箱中有一個叫做 Hierarchy Viewer 的工具,能夠在程序運行時分析 Layout。你可以用這個工具找到 Layout 的性能瓶頸。

Hierarchy Viewer 會讓你選擇設備或者模擬器上正在運行的進程,然后顯示其 Layout 的樹型結構。每個塊上的交通燈分別代表了它在測量、布局和繪畫時的性能,幫你找出瓶頸部分。比如,下圖是 ListView 中一個列表項的Layout。列表項里,左邊放一個小位圖,右邊是兩個層疊的文字。像這種需要被多次 inflate 的 Layout ,優化它們會有事半功倍的效果。

The hierarchyviewer tool is available in <sdk>/tools/. Click Load View Hierarchy to view the layout hierarchy of the selected component.

找到UI性能瓶頸了,如何修正Layout?

上面的 Layout 由於有這個嵌套的 LinearLayout 導致性能太慢,可能的解決辦法是將 Layout 層級扁平化 - 變淺變寬,而不是又窄又深。RelativeaLayout 作為根節點時就可以達到目的。所以,當換成基於 RelativeLayout 的設計時,你的 Layout 變成了兩層。新的Layout變成如下形式:

可能看起來是很小的進步,但是由於它對列表中每個項都有效,這個時間要翻倍。這個時間的主要差異是由於在 LinearLayout 中使用 layout_weight 所致,因為會減慢“測量”的速度。這只是一個正確使用各種 Layout 的例子,當你使用 layout_weight 時有必要慎重。

如何使用Lint工具輔助檢測?

運行 Lint 工具來檢查 Layout 可能的優化方法,是個很好的實踐。Lint 已經取代了Layoutopt工具,它擁有更強大的功能。

從Eclipse中如何啟動Lint?如下方式均可以:

Lint中包含如下檢測規則:

使用compound drawable — 用一個compound drawable 替代一個包含 ImageView 和 TextView 的 LinearLayout 會更有效率。

合並根 frame — 如果 FrameLayout 是 Layout 的根節點,並且沒有使用 padding 或者背景等,那么用 merge 標簽替代他們會稍微高效些。

沒用的子節點 — 一個沒有子節點或者背景的 Layout 應該被去掉,來獲得更扁平的層級。

沒用的父節點 — 一個節點如果沒有兄弟節點,並且它不是 ScrollView 或根節點,沒有背景,這樣的節點應該直接被子節點取代,來獲得更扁平的層級。

太深的 Layout — Layout 的嵌套層數太深對性能有很大影響。嘗試使用更扁平的 Layout ,比如 RelativeLayout 或 GridLayout 來提高性能。一般最多不超過10層。

主題二:使用<include>標簽重復利用布局

雖然 Android 提供很多小的可重用的交互組件,你仍然可能需要重用復雜一點的組件,這也許會用到 Layout。為了高效重用整個的 Layout,你可以使用 <include/> 和 <merge/> 標簽把其他 Layout 嵌入當前 Layout。

重用 Layout 非常強大,它讓你可以創建復雜的可重用 Layout。比如,一個 yes/no 按鈕面板,或者帶有文字的自定義進度條。這也意味着,任何在多個 Layout 中重復出現的元素可以被提取出來,被單獨管理,再添加到 Layout 中。所以,雖然可以添加一個自定義 View 來實現單獨的 UI 組件,你可以更簡單的直接重用某個 Layout 文件。

如何創建可重用的Layout?

如果你已經知道你需要重用的 Layout,就先創建一個新的 XML 文件並定義 Layout 。比如,以下是一個來自 G-Kenya codelab 的 Layout,定義了一個需要添加到每個 Activity 中的標題欄(titlebar.xml):

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width=”match_parent”
    android:layout_height="wrap_content"
    android:background="@color/titlebar_bg">
    <ImageView android:layout_width="wrap_content"
               android:layout_height="wrap_content" 
               android:src="@drawable/gafricalogo" />
</FrameLayout>

其中:根節點的View類型就是你想要的添加進入的Layout。

如何添加到指定Layout中,使用<include>標簽

使用 <include> 標簽,可以在 Layout 中添加可重用的組件。比如,這里有一個來自 G-Kenya codelab 的 Layout 需要包含上面的那個標題欄:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width=”match_parent”
    android:layout_height=”match_parent”
    android:background="@color/app_bg"
    android:gravity="center_horizontal">
    <include layout="@layout/titlebar"/>
    <TextView android:layout_width=”match_parent”
              android:layout_height="wrap_content"
              android:text="@string/hello"
              android:padding="10dp" />
    ...
</LinearLayout>

你也可以覆寫被添加的Layout的所有Layout參數(任何android:layout_* 屬性),通過在<include/>中聲明他們來完成。比如:

<include android:id=”@+id/news_title”
         android:layout_width=”match_parent”
         android:layout_height=”match_parent”
         layout=”@layout/title”/>

但必須指出的是:如果想要覆寫被加入Layout的屬性,必須先覆寫其layout_width和layout_height屬性。

另一個方式:使用<merge>標簽

<merge /> 標簽在你嵌套 Layout 時取消了 UI 層級中冗余的 ViewGroup 。比如,如果你有一個 Layout 是一個豎直方向的 LinearLayout,其中包含兩個連續的 View 可以在別的 Layout 中重用,那么你會做一個 LinearLayout 來包含這兩個 View ,以便重用。不過,當使用一個 LinearLayout 作為另一個 LinearLayout 的根節點時,這種嵌套 LinearLayout 的方式除了減慢你的 UI 性能外沒有任何意義。

為了避免這種情況,可以使用<merge>元素來替代可重用Layout的根節點。

<merge xmlns:android="http://schemas.android.com/apk/res/android">
    <Button
        android:layout_width="fill_parent" 
        android:layout_height="wrap_content"
        android:text="@string/add"/>
    <Button
        android:layout_width="fill_parent" 
        android:layout_height="wrap_content"
        android:text="@string/delete"/>
</merge>

現在,當你要將這個 Layout 包含到另一個 Layout 中時(並且使用了 <include/> 標簽),系統會忽略 <merge> 標簽,直接把兩個 Button 放到 Layout 中 <include> 的所在位置。

主題三:按需加載布局

主題四:優化ListView滑動性能

保持程序流暢的關鍵,是讓主線程(UI 線程)不要進行大量運算。你要確保在其他線程執行磁盤讀寫、網絡讀寫或是 SQL 操作等。為了測試你的應用的狀態,你可以啟用 StrictMode。

使用后台線程加載數據

Using a background thread ("worker thread") removes strain from the main thread so it can focus on drawing the UI.In many cases, using AsyncTask provides a simple way to perform your work outside the main thread. UI線程僅僅做Layout的繪制,“worker thread”運行后台任務。

// Using an AsyncTask to load the slow images in a background thread
new AsyncTask<ViewHolder, Void, Bitmap>() {
    private ViewHolder v;
    @Override
    protected Bitmap doInBackground(ViewHolder... params) {
        v = params[0];
        return mFakeImageLoader.getImage();
    }
    @Override
    protected void onPostExecute(Bitmap result) {
        super.onPostExecute(result);
        if (v.position == position) {
            // If this item hasn't been recycled already, hide the
            // progress and set and show the image
            v.progress.setVisibility(View.GONE);
            v.icon.setVisibility(View.VISIBLE);
            v.icon.setImageBitmap(result);
        }
    }
}.execute(holder);

這個行為是全局的,這意味着你不需要考慮自己定義線程池的事情。從 Android 3.0 (API level 11) 開始, AsyncTask 有個新特性,那就是它可以在多個 CPU 核上運行。你可以調用 executeOnExecutor()而不是execute(),前者可以根據CPU的核心數來觸發多個任務同時進行。

使用ViewHolder填入視圖對象

你的代碼可能在 ListView 滑動時經常使用 findViewById(),這樣會降低性能。即使是 Adapter 返回一個用於回收的 inflate 后的視圖,你仍然需要查看這個元素並更新它。避免頻繁調用 findViewById() 的方法之一,就是使用 ViewHolder(視圖占位符)的設計模式。

一個 ViewHolder 對象存儲了他的標簽下的每個視圖。這樣你不用頻繁查找這個元素。第一,你需要創建一個類來存儲你會用到的視圖。比如:

static class ViewHolder {
  TextView text;
  TextView timestamp;
  ImageView icon;
  ProgressBar progress;
  int position;
}

在Layout類中生成一個ViewHolder實例:

ViewHolder holder = new ViewHolder();
holder.icon = (ImageView) convertView.findViewById(R.id.listitem_image);
holder.text = (TextView) convertView.findViewById(R.id.listitem_text);
holder.timestamp = (TextView) convertView.findViewById(R.id.listitem_timestamp);
holder.progress = (ProgressBar) convertView.findViewById(R.id.progress_spinner);
convertView.setTag(holder);

  

這樣你就可以輕松獲取每個視圖,而不是使用 findViewById() 來不斷查找子視圖,節省了寶貴的運算時間。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM