Android項(xiàng)目時(shí)間越長(zhǎng),一不留神尺棋,就發(fā)現(xiàn)啟動(dòng)越來(lái)越慢封锉,越慢越慢......
這極大地影響了用戶(hù)體驗(yàn),所以提升啟動(dòng)速度非常重要了陡鹃。
所謂工欲善其事烘浦,必先利其器,本章咱們先來(lái)講講啟動(dòng)優(yōu)化需要的一些工具萍鲸!
一闷叉、 一些簡(jiǎn)單的啟動(dòng)耗時(shí)統(tǒng)計(jì)
1. ADB 命令查看啟動(dòng)耗時(shí)。
在命令行中輸入以下命令脊阴,它會(huì)自己?jiǎn)?dòng)APP握侧,并且打印啟動(dòng)耗時(shí)蚯瞧。
adb shell am start -S -W com.xiaoxiao.demo/.MainActivity
打印結(jié)果:
Stopping: com.xiaoxiao.demo
Starting: Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] cmp=com.xiaoxiao.demo/.MainActivity }
Status: ok
Activity: com.xiaoxiao.demo/.MainActivity
ThisTime: 6309
TotalTime: 6309
WaitTime: 6371
Complete
ThisTime :該activity啟動(dòng)耗時(shí),單位ms品擎。
TotalTime :應(yīng)用自身啟動(dòng)耗時(shí)=ThisTime+應(yīng)用application等資源啟動(dòng)時(shí)間埋合。WaitTime :系統(tǒng)啟動(dòng)應(yīng)用耗時(shí)=TotalTime+系統(tǒng)資源啟動(dòng)時(shí)間。
2. logcat查看頁(yè)面啟動(dòng)時(shí)間
在logcat中過(guò)濾包含 Displayed 的打印結(jié)果萄传。這個(gè)值代表從啟動(dòng)進(jìn)程到在屏幕上完成對(duì)應(yīng) Activity 的繪制所用的時(shí)間甚颂。
ActivityManager: Displayed com.babytree.apps.pregnancy/.MainActivity: +6s349ms
ActivityManager: Displayed com.babytree.apps.pregnancy/.MainActivity: +5s543ms
3. 增加啟動(dòng)耗時(shí)日志
想知道比較細(xì)節(jié)一些的,比如Application中哪些任務(wù)比較耗時(shí)秀菱,總耗時(shí)等振诬,就可以自己打印一些日志了。在方法調(diào)用前后分別加上日志衍菱,記錄時(shí)間即可赶么。
/**
* 方便查看方法調(diào)用時(shí)長(zhǎng)。
* 使用 方法前調(diào)用 begin("FACTION_TAG_NAME") 方法后調(diào)用 end("FACTION_TAG_NAME")
*/
object TimeLog {
private var TAG = "TimeLog"
private var isOpenLog = false
private var times: ConcurrentHashMap<String, Long>? = null
private var current = System.currentTimeMillis()
private var current1 = current
@JvmStatic
fun openLog(open: Boolean) {
isOpenLog = open
if (isOpenLog) {
times = ConcurrentHashMap(20)
}
}
@JvmStatic
fun setLogTag(tag: String) {
TAG = tag
}
/**
* 與 end 或者 endI 成對(duì)出現(xiàn)
*/
@JvmStatic
fun begin(name: String?) {
if (times != null && !name.isNullOrEmpty()) {
times!![name] = System.currentTimeMillis()
}
}
/**
* 與begin成對(duì)出現(xiàn)
*/
@JvmStatic
fun end(name: String?) {
if (times != null && !name.isNullOrEmpty() && times?.get(name) != null) {
val l = System.currentTimeMillis() - times?.get(name)!!
Log.d(TAG, "$name:$l")
}
}
/**
* 與begin成對(duì)出現(xiàn)
*/
@JvmStatic
fun endI(name: String?) {
if (!times.isNullOrEmpty() && !name.isNullOrEmpty() && times?.get(name) != null) {
val l = System.currentTimeMillis() - times?.get(name)!!
Log.i(TAG, "$name:$l")
}
}
/**
* 打印第一次初始化此類(lèi)時(shí)間和現(xiàn)在時(shí)間的時(shí)間差脊串。
* 打印與上一個(gè)調(diào)用times()方法的時(shí)間差辫呻。
* 打印當(dāng)前時(shí)間的時(shí)間戳。
*/
@JvmStatic
fun times(tag: String) {
val millis = System.currentTimeMillis()
val l = millis - current
val l1 = millis - current1
Log.d(TAG, "$tag:totalTime:$l,thisTime:$l1,current:$millis")
current1 = millis
}
}
比如琼锋,想要知道直播初始化需要的時(shí)間:
TimeLog.begin("LiveCenter")
LiveCenter.init(this, 1, sdkDebug)
TimeLog.end("LiveCenter")
這種方式比較簡(jiǎn)單放闺,方便我們自己查看各任務(wù)的啟動(dòng)時(shí)間,快速定位哪些任務(wù)耗時(shí)斩例,后續(xù)再針對(duì)任務(wù)進(jìn)行分析優(yōu)化雄人。還有从橘,這種直接打印也只是自己看看念赶,知道個(gè)大概,數(shù)據(jù)不準(zhǔn)確恰力。如果想知道準(zhǔn)確的數(shù)據(jù)叉谜,則需要大批量的數(shù)據(jù)信息了,這個(gè)現(xiàn)在有些公司提供的有服務(wù)踩萎,比如火山停局,也是通過(guò)這種類(lèi)似的方式添加日志,不過(guò)他們已經(jīng)做好了數(shù)據(jù)統(tǒng)計(jì)香府,所以比較方便查看大數(shù)據(jù)量的統(tǒng)計(jì)結(jié)果董栽,這樣就能看到各個(gè)任務(wù)在各種機(jī)型和所有手機(jī)上的平均啟動(dòng)時(shí)長(zhǎng)等信息了。
來(lái)企孩,筒子們锭碳,看看美圖,養(yǎng)養(yǎng)眼勿璃,咱們休息1秒鐘~
二擒抛、啟動(dòng)時(shí)長(zhǎng)分析監(jiān)測(cè)工具
1. Systrace工具
最簡(jiǎn)單的生成 HTML 報(bào)告的命令:
python2 systrace.py
在systrace.py文件目錄下(在platform-tools文件夾里面)推汽,輸入左邊命令。比如我的systrace.py在以下目錄中
file:///Users/xiaoxiao/Library/Android/sdk/platform-tools/systrace
手機(jī)連上電腦歧沪,按下enter命令后就開(kāi)始跟蹤歹撒,啟動(dòng)想要追蹤的APP,啟動(dòng)結(jié)束后诊胞,在命令行中再次按 Enter 鍵結(jié)束跟蹤暖夭。systrace 會(huì)將報(bào)告保存到 systrace.py 所在的目錄中,并將其命名為 trace.html撵孤。
至于systrace的其他命令鳞尔,大家可以上網(wǎng)查啊,一大堆啊早直,我就不詳細(xì)描述了寥假。
python2 systrace.py -a com.xiaoxiao.demo -t 9 -o trace_app_9s.html sched freq idle am wm gfx view binder_driver hal dalvik camera input res memory
trace.html
可以直接用瀏覽器打開(kāi) ( 基本操作:w鍵放大,s鍵縮小霞扬,a左移糕韧,d右移 ),這時(shí)候就可以比較直觀的查看啟動(dòng)耗時(shí)了喻圃。
對(duì)于比較耗時(shí)的任務(wù)萤彩,可以查看它里面具體的耗時(shí)的方法,從而分析耗時(shí)的原因斧拍,再看能不能減少耗時(shí)雀扶。來(lái),咱們舉個(gè)栗子八列凇愚墓!
仔細(xì)看FileProvider啟動(dòng)耗時(shí),我們發(fā)現(xiàn)它的啟動(dòng)耗時(shí)主要就是getPathStrategy() 方法耗時(shí)昂勉。如果要解決FileProvider耗時(shí)問(wèn)題浪册,就得從這個(gè)方法出發(fā),往下找它的耗時(shí)原因岗照,再找解決方案村象。
這里我直接說(shuō)結(jié)果啊,分析完源碼的結(jié)果就是:getPathStrategy 是在啟動(dòng)階段完全沒(méi)有必要的攒至,可以在 FileProvider的query厚者、getType、openFile 等接口被調(diào)用到的時(shí)候再去執(zhí)行 getPathStrategy 邏輯迫吐。
解決方案:FileProvider 是 androidx 中的代碼库菲,無(wú)法直接修改,但是它會(huì)參與代碼編譯渠抹,所以可以在編譯階段通過(guò)修改字節(jié)碼的方式去修改它的實(shí)現(xiàn)蝙昙。
通過(guò)字節(jié)碼修改FileProvider.attachInfo 內(nèi) getPathStrategy耗時(shí)方法調(diào)用時(shí)機(jī)闪萄,延遲給mStrategy變量賦值的減少Application.attachBaseContext結(jié)束到onCreate開(kāi)始之間的耗時(shí),然后在 FileProvider的query奇颠、getType败去、openFile 等接口被調(diào)用到的時(shí)候再去執(zhí)行 getPathStrategy 并賦值的邏輯。
上圖是優(yōu)化后的圖烈拒,可以看到在啟動(dòng)的時(shí)候getPathStrategy沒(méi)有調(diào)用圆裕,不過(guò)圖中沒(méi)有體現(xiàn)出來(lái)啟動(dòng)時(shí)長(zhǎng)差異,據(jù)不負(fù)責(zé)統(tǒng)計(jì)荆几,實(shí)際上啟動(dòng)時(shí)長(zhǎng)短了約30ms(這個(gè)我們沒(méi)有走大數(shù)據(jù)量的統(tǒng)計(jì)吓妆,只是根據(jù)個(gè)別手機(jī)得出來(lái)的一個(gè)數(shù)值)。
篇幅原因吨铸,這里涉及到的修改字節(jié)碼和具體修改這里不細(xì)說(shuō)了行拢,回頭會(huì)單獨(dú)寫(xiě)一篇文章說(shuō)說(shuō)FileProvider的啟動(dòng)耗時(shí)優(yōu)化。感興趣的朋友也可以在網(wǎng)上搜一下這一塊的文章诞吱。
2. 字節(jié)開(kāi)源性能分析工具-Btrace
btrace(又名 RheaTrace) 是一個(gè)基于 Systrace 實(shí)現(xiàn)的高性能 Android trace 工具舟奠,它支持在 App 編譯期間自動(dòng)注入自定義事件,并使用 bhook額外提供 IO 等 native 事件房维。
特征:
支持自動(dòng)注入自定義事件沼瘫,在編譯 Apk 期間為 App 方法自動(dòng)注入Trace#beginSection(String) 和 Trace#endSection()。
提供額外 IO 等 native 事件咙俩,方便定位耗時(shí)原因耿戚。
支持僅采集主線程 trace 事件。
使用便捷阿趁,穩(wěn)定性高膜蛔,性能優(yōu)于 Systrace。
接入和使用:
- 根目錄下 build.gradle 文件中增加 rhea-gradle-plugin 作為依賴(lài)歌焦。
dependencies {
classpath 'com.bytedance.btrace:rhea-gradle-plugin:1.0.1'
}
- app/build.gradle 文件中應(yīng)用如下所示插件和依賴(lài)飞几。
dependencies {
//rheatrace core lib
implementation "com.bytedance.btrace:rhea-core:1.0.1"
}
...
rheaTrace {
compilation {
//為減少 APK 體積, 你可以為 App 中需要跟蹤的方法設(shè)置 id 以此來(lái)跟蹤此自定義事件, 默認(rèn)值 false。
traceWithMethodID = false
//該文件配置決定哪些方法您不希望跟蹤, 默認(rèn)值 null独撇。
traceFilterFilePath = "${project.rootDir}/rhea-trace/traceFilter.txt"
//用特指定方法 id 來(lái)設(shè)置自定義事件名稱(chēng), 默認(rèn)值 null。
applyMethodMappingFilePath = "${project.rootDir}/rhea-trace/keep-method-id.txt"
}
runtime {
//僅在主線程抓取跟蹤事件, 默認(rèn)值 false躁锁。
mainThreadOnly true
//在 App 啟動(dòng)之初開(kāi)始抓取跟蹤事件, 默認(rèn)值 true纷铣。
startWhenAppLaunch true
//指定內(nèi)存存儲(chǔ) atrace 數(shù)據(jù) ring buffer 的大小。
atraceBufferSize "500000"
}
}
...
apply plugin: 'com.bytedance.rhea-trace'
- 檢測(cè)電腦 python 版本战转,由于 Systrace 的關(guān)系 RheaTrace 僅支持 python2.7 版本搜立,請(qǐng)將 systrace配置在環(huán)境變量中。
例如槐秧,我是Mac啄踊,環(huán)境變量配置在 ~/.bash_profile 文件中忧设。
export PATH=${PATH}:/Users/${user_name}/Library/Android/sdk/platform-tools/systrace
以上配置完成后,打開(kāi)終端颠通,先進(jìn)入到rheatrace.py 所在位置址晕,你下載的btrace代碼存放位置。
如:/Users/xiaoxiao/code/btrace/scripts/python/rheatrace/
btrace的命令和systrace是一樣的昂~~
python2 rheatrace.py -a com.xiaoxiao.demo -t 9 -o rheatrace_9s.html
已知問(wèn)題(官方說(shuō)的啊~):
僅支持 python2.7顿锰,請(qǐng)注意檢查 python 環(huán)境谨垃。
暫不支持 Windows。
僅支持采集主進(jìn)程的 trace 事件硼控。
需要外置存儲(chǔ)的讀寫(xiě)權(quán)限刘陶,因此您需要手動(dòng)賦予該權(quán)限。
如果您無(wú)法直接打開(kāi)輸出產(chǎn)物 systrace.html 牢撼,請(qǐng)用 perfetto 加載匙隔。ps:我就是這樣的,直接用瀏覽器打不開(kāi)熏版,只能用perfetto打開(kāi)牡直。perfetto也很好用,直接將文件拖進(jìn)去就行了纳决,而且它支持手勢(shì)放大縮小碰逸,不用像在瀏覽器那樣用按鍵控制。
perfetto打開(kāi)的大概長(zhǎng)這樣阔加。
筒子們饵史,咱們?cè)傩菹⒁幻腌妬?lái)~
三、 profiler使用和分析
Traceview是android平臺(tái)配備一個(gè)很好的性能分析的工具胜榔。它可以通過(guò)圖形化的方式讓我們了解我們要跟蹤的程序的性能胳喷,并且能具體到每個(gè)方法的執(zhí)行時(shí)間。但是目前Traceview 已棄用夭织。
如果使用 Android Studio3.2 或更高版本吭露,則應(yīng)改為使用 CPU Profiler.谷歌官網(wǎng)profiler工具使用文檔:
采樣方式有:
- 直接采樣,運(yùn)行APP的時(shí)候尊惰,打開(kāi)profile讲竿,點(diǎn)擊開(kāi)始采樣。
- 代碼采樣弄屡,通過(guò)代碼進(jìn)行精準(zhǔn)采樣题禀。
- 捕獲設(shè)備上的系統(tǒng)跟蹤記錄
1. 直接采樣
APP --》 Edit configurations --》 Profiling
--》 勾選 Start this recording on startup
--》 選擇Java/Kotlin Mothed Trace(全采樣) 或者 Java/Kotlin Mothed Sample(部分采樣,建議選擇這個(gè)膀捷,另一個(gè)太卡了)
采樣示例圖如下:
分析數(shù)據(jù)主要看主線程的迈嘹,就是上面的main模塊。
2.代碼采樣
在需要采樣的類(lèi)里面,通過(guò)以下代碼進(jìn)行采樣
val fileName = BAFFileUtil.getSDCardPath() + "test_trace_app_all"
Debug.startMethodTracing(fileName,800000000)
//startMethodTracing(String tracePath, int bufferSize) {
//tracePath:trace文件存放位置秀仲;
//bufferSize: trace文件最大支持的大小如果文件不設(shè)置大小融痛,最大默認(rèn)為8M,一般都不夠神僵。
//間隔采樣
//intervalUs 間隔時(shí)間雁刷,微秒
//startMethodTracingSampling(String tracePath, int bufferSize,int intervalUs)
Debug.startMethodTracingSampling(fileName,800000000,10)
//結(jié)束采樣
Debug.stopMethodTracing()
注意:
startMethodTracing 和 stopMethodTracing 需要成對(duì)出現(xiàn)
startMethodTracingSampling 和 stopMethodTracing 需要成對(duì)出現(xiàn)
3. 設(shè)備直接采樣
優(yōu)點(diǎn):方便 缺點(diǎn):不夠詳細(xì),很多東西沒(méi)采樣到
首次使用設(shè)置:
如果您是首次在測(cè)試設(shè)備上使用 System Tracing挑豌,或在設(shè)備的快捷設(shè)置面板中看不到 System Tracing 圖塊(手機(jī)采樣3.png)安券,請(qǐng)完成以下設(shè)置步驟:
啟用開(kāi)發(fā)者選項(xiàng)(如果尚未啟用此選項(xiàng))。
打開(kāi)開(kāi)發(fā)者選項(xiàng)設(shè)置屏幕氓英。
在調(diào)試部分中侯勉,選擇 System Tracing。此時(shí)會(huì)打開(kāi) System Tracing 應(yīng)用铝阐,其中顯示了應(yīng)用菜單址貌。
在應(yīng)用菜單中,啟用顯示“快捷設(shè)置”圖塊徘键,如圖 2 所示练对。系統(tǒng)會(huì)將 System Tracing 圖塊添加到快捷設(shè)置面板中。這樣就可以采樣了~
四吹害、啟動(dòng)優(yōu)化器
這個(gè)不算是工具啊螟凭,是一種啟動(dòng)框架,一般是通過(guò)有向無(wú)環(huán)圖構(gòu)建的異步啟動(dòng)框架它呀。感興趣的同學(xué)可以自己了解一下~
Alpha啟動(dòng)框架
android-startup
AppStartFaster
以上螺男,就說(shuō)到這里!
如果能幫到你是我的榮幸纵穿,如果有錯(cuò)誤歡迎指正下隧,歡迎留言~