相關文章
Android繪製優化(一)繪製效能分析
前言
我們知道一個介面的測量和繪製是通過遞迴來完成的,減少佈局的層數就會減少測量和繪製的時間,從而效能就會得到提升。當然這只是佈局優化的一方面,那麼如何來進行佈局的分析和優化呢?本篇文章會給你一個滿意的答案。
1.佈局優化工具
在講到如何去佈局優化前,我們先來學習兩種佈局優化的工具。
Hierarchy Viewer
Hierarchy Viewer是Android SDK自帶的視覺化的除錯工具,用來檢查佈局巢狀和繪製的時間。需要注意的是在在Android的官方文件中提到:出於安全考慮,Hierarchy Viewer只能連線Android開發版手機或是模擬器。
首先我們在Android Studio中選擇Tools->Android->Android Device Monitor,在Android Device Monitor中選擇Hierarchy Viewer ,如下圖所示:
選擇Hierarchy Viewer後會進出Hierarchy Viewer視窗,如下圖所示。
Hierarchy Viewer中有4個四個子視窗,它們的的作用為:
- Windows:當前裝置所有介面列表。
- Tree View:將當前Activity的所有View的層次按照高層到低層從左到右顯示出來。
- Tree Overview:全域性概覽,以縮略的形式顯示。
- Layout View:整體佈局圖,以手機螢幕上真實的位置呈現出來。單擊某一個控制元件,會在Tree Overview視窗中顯示出對應的控制元件。
根據上面講到的Hierarchy Viewer的4個四個子視窗,我們可以很容易的檢視我們佈局控制元件的層級關係。當然Hierarchy Viewer還可以檢視某一個View的耗時,我們可以選擇某一個View,然後單擊下圖紅色箭頭標識的按鈕,這裡我們把他簡稱為Layout Time按鈕。
從圖中可以看出被選中的RelativeLayout自身的Measure、Layout和Draw的耗時資料都為n/a。單擊Layout Time按鈕後,就可以檢視View的耗時情況了,如下圖所示。
從圖中可以看出,被選中的LinearLayout給出了自身Measure、Layout和Draw的耗時,並且它所包含的View中都有了三個指示燈,分別代表當前View在Measure、Layout和Draw的耗時,綠色代表比其他50%View的同階段(比如Measure階段)速度要快,黃色則代表比其他50%View同階段速度要慢,紅色則代表比其他View同階段都要慢,則需要注意了。如果想要看View的具體耗時,則點選該View就可以了。
Android Lint
Android lint是在ADT 16提供的新工具,它是一個程式碼掃描工具,通過程式碼靜態檢查來發現程式碼出現的潛在問題,並給出優化建議。檢查的範圍主要有以下幾點:
- Correctness 正確性
- Security 安全性
- Performance 效能
- Usability 可用性
- Accessibility 可達性
- Internationalization 國際化
Android Lint功能十分強大,這裡我們只關注XML佈局檢查,我們可以通過Android Studio的Analyze->Inspect Code來配置檢查的範圍,如下圖所示。
點選上圖的OK按鈕後,就會進行程式碼檢查,檢查的結果如下圖所示。
圖中列出了專案中出現的問題種類,以及每個問題種類的個數,問題種類包括我們前面提到的Correctness 、Internationalization 、Performance等。我們點選展開最後的XML一項,點選一個問題,就會出現如下圖的提示。
可以看出給出了Namespace declaration is never used的提示,並指出了問題所在的檔案和行數,我們點選數字3,直接跳入到問題的程式碼,發現如下程式碼:
<?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">
...複製程式碼
第三行的Namespace 確實沒有被用到。如果想要自定義Android Lint的檢查提示,可以通過File->Settings->Editor->Inspections中來配置Android Lint,如下圖所示。
從圖中可以發現,我們可以配置Android Lint檢查的範圍以及問題的嚴重等級。
2.佈局優化方法
佈局的優化方法很多,主要包括合理運用佈局、Include、Merge、ViewStub,下面我們來一一對這些內容進行講解。
合理運用佈局
我們常用的佈局主要有LinearLayout、RelativeLayout和FrameLayout等,合理的使用它們可以使得Android繪製工作量變少,效能得到提高。我們來舉個簡單的例子。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="佈局優化" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Merge" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="ViewStub" />
</LinearLayout>
</LinearLayout>複製程式碼
上面的程式碼用了兩個LinearLayout來進行佈局,執行效果如下圖所示。
我們用Hierarchy Viewer來檢視層級情況,如下圖所示。
可以看到我們的佈局共有3層,一共含有5個View。如果我們用RelativeLayout來進行改寫呢?程式碼如下所示。
<?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:id="@+id/tv_text1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="佈局優化" />
<TextView
android:id="@+id/tv_text2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:layout_toRightOf="@id/tv_text1"
android:text="Merge" />
<TextView
android:id="@+id/tv_text3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/tv_text2"
android:layout_marginLeft="10dp"
android:layout_toRightOf="@+id/tv_text1"
android:text="ViewStub" />
</RelativeLayout>複製程式碼
我們只用了一個RelativeLayout來進行佈局。用Hierarchy Viewer來檢視層級情況,如下圖所示。
佈局共有兩層,一共含有4個View。從這裡我們就可以看出我們用RelativeLayout減少了一層的佈局,當然這只是一個簡單例子,如果佈局複雜,那麼合理的用RelativeLayout來替代LinearLayout會減少很多層佈局。
一般情況下,RelativeLayout的效能是比LinearLayout低,因為RelativeLayout中的View的排列方式是基於彼此依賴的。但是如果佈局層數較多時,如果能用RelativeLayout來實現,還是推薦用RelativeLayout。
使用Include標籤來進行佈局複用
一個很常見的場景就是,多個佈局需要複用一個相同的佈局,比如一個TitleBar。如果這些介面都要加上這個相同佈局TitleBar,維護起來就就很麻煩,我們需要複製TitleBar的佈局到每個需要新增的介面,這樣容易發生遺漏。如果需要修改TitleBar則需要去每個引用TitleBar的佈局進行修改。為了解決這些問題,我們可以用Include標籤來解決。
首先我們先來寫一個簡單的TitleBar佈局:titlebar.xml 如下所示。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="40dp"
android:background="@android:color/darker_gray">
<ImageView
android:layout_width="30dp"
android:layout_height="30dp"
android:src="@drawable/ico_left"
android:padding="3dp"
android:layout_gravity="center"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center"
android:text="繪製優化" />
</LinearLayout>複製程式碼
這個TitleBar由ImageView和TextView組成,下面我們將TitleBar引入到我們此前用過的佈局中,如下所示。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<include layout="@layout/titlebar" />
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<TextView
android:id="@+id/tv_text1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="佈局優化" />
...
</RelativeLayout>
</LinearLayout>複製程式碼
可以看到我們用include標籤引入了titlebar佈局,執行效果如下圖所示。
用Merge標籤去除多餘層級
Merge意味著合併,在合適的場景使用Merge標籤可以減少多餘的層級。Merge標籤一般和Include標籤搭配使用,上面的例子,我們用Hierarchy Viewer來檢視佈局層級,如下圖所示。
可以看到我們用Include標籤引用的佈局的根佈局是一個LinearLayout。如果我們使用Merge標籤來替換LinearLayout呢?titlebar.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="40dp"
android:background="@android:color/darker_gray
>
<ImageView
android:layout_width="30dp"
android:layout_height="30dp"
android:src="@drawable/ico_left"
android:padding="3dp"
android:layout_gravity="center"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center"
android:text="繪製優化" />
</merge>複製程式碼
這時我們再用Hierarchy Viewer來檢視佈局層級,如下圖所示。
可以看到此前的根佈局LinearLayout沒有了,但是我們用merge標籤來替代LinearLayout導致LinearLayout失效,因此佈局就錯亂了,因此可以得知merge標籤最好是來替代FrameLayout,或者是佈局一致的LinearLayout,比如當前佈局的LinearLayout是垂直方向的,被包含的佈局的LinearLayout也是垂直方向的則可以用merge標籤,本場景包含的LinearLayout是水平的,顯然並不符合這一要求。但是如果執意想要在本場景使用merge標籤也是可以的,就是用繼承自LinearLayout的自定義View,程式碼如下所示。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.example.liuwangshu.moonlayout.TitleBar
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:background="@android:color/darker_gray">
</com.example.liuwangshu.moonlayout.TitleBar>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<TextView
android:id="@+id/tv_text1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="佈局優化" />
...
</RelativeLayout>
</LinearLayout>複製程式碼
上面佈局中TitleBar就是一個自定義View,它繼承LinearLayout。我們在TitleBar標籤中新增此前的LinearLayout的屬性:android:orientation和android:background。
使用ViewStub來提高載入速度
一個很常見的開發場景就是我們想要一個佈局時,並不是所有的控制元件都需要顯示出來,而是顯示出一部分,對於這種情況,我們一般採用的方法就是使用View的GONE和INVISIBLE,但是這種方法效率不高,雖然是達到了隱藏的目的,但是仍在佈局當中,系統仍然會解析它們,我們可以用ViewStub來解決這一問題。
ViewStub是輕量級的View,不可見並且不佔佈局位置。當ViewStub呼叫inflate方法或者設定可見時,系統會載入ViewStub指定的佈局,然後將這個佈局新增到ViewStub中,因此,在對ViewStub呼叫inflate方法或者設定可見時,它是不佔佈局空間和系統資源的,它主要的目的就是為目標檢視佔用一個位置。因此,使用ViewStub可以提高介面初始化的效能,從而提高介面的載入速度。
我們首先在佈局中加入ViewStub標籤,佈局程式碼如下所示。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<ViewStub
android:id="@+id/viewsub"
android:layout_width="match_parent"
android:layout_height="40dp"
android:layout="@layout/titlebar"/>
...
</LinearLayout>複製程式碼
ViewStub標籤中用android:layout引用了此前寫好的佈局titlebar.xml。這時我們執行程式,ViewStub標籤所引用的佈局是顯示不出來的,因為引該局還沒有載入到ViewStub中,接下來在程式碼中使用ViewStub:
public class MainActivity extends AppCompatActivity {
private ViewStub viewsub;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
viewsub= (ViewStub) findViewById(R.id.viewsub);
// viewsub.inflate();//1
viewsub.setVisibility(View.VISIBLE);//2
}
}複製程式碼
可以使用註釋1和註釋2處的程式碼來將ViewStub引用的佈局載入到ViewStub中,這樣引用的佈局就顯示了出來。
在使用ViewStub時需要主要以下問題:
- ViewStub只能載入一次,載入後ViewStub物件會被置為空,這樣當ViewStub引用的佈局被載入後,就不能用ViewStub來控制引用的佈局了。因此,如果一個控制元件需要不斷的顯示和隱藏,還是要使用View的Visibility屬性。
- ViewStub不能巢狀Merge標籤。
- ViewStub操作的是佈局檔案,如果只是想操作具體的View,還是要使用View的Visibility屬性。
3.避免GPU過度繪製
什麼是過度繪製呢?我們來打個比方,假設你要粉刷房子的牆壁,一開始刷了綠色,接著又刷了黃色,這樣黃色就將綠色蓋住,也就說明第一次的大量粉刷工作白做了。同樣手機螢幕繪製也是如此,過度繪製是指在螢幕上某個畫素在同一幀的時間內被繪製多次,從而浪費了GPU和CPU的資源。產生這一原因主要有兩個原因:
- 在XML佈局中,控制元件有重疊且都有設定背景。
- View的OnDraw中同一區域繪製多次。
過度繪製是不可避免的,但是過多的過度繪製會浪費很多資源,並且導致效能問題,因此,避免過度繪製是十分必要的。我們可以用Android系統中自帶的工具來檢測過度繪製。首先要保證系統版本在Android 4.1以上,接著在開發者選項中開啟除錯GPU過度繪製選項就可以進入GPU過度繪製模式,如下圖所示。
這時螢幕會出現出各種顏色,主要有以下幾種,如下圖所示。
各個顏色的定義為:
- 原色: 沒有過度繪製 – 每個畫素在螢幕上繪製了一次。
- 藍色: 一次過度繪製 – 每個畫素點在螢幕上繪製了兩次。
- 綠色: 兩次過度繪製 – 每個畫素點在螢幕上繪製了三次。
- 粉色: 三次過度繪製 – 每個畫素點在螢幕上繪製了四次。
- 紅色: 四次或四次以上過度繪製 – 每個畫素點在螢幕上繪製了五次或者五次以上。
最理想的是藍色,一個畫素只繪製一次,合格的頁面繪製是白色、藍色為主,綠色以上區域不能超過整個的三分之一,顏色越淺越好。
避免過度繪製主要有以下幾個方案:
1.移除不需要的background。
2.在自定義View的OnDraw方法中,用canvas.clipRect來指定繪製的區域,防止重疊的元件發生過度繪製。
歡迎關注我的微信公眾號,第一時間獲得部落格更新提醒,以及更多成體系的Android相關原創技術乾貨。
掃一掃下方二維碼或者長按識別二維碼,即可關注。
![Android繪製優化(二)佈局優化](https://i.iter01.com/images/fcc5840e86e77c87f45600c3963beafd3085f6a7fd15e267d2843cb5fc421212.jpg)