Android性能分析&啟動(dòng)優(yōu)化

兩年前我做過(guò)了類似的啟動(dòng)優(yōu)化分析《如何統(tǒng)計(jì)Android App啟動(dòng)時(shí)間》《如何優(yōu)化Androd App啟動(dòng)速度》。兩年過(guò)后,今天看來(lái),之前說(shuō)的nimbledroid工具已經(jīng)需要收費(fèi),而且Android Studio自帶的Android Profiler已經(jīng)足夠強(qiáng)大,并且Systrace也有了更為強(qiáng)大的Perfetto UI分析工具。我們是時(shí)候來(lái)重新學(xué)習(xí)一下目前性能分析的方法以及如何在分析的基礎(chǔ)上做啟動(dòng)優(yōu)化這個(gè)事情。轉(zhuǎn)載請(qǐng)注明來(lái)源「Bug總柴」

性能分析工具

首先我們來(lái)學(xué)習(xí)一下如何使用性能分析的工具。我們從一個(gè)具體的例子出發(fā),就是如何分析應(yīng)用啟動(dòng)的性能。

Android Profiler

配置

我們來(lái)先看看Android Profiler。為了能在應(yīng)用一啟動(dòng)就能馬上捕捉到分析數(shù)據(jù),我們需要按照下面的步驟配置一下:

  • 選擇 Run -> Edit Configurations


    步驟一
  • 在設(shè)置里面選擇Profiling的tab,然后選中Start recording CPU activity on startup。注意這里選擇的Sample Java Methods,表示可以定位到Java代碼。其他選項(xiàng)的含義查看cpu-profiler#configurations。
    如果想有更詳細(xì)的信息的話,可以選中Enable advanced profiling。
    步驟二
  • 在配置完之后選擇Run -> Profiler


    步驟三

    在頁(yè)面啟動(dòng)完成之后停止監(jiān)測(cè),可以得到啟動(dòng)過(guò)程的CPU、內(nèi)存網(wǎng)絡(luò)和電量消耗信息,如下圖:


    Android Profiler

CPU監(jiān)控

分析過(guò)程

點(diǎn)擊進(jìn)入CPU模塊


CPU分析

可以選擇線程,并看到線程的具體代碼耗時(shí)。
如以下例子


CPU分析例子

綠色表示我們寫的代碼耗時(shí),我們可以選擇主線程進(jìn)行觀察。這里顯示在Applicaiton onCreate過(guò)程中需要耗費(fèi)620ms。其中比較耗時(shí)的方法是registerByCourseKey和initYouzanSDK。并且通過(guò)Call Chart視圖不斷的往下看可以看出導(dǎo)致這個(gè)方法耗時(shí)的具體原因
registerByCourseKey.png

initYouzanSDK.png

通過(guò)這樣不斷的往下分析,就能大致定位到啟動(dòng)CPU耗時(shí)的原因。下面我們舉一個(gè)具體的優(yōu)化例子。

優(yōu)化例子

優(yōu)化前:


優(yōu)化前

如果上圖所示,在啟動(dòng)過(guò)程中RxBroadcast的時(shí)候帶來(lái)了較大的耗時(shí)


RxBroadcast

查看代碼:
private fun initBroadcast() {
    val filter = IntentFilter()
    ……
    disposables.add(RxBroadcast.fromLocalBroadcast(context, filter)
        .subscribe({ intent ->
            ……
        },
        { throwable: Throwable ->
            ……
        }
   ))
}

確實(shí)在initBroadcast使用了RxBroadcast.fromLocalBroadcast()方法,我們嘗試使用LocalBroadcastManager.registerReceiver代替。修改為如下代碼:

private fun initBroadcast() {
    val filter = IntentFilter()
    ……
    LocalBroadcastManager.getInstance(context).registerReceiver(broadcastReceiver, filter)
}

優(yōu)化后重新進(jìn)行啟動(dòng)CPU分析:


優(yōu)化后

可以看出初始化的時(shí)間比優(yōu)化前減少了90ms。由此我們也可以得到結(jié)論,使用RxBroadcast雖然比較炫酷,但是這是一個(gè)比較耗時(shí)的行為,因此應(yīng)該盡量減少RxBroadcast的使用。

