APP性能優(yōu)化-Memory
APP性能優(yōu)化-穩(wěn)定性(crash率)
APP性能優(yōu)化-包體壓縮
APP性能優(yōu)化-CPU
APP性能優(yōu)化-UI
APP性能優(yōu)化-流暢度
影響UI性能的幾個(gè)方面
1.各種布局自身性能問題,FrameLayout >LinearLayout >RelativeLayout>ConstraintLayout
2.布局層級、復(fù)雜度,合理使用布局特性減少布局層級以及復(fù)雜度
3.overdraw(過度繪制),去除theme默認(rèn)背景,減少不必要的背景設(shè)置
分析工具 :Layout Inspector、Lint、GPU過度繪制、GPU呈現(xiàn)分析
布局本身性能問題
常見的六種布局:FrameLayout、RelativeLayout、LinearLayout、AbsoluteLayout、TableLayout、ConstraintLayout。AbsoluteLayout、TableLayout為針對性布局,這里不做分析。布局的繪制過程由ViewRootImp中發(fā)起,經(jīng)過performTraversals->performMeasure->performLayout->performDraw,然后分發(fā)到ViewGroup以及View,性能問題主要取決于各View的onMeasure、onLayout、onDraw,寫一段測試代碼跟蹤以上各種布局關(guān)鍵函數(shù)的執(zhí)行步驟:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity"
android:orientation="vertical"
android:id="@+id/main"
android:background="@android:color/white"
>
<com.code.layoutanalyze.TLinearLayout
android:layout_width="match_parent"
android:background="@android:color/white"
android:layout_margin="20dp"
android:layout_height="50dp"
></com.code.layoutanalyze.TLinearLayout>
</FrameLayout>
public class TLinearLayout extends LinearLayout {
...
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
L.e(TAG,"onMeasure");
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
L.e(TAG,"onLayout");
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
L.e(TAG,"onDraw");
}
}
依次把TLinearLayout的parent換成FrameLayout、RelativeLayout、LinearLayout、ConstraintLayout查看打印log
parent為`FrameLayout`、`LinearLayout`
2019-10-29 18:02:33.865 28124-28124/com.code.layoutanalyze E/com.code.layout:TLinearLayout: onMeasure
2019-10-29 18:02:33.895 28124-28124/com.code.layoutanalyze E/com.code.layout:TLinearLayout: onMeasure
2019-10-29 18:02:33.896 28124-28124/com.code.layoutanalyze E/com.code.layout:TLinearLayout: onLayout
2019-10-29 18:02:33.939 28124-28124/com.code.layoutanalyze E/com.code.layout:TLinearLayout: onDraw
parent為`RelativeLayout`、`ConstraintLayout`
2019-10-29 18:03:05.530 28482-28482/com.code.layoutanalyze E/com.code.layout:TLinearLayout: onMeasure
2019-10-29 18:03:05.531 28482-28482/com.code.layoutanalyze E/com.code.layout:TLinearLayout: onMeasure
2019-10-29 18:03:05.556 28482-28482/com.code.layoutanalyze E/com.code.layout:TLinearLayout: onMeasure
2019-10-29 18:03:05.557 28482-28482/com.code.layoutanalyze E/com.code.layout:TLinearLayout: onMeasure
2019-10-29 18:03:05.558 28482-28482/com.code.layoutanalyze E/com.code.layout:TLinearLayout: onLayout
2019-10-29 18:03:05.587 28482-28482/com.code.layoutanalyze E/com.code.layout:TLinearLayout: onDraw
經(jīng)過代碼跟蹤發(fā)現(xiàn)再一次繪制過程中performTraversals被調(diào)用兩次,直接導(dǎo)致onMeasure被調(diào)用兩次,onLayout、onDraw會(huì)進(jìn)行優(yōu)化之調(diào)用了一次,具體繪制過程可以參考http://www.itdecent.cn/p/733c7e9fb284
RelativeLayout、ConstraintLayout為什么onMeasure被雙倍調(diào)用?
這兩個(gè)布局子View存在依賴關(guān)系,都是基于相對位置的,而且子View會(huì)在橫向和縱向兩個(gè)方向上分布,因此,需要在橫向和縱向分別進(jìn)行一次measure過程。
布局自身性能結(jié)論
理論狀態(tài):在同等布局層級的情況下 FrameLayout >LinearLayout >RelativeLayout>ConstraintLayout,由于FrameLayout約束條件較少在所以會(huì)優(yōu)于LinearLayout
實(shí)際狀態(tài):由于布局的層級在優(yōu)先級上高于布局本身的性能,在實(shí)際開發(fā)中頁面都是復(fù)雜布局,ConstraintLayout、RelativeLayout能在復(fù)雜布局中大量減少布局層級,所以FrameLayout >ConstraintLayout >RelativeLayout>LinearLayout,這也是為什么新建Activity時(shí)默認(rèn)布局從原來的RelativeLayout變成了ConstraintLayout
布局層級、復(fù)雜度
盡量追求以最扁平的方式以及最少的布局個(gè)數(shù)實(shí)現(xiàn)想通的布局效果,合理使用布局特性減少布局層級以及復(fù)雜度。
示例1:假設(shè)如果我們要實(shí)現(xiàn)以下效果

