文章目錄
-
- 背景
- 過度繪制
-
- 補充
- 檢測布局中的背景重疊
- 檢測視圖層級
- Hierarchy Viewer工具檢測
- clipRect 和 quickReject 方法
背景
之前我們的項目開發周期,從兩周發一個版本,變成一周發一版本,這種快速疊代的節奏持續了将近一年半。平時開發,重心都放在了業務之上,很難有很多的時間去分析一些複雜業務多帶來的性能問題,導緻代碼越來越沉重(比如:一個Fragment頁面的代碼到了3千多行。),而且頁面渲染速度和幀率都大大下降,出現卡頓,嚴重影響使用者的體驗。當時這種問題已經發展到了很嚴重的地步,如果再一直這樣持續下去,将可能導緻使用者流失,是以性能問題是必須解決的。
大概半年之前,我們開始對業務和架構進行重新梳理,對項目做一些重構和優化。我參與到了這個優化項目中,主要負責對過度渲染,過度繪制,方法耗時,記憶體優化方面的優化,下面将對這幾點進行一些總結。
過度繪制
去除過度繪制主要從三方面入手:
- 移除布局中不必要的背景
- 使視圖層級扁平化
- 降低透明度
具體如何分析和去除過度繪制,可以檢視我之前的部落格關于過度繪制和渲染的介紹。
補充
檢測布局中的背景重疊
關于背景重疊引起的過度繪制,可以從統計View背景重疊的次數,來做具體的優化。View是一個樹形結構,可以對View樹進行周遊,得到過度繪制的View路徑:
/**
* 最小次數
*/
private static final int NUM = 3;
/**
* 測試過度繪制的View路徑
*
* @param view 根View節點
*/
public static void testBackgroundOverdraw(View view) {
Map<ArrayList<View>, Integer> result = new LinkedHashMap<>();
ArrayList<View> list = new ArrayList<>();
findBackgroundOverdrawPath(view, result, list, NUM);
Log.d(TAG, "背景過度繪制大于" + NUM + "的布局的個數:" + result.size());
Iterator<Map.Entry<ArrayList<View>, Integer>> iterator = result.entrySet().iterator();
int index = 0;
while (iterator.hasNext()) {
Map.Entry<ArrayList<View>, Integer> entry = iterator.next();
ArrayList path = entry.getKey();
Integer num = entry.getValue();
Log.d(TAG, "布局[" + index++ + "]深度:" + path.size() + ";次數:" + num + ";布局視圖:" + path.toString());
}
}
/**
* @param node 根View節點
* @param result 過度繪制路徑集合
* @param list 存儲臨時路徑
* @param target 過度繪制次數
*/
private static void findBackgroundOverdrawPath(View node, Map<ArrayList<View>, Integer> result, ArrayList<View> list, int target) {
if (node == null) return;
list.add(node);
if (!(node instanceof ViewGroup)) {
int count = 0;
for (View view : list) {
if (view.getBackground() != null) count++;
}
if (count >= target)
result.put(list, count);
} else {
ViewGroup viewGroup = (ViewGroup) node;
for (int i = 0, count = viewGroup.getChildCount(); i < count; i++) {
findBackgroundOverdrawPath(viewGroup.getChildAt(i), result, new ArrayList<View>(list), target);
}
}
}
例如,某個Activity的布局:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/holo_green_light"
tools:context=".MainActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@android:color/holo_blue_light"
android:text="Hello World!"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:background="@android:color/holo_red_light"
android:orientation="vertical"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:background="@android:color/holo_blue_light"
android:text="Hello World!" />
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:background="@android:color/holo_orange_light"
android:text="Hello World!" />
</FrameLayout>
</LinearLayout>
</android.support.constraint.ConstraintLayout>
統計結果:
11-04 21:12:40.371 21520-21520/com.example.wangjiang.after D/UITestUtil: 背景過度繪制大于3的布局的個數:3
11-04 21:12:40.371 21520-21520/com.example.wangjiang.after D/UITestUtil: 布局[0]深度:7;次數:3;布局視圖:[com.android.internal.policy.PhoneWindow$DecorView{dc28997 V.E...... R.....ID 0,0-720,1280}, android.widget.LinearLayout{38ac784 V.E...... ......ID 0,0-720,1280}, android.widget.FrameLayout{ef95b6d V.E...... ......ID 0,36-720,1280}, android.support.v7.widget.ActionBarOverlayLayout{d5553a2 V.E...... ......ID 0,0-720,1244 #7f070030 app:id/decor_content_parent}, android.support.v7.widget.ContentFrameLayout{e1c2833 V.E...... ......ID 0,112-720,1244 #1020002 android:id/content}, android.support.constraint.ConstraintLayout{aed7f0 V.E...... ......ID 0,0-720,1132}, android.support.v7.widget.AppCompatTextView{3dd4169 V.ED..... ......ID 286,0-435,38}]
11-04 21:12:40.371 21520-21520/com.example.wangjiang.after D/UITestUtil: 布局[1]深度:8;次數:4;布局視圖:[com.android.internal.policy.PhoneWindow$DecorView{dc28997 V.E...... R.....ID 0,0-720,1280}, android.widget.LinearLayout{38ac784 V.E...... ......ID 0,0-720,1280}, android.widget.FrameLayout{ef95b6d V.E...... ......ID 0,36-720,1280}, android.support.v7.widget.ActionBarOverlayLayout{d5553a2 V.E...... ......ID 0,0-720,1244 #7f070030 app:id/decor_content_parent}, android.support.v7.widget.ContentFrameLayout{e1c2833 V.E...... ......ID 0,112-720,1244 #1020002 android:id/content}, android.support.constraint.ConstraintLayout{aed7f0 V.E...... ......ID 0,0-720,1132}, android.widget.LinearLayout{f5907ee V.E...... ......ID 0,528-720,604}, android.support.v7.widget.AppCompatTextView{967148f V.ED..... ......ID 285,0-434,38}]
11-04 21:12:40.371 21520-21520/com.example.wangjiang.after D/UITestUtil: 布局[2]深度:9;次數:4;布局視圖:[com.android.internal.policy.PhoneWindow$DecorView{dc28997 V.E...... R.....ID 0,0-720,1280}, android.widget.LinearLayout{38ac784 V.E...... ......ID 0,0-720,1280}, android.widget.FrameLayout{ef95b6d V.E...... ......ID 0,36-720,1280}, android.support.v7.widget.ActionBarOverlayLayout{d5553a2 V.E...... ......ID 0,0-720,1244 #7f070030 app:id/decor_content_parent}, android.support.v7.widget.ContentFrameLayout{e1c2833 V.E...... ......ID 0,112-720,1244 #1020002 android:id/content}, android.support.constraint.ConstraintLayout{aed7f0 V.E...... ......ID 0,0-720,1132}, android.widget.LinearLayout{f5907ee V.E...... ......ID 0,528-720,604}, android.widget.FrameLayout{ec1831c V.E...... ......ID 0,38-720,76}, android.support.v7.widget.AppCompatTextView{1ff8b25 V.ED..... ......ID 285,0-434,38}]
上面統計了布局背景重疊超3次以上的布局路徑,3次一個,4次兩個,根節點是從DecorView開始的。當然,也可以根據個人需要來收集相應的資訊。
檢測視圖層級
同理,布局層級嵌套次數的統計,也可以通過同樣的方式:
/**
* 測試布局層級的View路徑
*
* @param view 根View節點
*/
public static void testLayoutHierarchy(View view) {
ArrayList<ArrayList<View>> result = new ArrayList<>();
ArrayList<View> list = new ArrayList<>();
findLayoutHierarchyPath(view, result, list, NUM);
Log.d(TAG, "布局層級嵌套大于" + NUM + "的布局的個數:" + result.size());
int index = 0;
for (ArrayList path : result)
Log.d(TAG, "布局[" + index++ + "]深度:" + path.size() + ";嵌套次數:" + (path.size() - 1) + ";布局視圖:" + path.toString());
}
/**
* @param node 根View節點
* @param result 布局層級路徑集合
* @param list 存儲臨時路徑
* @param target 布局層級嵌套的次數
*/
private static void findLayoutHierarchyPath(View node, ArrayList<ArrayList<View>> result, ArrayList<View> list, int target) {
if (node == null) return;
list.add(node);
if (!(node instanceof ViewGroup)) {
int count = list.size();
if (--count >= target)
result.add(list);
} else {
ViewGroup viewGroup = (ViewGroup) node;
for (int i = 0, count = viewGroup.getChildCount(); i < count; i++) {
findLayoutHierarchyPath(viewGroup.getChildAt(i), result, new ArrayList<View>(list), target);
}
}
}
統計結果:
11-04 21:43:53.786 23207-23207/com.example.wangjiang.after D/UITestUtil: 布局層級嵌套大于3的布局的個數:2
11-04 21:43:53.786 23207-23207/com.example.wangjiang.after D/UITestUtil: 布局[0]深度:4;嵌套次數:3;布局視圖:[android.support.v7.widget.ContentFrameLayout{e1c2833 V.E...... ......ID 0,112-720,1244 #1020002 android:id/content}, android.support.constraint.ConstraintLayout{aed7f0 V.E...... ......ID 0,0-720,1132}, android.widget.LinearLayout{f5907ee V.E...... ......ID 0,528-720,604}, android.support.v7.widget.AppCompatTextView{967148f V.ED..... ......ID 285,0-434,38}]
11-04 21:43:53.786 23207-23207/com.example.wangjiang.after D/UITestUtil: 布局[1]深度:5;嵌套次數:4;布局視圖:[android.support.v7.widget.ContentFrameLayout{e1c2833 V.E...... ......ID 0,112-720,1244 #1020002 android:id/content}, android.support.constraint.ConstraintLayout{aed7f0 V.E...... ......ID 0,0-720,1132}, android.widget.LinearLayout{f5907ee V.E...... ......ID 0,528-720,604}, android.widget.FrameLayout{ec1831c V.E...... ......ID 0,38-720,76}, android.support.v7.widget.AppCompatTextView{1ff8b25 V.ED..... ......ID 285,0-434,38}]
布局也是上面的布局,統計到布局層級嵌套超過3層的有2個,根節點是從android.R.id.content 開始的。
Hierarchy Viewer工具檢測
Hierarchy Viewer可以很直接的呈現布局的層次關系,視圖元件的各種屬性。 我們可以通過紅,黃,綠三種不同的顔色來區分布局的Measure,Layout,Executive的相對性能表現如何。
注意:Hierarchy Viewer已經被抛棄了。如果您使用的是Android Studio 3.1或更高版本,那麼應該使用Layout Inspector在運作時檢查應用程式的視圖層次結構。要了解應用程式布局的渲染速度,請使用此部落格文章中描述的Window.OnFrameMetricsAvailableListener。
clipRect 和 quickReject 方法
Canvas類提供了clipRect 和 quickReject 方法:
- clipRect:可以指定一塊矩形區域,隻有在這個區域内才會被繪制,其他的區域會被忽視。
- quickReject:判斷是否沒和某個矩形相交,進而跳過那些非矩形區域内的繪制操作。
clipRect 和 quickReject 方法主要是用來避免在自定義View中繪制重疊的區域。