注意事項(xiàng)

  • 需要注意的是這里的耗時(shí)有些是在CPU處于Sleep狀態(tài)下的。
    在Sleep狀態(tài)表示CPU被其他線程占用,這個(gè)時(shí)候需要分析主線程Sleep狀態(tài)下其他線程的情況。例如:


    sleep

    這里顯示主線程在00:06左右的時(shí)間處于Sleeping狀態(tài),這個(gè)時(shí)候查看其他線程的CPU占用


    memoryag

    發(fā)現(xiàn)在MemoryAg的線程在占用CPU資源,這種情況下不應(yīng)該認(rèn)為對(duì)應(yīng)的主線程方法耗時(shí),而是要考慮例如內(nèi)存回收或者其他線程占用了CPU資源的情況。
  • 還需要注意不是每次點(diǎn)擊"Profiler"都會(huì)正常把信息記錄下來(lái),偶爾會(huì)出現(xiàn)應(yīng)用閃退的情況,這可能是Android Studio的Bug或者是日志太大了的問(wèn)題。這種情況不要灰心,多試幾次就會(huì)好。

Perfetto UI

使用過(guò)程

在Android 10的手機(jī)上,開發(fā)者模式新增加了一個(gè)“系統(tǒng)跟蹤”的功能,我們首先將開發(fā)者模式下的“系統(tǒng)跟蹤”打開:

系統(tǒng)跟蹤

開啟跟蹤圖塊

我們也可以從“類別”選項(xiàng)中選擇我們關(guān)注的信息類別:
信息類別

設(shè)置完之后我們會(huì)發(fā)現(xiàn)下拉快捷選項(xiàng)多了個(gè)棒棒糖形狀的圖標(biāo)
系統(tǒng)跟蹤棒棒糖

這個(gè)時(shí)候殺掉我們需要調(diào)試的應(yīng)用,然后點(diǎn)擊開啟棒棒糖,接著打開應(yīng)用,等待應(yīng)用完全打開之后,再點(diǎn)擊一次棒棒糖,結(jié)束錄制。
開始錄制

結(jié)束錄制

然后我們保存錄制后的文件,后綴為“.perfetto-trace”
然后我們?cè)?a target="_blank">perfetto ui網(wǎng)站上選擇Open trace file上傳剛剛得到的文件
perfetto上傳

渲染之后我們可以得到類似于之前systrace的分析,通過(guò)Perfetto UI我們可以更加容易操控
Perfetto分析

分析過(guò)程

首先我們需要知道,通過(guò)“系統(tǒng)跟蹤”得到的結(jié)果是類似于在Android Studio里面Profiler選擇“Trace System Calls”的結(jié)果,我們可以看到系統(tǒng)中所有CPU在時(shí)間軸的所有運(yùn)行任務(wù)。并且我們也可以看到系統(tǒng)所有的進(jìn)程以及進(jìn)程中所有的線程任務(wù)。


Trace System Calls

我們展開Perfetto UI的調(diào)試應(yīng)用里面的主線程:


詞典主線程

可以看到線程中每個(gè)步驟的耗時(shí)。我們可以通過(guò)不斷的放大來(lái)查看每個(gè)時(shí)間段的系統(tǒng)調(diào)用。

優(yōu)化例子

優(yōu)化前:


perfetto優(yōu)化前 inflate
perfetto優(yōu)化前

可以看出在首頁(yè)inflate的過(guò)程中,有個(gè)一個(gè)“bg_simple_dict_blueriver.jpg”的圖標(biāo)耗時(shí)了29ms加載。分析其所在的代碼:

<ImageView
    android:id="@+id/iv_simple_dict_bg"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:src="@drawable/bg_simple_dict_blueriver"
    android:scaleType="centerCrop"
    android:visibility="gone"
/>

由于這個(gè)圖片只會(huì)在網(wǎng)絡(luò)不暢的時(shí)候作為placeholder存在,因此這里簡(jiǎn)單的做法可以將

android:src="@drawable/bg_simple_dict_blueriver"

修改為

