APP性能優(yōu)化-UI

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、ConstraintLayoutAbsoluteLayout、TableLayout為針對性布局,這里不做分析。布局的繪制過程由ViewRootImp中發(fā)起,經(jīng)過performTraversals->performMeasure->performLayout->performDraw,然后分發(fā)到ViewGroup以及View,性能問題主要取決于各View的onMeasureonLayout、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、LinearLayoutConstraintLayout查看打印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)以下效果
image.png

使用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)以下效果
image.png

實(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)化方案
image.png
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);
image.png
overdraw(過度繪制)

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

官方對overedraw的層級定義
image.png

在實(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è)置中開啟過度繪制,查看布局


image.png

只設(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>

重新編譯,查看


image.png

根布局的已經(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

image.png

分析工具
Layout Inspector

主要功能為能dump到其他APP的UI層級


image.png
Lint
image.png
image.png

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

GPU過度繪制
image.png
GPU呈現(xiàn)分析

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

image.png

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容