標籤:alt 標題列 already 複雜 設計模式 背景 executor set 啟動
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 threadnew 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 Object Storage Service了他的標籤下的每個視圖。這樣你不用頻繁尋找這個元素。第一,你需要建立一個類來儲存你會用到的視圖。比如:
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() 來不斷尋找子視圖,節省了寶貴的運算時間。
Android中,如何提升Layout的效能?