tools:src="@drawable/bg_simple_dict_blueriver"

更好的辦法也可以將ImageView改為ViewStub引入,在有需要的時(shí)候再渲染出來(lái),節(jié)省布局渲染時(shí)間。
優(yōu)化后:


perfetto 優(yōu)化后

可以看出,在優(yōu)化后inflate的時(shí)間由原來(lái)的118ms降低到了103ms,并且在inflate過(guò)程中也沒(méi)有了bg_simple_dict_blueriver.jpg圖片加載的過(guò)程。

啟動(dòng)優(yōu)化

有了以上的Sample Java Methods以及Trace System Calls分析,我們可以得到從宏觀代碼層面以及微觀CPU執(zhí)行層面的啟動(dòng)任務(wù)耗時(shí)。

Proguard & R8

Proguard

R8

除了業(yè)務(wù)的懶加載處理之外,我們可以看到dex文件的加載時(shí)間占據(jù)了大部分的啟動(dòng)時(shí)間。dex的加載時(shí)間跟代碼量級(jí)有關(guān)。由于長(zhǎng)期的歷史引入了大量了第三方庫(kù)以及本身業(yè)務(wù)增長(zhǎng)帶來(lái)的代碼量增加,我們dex加載的速度也越來(lái)越慢。為了解決dex加載慢的問(wèn)題,我們可以通過(guò)兩個(gè)方面:首先是處理對(duì)dex加載有較大影響的加固過(guò)程,這個(gè)可以跟杭研進(jìn)行溝通處理。第二就是在代碼中加入代碼壓縮和混淆。

代碼壓縮和混淆可以使得dex文件變小,從而減少dex文件加載的時(shí)間。但是從零開始加入代碼壓縮和混淆是一個(gè)非常艱巨的過(guò)程,因?yàn)榇a壓縮和混淆后會(huì)導(dǎo)致很容易發(fā)生ClassNotFoundException以及NoSuchMethodError,并且會(huì)對(duì)諸如push、序列化等依賴類名以及屬性名的代碼失效。加入代碼壓縮和混淆需要額外的細(xì)心和較大的工作量。

在加入代碼壓縮和混淆的過(guò)程中,我們總結(jié)了以下的方法步驟:

本地代碼

  • 檢查所有使用注解的代碼,加入proguard 規(guī)則
  • 檢查所有JNI相關(guān)代碼,加入proguard 規(guī)則
  • 檢查所有使用反射的代碼,加入proguard 規(guī)則
  • 檢查所有序列化以及會(huì)使用Json轉(zhuǎn)換為Modle的代碼,加入proguard 規(guī)則
  • 檢查所有根據(jù)類名來(lái)使用的代碼,例如Push等,加入proguard 規(guī)則
  • 要求以后代碼重構(gòu)需要對(duì)Proguard進(jìn)行相應(yīng)改變
  • 要求新增的代碼需要添加Proguard規(guī)則

三方代碼

  • 判斷External Libraries中的三方庫(kù)引用是否是release依賴或者debug依賴,如果是的話繼續(xù)
  • 判斷l(xiāng)ib庫(kù)是否為目前代碼所需要的,如果引用了沒(méi)有使用或者引用了目前代碼上所有使用的地方都已經(jīng)不再使用,則清理這個(gè)lib并清理相關(guān)沒(méi)有用到的代碼
  • 若果lib庫(kù)為目前代碼所需要的,到該lib庫(kù)的官網(wǎng)查找相應(yīng)的proguard規(guī)則,并粘貼到proguard-rules.pro文件中
  • 如果該lib官網(wǎng)庫(kù)沒(méi)有相應(yīng)proguard規(guī)則,則觀察lib庫(kù)是否有用到native代碼、annotation或者反射這種需要proguard處理的地方,有的話添加相應(yīng)規(guī)則
  • 添加完proguard規(guī)則之后,找到目前項(xiàng)目中使用到這個(gè)庫(kù)的地方,嘗試一下是否會(huì)有崩潰出現(xiàn)
  • 如果有崩潰出現(xiàn),根據(jù)崩潰提示增加相應(yīng)proguard規(guī)則

參考

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

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