使用LinearLayout
<LinearLayout
android:layout_width="match_parent"
android:layout_height="100dp"
android:orientation="horizontal"
>
<TextView
android:id="@+id/tx1"
android:layout_width="50dp"
android:layout_height="match_parent"
android:text="1"
android:background="@color/colorPrimary"
android:gravity="center"
/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
>
<TextView
android:id="@+id/tx2"
android:layout_width="match_parent"
android:layout_height="0dp"
android:text="2"
android:layout_weight="1"
android:background="@color/colorAccent"
android:gravity="center"
/>
<TextView
android:id="@+id/tx3"
android:layout_width="match_parent"
android:layout_height="0dp"
android:text="3"
android:layout_weight="1"
android:background="@color/colorPrimaryDark"
android:gravity="center"
/>
</LinearLayout>
</LinearLayout>
使用RelativeLayout
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="100dp"
android:orientation="horizontal"
>
<TextView
android:id="@+id/tx1"
android:layout_width="50dp"
android:layout_height="match_parent"
android:text="1"
android:background="@color/colorPrimary"
android:gravity="center"
/>
<TextView
android:layout_toRightOf="@+id/tx1"
android:id="@+id/tx2"
android:layout_width="match_parent"
android:layout_height="50dp"
android:text="2"
android:layout_weight="1"
android:background="@color/colorAccent"
android:gravity="center"
android:layout_marginBottom="50dp"
/>
<TextView
android:layout_alignBottom="@+id/tx1"
android:layout_toRightOf="@+id/tx1"
android:id="@+id/tx3"
android:layout_width="match_parent"
android:layout_height="50dp"
android:text="3"
android:layout_weight="1"
android:background="@color/colorPrimaryDark"
android:gravity="center"
/>
</RelativeLayout>
RelativeLayout所需要的布局層級明顯少于LinearLayout
示例2:假設(shè)如果我們要實(shí)現(xiàn)以下效果

實(shí)現(xiàn)方案一:
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
>
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@mipmap/ic_launcher"
/>
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginLeft="20dp"
android:text="測試測試測試"
android:gravity="center_vertical"
/>
</LinearLayout>
優(yōu)化方案:
<TextView
android:layout_marginTop="100dp"
android:drawableLeft="@mipmap/ic_launcher"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:drawablePadding="20dp"
android:text="測試測試測試"
android:gravity="center_vertical"
/>
其它布局優(yōu)化方案

1.include提高布局復(fù)用
<?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:background="@android:color/white"
>
<include layout="@layout/view_test" />
</LinearLayout>
view_test.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="match_parent"
android:orientation="vertical"
>
<ImageView
android:layout_width="wrap_content"
android:src="@mipmap/ic_launcher_round"
android:layout_height="wrap_content"
/>
<TextView
android:layout_width="match_parent"
android:text="12345"
android:layout_marginLeft="10dp"
android:layout_height="wrap_content"
/>
</LinearLayout>
2.merage減少布局層級
<?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:background="@android:color/white"
>
<include layout="@layout/view_test" />
</LinearLayout>
view_test.xml
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
>
<ImageView
android:layout_width="wrap_content"
android:src="@mipmap/ic_launcher_round"
android:layout_height="wrap_content"
/>
<TextView
android:layout_width="match_parent"
android:text="12345"
android:layout_marginLeft="10dp"
android:layout_height="wrap_content"
/>
</merge>
3.ViewStub減少初次測量 & 繪制時(shí)間
ViewStub繼承了View,它非常輕量級且寬和高都為0,因此它本身不參與任何的繪制過程,避免資源的浪費(fèi),減少渲染時(shí)間,在需要的時(shí)候才加載View。因此ViewStub的意義在于按需求加載所需的布局,在實(shí)際開發(fā)中,很多布局在正常情況下不會(huì)顯示,比如加載數(shù)據(jù)暫無數(shù)據(jù),網(wǎng)絡(luò)異常等界面,這個(gè)時(shí)候就沒必要在整個(gè)界面初始化的時(shí)候?qū)⑵浼虞d進(jìn)來,通過ViewStub就可以做到在使用時(shí)在加載,提高了程序初始化時(shí)的性能
<?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"
android:background="@android:color/white"
android:orientation="vertical"
>
<ViewStub
android:id="@+id/error_view"
android:layout="@layout/view_error"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
</RelativeLayout>
view_error.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal"
android:layout_centerInParent="true"
xmlns:android="http://schemas.android.com/apk/res/android"
>
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@android:drawable/btn_dialog"
/>
<TextView
android:paddingLeft="20dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="加載錯(cuò)誤,請重試"
/>
</LinearLayout>
ViewStub error_view = findViewById(R.id.error_view);
error_view.inflate();//或者 error_view.setVisibility(View.VISIBLE);

