前言
性能優(yōu)化的過(guò)程分兩部分:
- 發(fā)現(xiàn)性能瓶頸
- 制定方案,解決性能問(wèn)題
解決性能問(wèn)題的方案需要具體情況具體分析甲棍,并沒(méi)有完全固定的路子简识,更多的是靠經(jīng)驗(yàn)的積累,本文不做涉及。但是發(fā)現(xiàn)性能瓶頸確實(shí)有著固定的方法七扰。本文主要介紹 如何找到性能瓶頸
奢赂。
如何找到性能瓶頸
常用的性能檢測(cè)工具是traceview,集成于 Android Device Monitor
中戳寸。從Android Studio3.0開(kāi)始呈驶, Android Device Monitor
被廢棄,取而代之的是 Android Profiler
疫鹊,其中提供了 Memory Prodiler
袖瞻、CPU Profiler
、Network Prodiler
三大功能拆吆。
內(nèi)存優(yōu)化(包括內(nèi)存泄漏)常用的是 MAT 或者 LeakCanary 聋迎,而 Memory Profiler 相當(dāng)于將 MAT 的簡(jiǎn)化版功能集成到 AS 中。相對(duì)的在性能優(yōu)化方面枣耀,CPU Profiler 相當(dāng)于將 traceview 的功能集成到了 AS 中霉晕。
所以,使用AS3.0之前版本的捞奕,可以使用traceview牺堰,而使用AS3.0以后版本的,除了traceview颅围,還可以選擇CPU Profiler伟葫。
如果想追蹤系統(tǒng)進(jìn)程的詳細(xì)數(shù)據(jù),以解決幀引起的界面卡頓等問(wèn)題院促,可以使用
systrace
筏养,本文不做涉及〕M兀可參考:
Perform on-device system tracing
systrace
traceview 使用方法
使用 traceview 需要首先使用 Debug
類進(jìn)行 插樁
渐溶,當(dāng)應(yīng)用執(zhí)行到被插樁的代碼時(shí)就會(huì)在手機(jī)sdcard中自動(dòng)生成 .trace
文件,之后使用 traceview 或者 AS(3.0以上版本)打開(kāi)文件即可弄抬。
一茎辐、插樁
插樁需要使用到 Debug
類,并且會(huì)在 sdcard 中生成 .trace
文件掂恕,所以你必須首先保證你的應(yīng)用具有寫外部存儲(chǔ)( WRITE_EXTERNAL_STORAGE
)的權(quán)限拖陆。
在想要跟蹤的代碼邏輯開(kāi)頭和結(jié)尾處分別插樁:
// Starts recording a trace log with the name you provide. For example, the
// following code tells the system to start recording a .trace file to the
// device with the name "sample.trace".
Debug.startMethodTracing("sample");
...
// The system begins buffering the generated trace data, until your
// application calls stopMethodTracing(), at which time it writes
// the buffered data to the output file.
Debug.stopMethodTracing();
生成的 .trace
文件會(huì)被保存在固定目錄下,與 getExternalFilesDir()
返回的目錄相同竹海,即 /sdcard/Android/data/[YOUR_PACKAGE_NAME]/files
下慕蔚。
請(qǐng)注意,如果您的應(yīng)用在未更改跟蹤日志名稱的情況下再次調(diào)用 startMethodTracing()斋配,則會(huì)覆蓋已保存至設(shè)備的現(xiàn)有日志孔飒。如果希望每次運(yùn)行都保存至不同的日志文件灌闺,可以使用如下代碼:
// Uses the SimpleDateFormat class to create a String with
// the current date and time.
SimpleDateFormat date =
new SimpleDateFormat("dd_MM_yyyy_hh_mm_ss");
String logDate = date.format(new Date());
// Applies the date and time to the name of the trace log.
Debug.startMethodTracing(
"sample-" + logDate);
如果系統(tǒng)在您調(diào)用 stopMethodTracing() 之前達(dá)到最大緩沖值,則會(huì)停止跟蹤并向管理中心發(fā)送通知坏瞄。 開(kāi)始和停止跟蹤的函數(shù)在您的整個(gè)應(yīng)用流程內(nèi)均有效桂对。 也就是說(shuō),您可以在 Activity 的 onCreate(Bundle) 函數(shù)中調(diào)用 startMethodTracing()鸠匀,在 Activity 的 onDestroy() 函數(shù)中調(diào)用 stopMethodTracing()蕉斜。
二、查看 .trace 文件
插好樁后缀棍,安裝應(yīng)用并運(yùn)行被檢測(cè)部分的功能宅此,然后就可以通過(guò) AS 或者 traceview 查看文件了。
使用 AS 查看
在AS中點(diǎn)擊 View - Tool Windows - Android File Explorer
打開(kāi) Android File Explorer :
在 /sdcard/Android/data/[YOUR_PACKAGE_NAME]/files
下即可找到生成的 .trace 文件爬范,雙擊文件即可打開(kāi)父腕。
將 .trace 文件保存至電腦,直接拖入AS窗口青瀑,也可直接打開(kāi)該視圖璧亮。
在打開(kāi)的視圖中,左上方可以選擇想要查看的線程斥难≈λ唬可以查看監(jiān)控期間指定線程運(yùn)行了多久、執(zhí)行了哪些方法哑诊、每個(gè)方法執(zhí)行了多久等等群扶。
其中有4個(gè)名詞需要解釋一下:
- Wall Clock Time:壁鐘時(shí)間,表示實(shí)際經(jīng)過(guò)的時(shí)間搭儒,即進(jìn)入某個(gè)方法到退出該方法的時(shí)間穷当,不考慮線程是活動(dòng)還是休眠狀態(tài)提茁。
- Thread time:線程時(shí)間淹禾,表示實(shí)際經(jīng)過(guò)的時(shí)間減去線程沒(méi)有消耗 CPU 資源(處于休眠)的時(shí)間部分。 對(duì)于任何給定函數(shù)茴扁,其線程時(shí)間始終少于或等于其壁鐘時(shí)間铃岔。 使用線程時(shí)間可以讓您更好地了解線程的實(shí)際 CPU 使用率中有多少是給定函數(shù)消耗的。
- Inclusive Time:方法執(zhí)行自己代碼的時(shí)間 + 執(zhí)行自己child方法的時(shí)間峭火。
- Exclusive Time:方法執(zhí)行自己代碼的時(shí)間毁习。
使用 traceview 查看
要使用 traceview 查看,需要首先將 .trace 文件保存到電腦:
adb pull /sdcard/Android/data/[YOUR_PACKAGE_NAME]/files/sample.trace D:\Documents\sample.trace
打開(kāi) Android Device Monitor
卖丸。AS3.0以前的版本纺且,就是LogCat所在的窗口,再切換一下tab頁(yè)即可稍浆。AS3.0以后载碌,進(jìn)入 android-sdk/tools/
路徑猜嘱,運(yùn)行以下命令:
monitor
雖然 Android Device Monitor 的 DDMS 也有 File Explorer ,但是未 root 的手機(jī)嫁艇,查看不到上述路徑朗伶,因此只能將 .trace 文件保存到電腦查看。
在 Android Device Monitor 中步咪,依次點(diǎn)擊 File - Open File
论皆,選擇 .trace 文件路徑即可打開(kāi):
內(nèi)容與AS打開(kāi)時(shí)類似,相差較大的主要是圖標(biāo)部分猾漫,沒(méi)有AS的 Call Chart
直觀形象点晴。
其中也有4個(gè)概念:
- Cpu Time:相當(dāng)于AS中的 Thread time。
- Real Time:相當(dāng)于AS中的Wall Clock Time悯周。
- Inclusive Time:同AS一樣觉鼻。
- Exclusive Time:同AS一樣。
使用 AS 查看還是使用 traceview 查看
這個(gè)就見(jiàn)仁見(jiàn)智了队橙,根據(jù)我個(gè)人使用的感覺(jué)來(lái)看坠陈,建議使用AS查看。原因有二:
- AS更簡(jiǎn)單捐康。不需要單獨(dú)打開(kāi)ADM仇矾,更不需要將 .trace 文件保存到電腦。
- AS的調(diào)用圖( Call Chart )更加直觀解总,cpu時(shí)間的消耗一目了然贮匕。
Call Chart 的水平軸表示函數(shù)調(diào)用(或調(diào)用方)的時(shí)間段和時(shí)間,并沿垂直軸顯示其被調(diào)用者花枫。 下圖展示了一個(gè)調(diào)用圖表示例刻盐,并描繪了給定函數(shù)的 self time、children time 以及總時(shí)間的概念劳翰。
最后需要注意一點(diǎn)敦锌,跟蹤分析過(guò)程中,應(yīng)用的運(yùn)行速度會(huì)減慢佳簸。所以乙墙,通過(guò) traceview 得到的分析數(shù)據(jù)并不能精確反應(yīng)某個(gè)方法在實(shí)際執(zhí)行時(shí)的絕對(duì)時(shí)間。關(guān)于這一點(diǎn)生均,在最后的注意事項(xiàng)中再做詳細(xì)分析听想。
Google還提供了基于樣本的分析方式,以減少分析對(duì)運(yùn)行時(shí)性能的影響马胧。要啟用樣本分析汉买,需調(diào)用
Debug.startMethodTracingSampling()
方法(而非Debug.startMethodTracing()
方法)。系統(tǒng)會(huì)定期收集樣本佩脊,直至調(diào)用stopMethodTracing()
蛙粘。
CPU Profiler 使用方法
使用 CPU Profiler 進(jìn)行函數(shù)跟蹤比 traceview 更簡(jiǎn)單朽色。不需要做任何代碼上的植入,下面做一個(gè)簡(jiǎn)單的介紹:
首先组题,通過(guò) View - Tool Windows - Android Profiler
打開(kāi) Android Profiler 葫男。手機(jī)連接電腦后運(yùn)行應(yīng)用,在 Android Profiler 中會(huì)看到以下視圖:
左上角可以選擇設(shè)備和進(jìn)程崔列,點(diǎn)擊 CPU 區(qū)域梢褐,即可進(jìn)入CPU Profiler視圖:
左上角可以選擇跟蹤模式:
- Sampled:按默認(rèn)采樣率捕獲應(yīng)用的調(diào)用堆棧。該模式的固有問(wèn)題是赵讯,如果應(yīng)用在一次捕獲后進(jìn)入一個(gè)函數(shù)并在下一次捕獲前退出該函數(shù)盈咳,則分析器不會(huì)記錄該函數(shù)調(diào)用。如果對(duì)此類生命周期很短的跟蹤函數(shù)感興趣边翼,可以使用“Instrumented”跟蹤鱼响。
- Instrumented:以在每個(gè)函數(shù)調(diào)用的開(kāi)始和結(jié)束時(shí)記錄時(shí)間戳。 分析比較時(shí)間戳组底,以生成函數(shù)跟蹤數(shù)據(jù)丈积。 需要注意的是,設(shè)置與函數(shù)關(guān)聯(lián)的開(kāi)銷會(huì)影響運(yùn)行時(shí)性能债鸡,甚至分析數(shù)據(jù)江滨,對(duì)于生命周期相對(duì)較短的函數(shù),這一點(diǎn)更為明顯厌均。 此外唬滑,如果應(yīng)用短時(shí)間內(nèi)執(zhí)行大量函數(shù),則分析器可能會(huì)迅速超出它的文件大小限制棺弊,且不能再記錄更多跟蹤數(shù)據(jù)晶密。
-
Edit configurations:自定義采樣率。與 traceview 中的
Debug.startMethodTracingSampling()
類似模她。
.trace
文件的大小是有限制的稻艰。對(duì)于給定錄制,當(dāng)分析器到達(dá)該限制時(shí)缝驳,AS 將停止收集新數(shù)據(jù)(不過(guò)连锯,這不會(huì)停止記錄)归苍。 在執(zhí)行“Instrumented”跟蹤時(shí)用狱,這種情況通常會(huì)更快發(fā)生,因?yàn)榕c“Sampled”跟蹤相比拼弃,此類跟蹤在較短時(shí)間里會(huì)收集更多數(shù)據(jù)夏伊。如果你使用的是Android 8.0(API 26)或更高版本的設(shè)備,則對(duì)于跟蹤數(shù)據(jù)的文件大小沒(méi)有限制吻氧,此值可忽略溺忧。不過(guò)咏连,你仍需留意每次記錄后設(shè)備收集了多少數(shù)據(jù),因?yàn)?AS 可能難以解析大型跟蹤文件鲁森。
點(diǎn)擊上方的“開(kāi)始錄制”按鈕祟滴,然后在應(yīng)用中操作執(zhí)行被追蹤的功能,結(jié)束后再點(diǎn)擊“停止錄制”按鈕歌溉。CPU Profiler 會(huì)自動(dòng)開(kāi)始分析并生成數(shù)據(jù)垄懂。
以上就是 CPU Profiler
和 traceview
的使用方法。至于如何制定優(yōu)化方案痛垛,就不展開(kāi)了草慧,并沒(méi)有完全固定的路子。就我本例的 onRebuild()
方法而言匙头,是針對(duì)耗時(shí)的Contact構(gòu)造過(guò)程做了并行處理漫谷,將上百個(gè)有序的構(gòu)造過(guò)程平分到5個(gè)線程中并發(fā)執(zhí)行,然后再按順序合并數(shù)據(jù)到一個(gè)線程中蹂析。最終 onRebuild()
執(zhí)行速度從15秒提升到了2.5秒舔示,對(duì)我來(lái)說(shuō)已經(jīng)夠用了。
重要注意事項(xiàng)
無(wú)論是使用 traceview 還是 CPU Profiler 進(jìn)行函數(shù)跟蹤电抚,有一點(diǎn)需要注意:跟蹤分析過(guò)程中斩郎,應(yīng)用的運(yùn)行速度會(huì)減慢。所以喻频,分析數(shù)據(jù)并不能精確反應(yīng)某個(gè)方法在實(shí)際執(zhí)行時(shí)的絕對(duì)時(shí)間缩宜。下面是我在優(yōu)化項(xiàng)目中的 onRebuild(boolean)
方法時(shí),記錄的4組數(shù)據(jù)甥温,讓我們來(lái)對(duì)比一下:
- 實(shí)際執(zhí)行時(shí)間:不啟用分析模式锻煌,正常運(yùn)行狀態(tài)下通過(guò)打印日志得到的實(shí)際執(zhí)行時(shí)間。
- Profiler統(tǒng)計(jì)時(shí)間:使用 CPU Profiler 分析獲得的執(zhí)行時(shí)間姻蚓。
- traceview統(tǒng)計(jì)時(shí)間:通過(guò)分析 traceview 產(chǎn)生的 .trace 文件宋梧,從中獲得的執(zhí)行時(shí)間。
- traceview實(shí)際時(shí)間:使用 traceview 的情況下狰挡,通過(guò)打印日志得到的實(shí)際執(zhí)行時(shí)間捂龄。
為什么針對(duì) traceview 會(huì)例舉兩個(gè)時(shí)間呢?這是因?yàn)闇y(cè)試過(guò)程中發(fā)現(xiàn) traceview 自動(dòng)分析出來(lái)的時(shí)間比
實(shí)際執(zhí)行時(shí)間
不僅沒(méi)有慢加叁,反而快了很多倦沧,疑惑下又在啟用 traceview 的情況下通過(guò)以下代碼測(cè)算了一下實(shí)際的時(shí)間,這個(gè)倒是真的比實(shí)際執(zhí)行時(shí)間
慢了它匕。
Debug.startMethodTracing("smssdk_onrebuild");
long curr = System.currentTimeMillis();
onRebuild(true);
Log.d(TAG, "onRebuild lasts: " + (System.currentTimeMillis() - curr));
Debug.stopMethodTracing();
實(shí)際執(zhí)行時(shí)間 | Profiler統(tǒng)計(jì)時(shí)間 | traceview統(tǒng)計(jì)時(shí)間 | traceview實(shí)際時(shí)間 |
---|---|---|---|
15s | 35s | 3.5s | 64s |
從上表數(shù)據(jù)可見(jiàn)展融,無(wú)論是 CPU Profiler 還是 traceview ,統(tǒng)計(jì)出來(lái)的時(shí)間都不能準(zhǔn)確代表實(shí)際執(zhí)行時(shí)間豫柬。更甚者告希,traceview自動(dòng)分析出來(lái)的數(shù)據(jù)也與traceview跟蹤模式下實(shí)際的時(shí)間有巨大差別扑浸,關(guān)于這一點(diǎn),我沒(méi)找到詳細(xì)的解釋燕偶,如果有人知道喝噪,還望不吝賜教。
既然跟蹤分析得到的時(shí)間都不能表示實(shí)際的時(shí)間指么,那么這些數(shù)據(jù)是不是沒(méi)用呢仙逻?當(dāng)然不是!它們至少在以下兩個(gè)方面具有價(jià)值:
- 在一次檢測(cè)得到的數(shù)據(jù)中涧尿,線程內(nèi)各個(gè)方法執(zhí)行所耗時(shí)間在整個(gè)線程執(zhí)行時(shí)間中所占比例具有一定參考價(jià)值系奉。占比高的方法當(dāng)是優(yōu)化的重點(diǎn)目標(biāo)。
- 優(yōu)化前后兩次檢測(cè)得到的數(shù)據(jù)姑廉,有比較價(jià)值缺亮,以確認(rèn)優(yōu)化方案是否真的生效。
通過(guò)這些工具跟蹤函數(shù)桥言,也只能做一個(gè)相對(duì)的參考萌踱,并不能完全正確的反應(yīng)函數(shù)的執(zhí)行性能。比如我通過(guò) CPU Profiler 獲得的
onRebuild()
方法的分析數(shù)據(jù)顯示号阿,整個(gè)執(zhí)行過(guò)程中 Contact 的構(gòu)造方法占了60%左右并鸵,Contact.toString() 方法占了40%左右,但實(shí)際上在 onRebuild() 方法消耗的15秒中扔涧,Contact.toString() 只消耗了百毫秒級(jí)园担,而九成以上時(shí)間都被其構(gòu)造方法消耗了,說(shuō)明 CPU Profiler 的監(jiān)控過(guò)程對(duì) Contact.toString() 的性能產(chǎn)生了更大的影響枯夜。
而同樣的問(wèn)題卻并沒(méi)有出現(xiàn)在 traceview 的分析結(jié)果中弯汰。
請(qǐng)注意,CPU Profiler 和 traceview 不能同時(shí)使用湖雹,如果代碼中植入了插樁的代碼咏闪,則有可能導(dǎo)致 CPU Profiler 無(wú)法正常開(kāi)始或停止錄制。
traceview 和 CPU Profiler 的對(duì)比
從用法上來(lái)看摔吏,traceview 比 CPU Profiler 稍微復(fù)雜一點(diǎn)鸽嫂。類似于MAT需要首先獲取 .hprof
堆轉(zhuǎn)儲(chǔ)文件,traceview 也要首先獲取 .trace
文件征讲,然后使用traceview分析該文件据某。而 CPU Profiler 則可以直接對(duì)應(yīng)用進(jìn)行分析。
從最終生成的圖表上來(lái)看稳诚,CPU Profiler 生成的圖表有 Call Chart
哗脖、Flame Chart
,它們可以非常形象的表示出線程內(nèi)執(zhí)行了哪些函數(shù)扳还,函數(shù)的執(zhí)行時(shí)間才避,調(diào)用棧等等,一目了然氨距,而且在任意函數(shù)上點(diǎn)擊右鍵桑逝,可以直接跳轉(zhuǎn)至對(duì)應(yīng)的代碼,非常方便俏让,在這一點(diǎn)上楞遏,相對(duì)于 traceview 要優(yōu)秀。
參考文獻(xiàn)
Inspect threads and methods traces
Inspect trace logs with Traceview
Generate trace logs by instrumenting your app