overdraw(過度繪制)
過度繪制只跟重疊View的背景有關(guān)系,如果一塊區(qū)域有100個(gè)View重疊但是這些View都沒設(shè)置背景是不會(huì)發(fā)生overdraw的。overdraw優(yōu)化方向:
1.去除theme默認(rèn)背景
2.減少布局不必要的背景設(shè)置
官方對overedraw的層級定義

在實(shí)際開發(fā)中每個(gè)頁面的主背景色可能都不一致,新建一個(gè)Activity時(shí)一般都會(huì)在跟布局上寫上自己的顏色
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity"
android:orientation="vertical"
android:id="@+id/main"
android:background="@android:color/white"
>
</LinearLayout>
設(shè)置中開啟過度繪制,查看布局

只設(shè)置了一個(gè)背景,根布局為什么就已1X overdraw了?一般會(huì)在Application中設(shè)置theme,theme是有默認(rèn)背景的
<application
android:theme="@style/AppTheme"
>
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
<style name="Base.ThemeOverlay.AppCompat.Dark" parent="Platform.ThemeOverlay.AppCompat.Dark">
<item name="android:windowBackground">@color/background_material_dark</item>
</style>
去除theme的默認(rèn)背景
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<item name="android:windowBackground">@null</item>
</style>
重新編譯,查看

根布局的已經(jīng)不存在overdraw了。在一個(gè)頁面的根布局可能會(huì)存在很多自定義的組合控件,在這些組合控件上很多人會(huì)設(shè)置默認(rèn)的顏色,這是很不好的習(xí)慣。把多個(gè)控件自定義組合起來一般是為了在多個(gè)地方復(fù)用,那每個(gè)頁面的主題色有可能是不一致的,如果設(shè)置的背景色和頁面主題色一致就會(huì)出現(xiàn)overdraw,如果不一致就需要重新設(shè)置組合控件的背景色。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity"
android:orientation="vertical"
android:id="@+id/main"
android:background="@android:color/white"
>
<com.code.layoutanalyze.TLinearLayout
android:layout_width="match_parent"
android:background="@android:color/white"
android:layout_margin="20dp"
android:layout_height="50dp"
>
</com.code.layoutanalyze.TLinearLayout>
</LinearLayout>
TLinearLayout是個(gè)自定義組合控件,設(shè)置了默認(rèn)背景色和跟布局一致(這層背景是多余的設(shè)置),出現(xiàn)2X overdraw

分析工具
Layout Inspector
主要功能為能dump到其他APP的UI層級

Lint


掃描出與布局相關(guān)的部分結(jié)果,以下只是Lint的冰山一角
1.paddingStart優(yōu)于paddingLeft
2.marginStart優(yōu)于marginLeft
3.不建議Hardcoded text內(nèi)容
4.無用資源文件
GPU過度繪制

GPU呈現(xiàn)分析
在屏幕上顯示條形圖:大致顯示了流暢度以及一些閾值
在adb shell dumpsys fgxinfo:通過adb命令拿到具體的量化幀率值,在后續(xù)的流暢度分析中會(huì)介紹

http://www.itdecent.cn/p/642f47989c7c
https://developer.huawei.com/consumer/cn/forum/forum.php?mod=viewthread&fid=23&tid=248&extra=page%3D1