一、啟動(dòng)耗時(shí)檢測(cè)
1、查看Logcat
在Android Studio Logcat中過(guò)濾關(guān)鍵字“Displayed”,可以看到對(duì)應(yīng)的Activity啟動(dòng)耗時(shí)日志。
2、adb shell
使用adb shell獲取應(yīng)用的啟動(dòng)時(shí)間
adb shell am start -W [packageName]/[AppstartActivity全路徑]
執(zhí)行后會(huì)得到三個(gè)時(shí)間:ThisTime、TotalTime和WaitTime,詳情如下:
ThisTime 最后一個(gè)Activity啟動(dòng)耗時(shí)。
TotalTime 所有Activity啟動(dòng)耗時(shí)。
WaitTime AMS啟動(dòng)Activity的總耗時(shí)。
一般查看得到的TotalTime,即應(yīng)用的啟動(dòng)時(shí)間,包括創(chuàng)建進(jìn)程 + Application初始化 + Activity初始化到界面顯示的過(guò)程。
特點(diǎn):
- 線下使用方便,不能帶到線上。
- 非嚴(yán)謹(jǐn)、精確時(shí)間。
3、AOP(Aspect Oriented Programming)打點(diǎn)
面向切面編程,通過(guò)預(yù)編譯和運(yùn)行期動(dòng)態(tài)代理實(shí)現(xiàn)程序功能統(tǒng)一維護(hù)的一種技術(shù)。
作用
利用AOP可以對(duì)業(yè)務(wù)邏輯的各個(gè)部分進(jìn)行隔離,從而使得業(yè)務(wù)邏輯各部分之間的耦合性降低,提高程序的可重用性,同時(shí)大大提高了開發(fā)效率。
AOP核心概念
1)、橫切關(guān)注點(diǎn)
對(duì)哪些方法進(jìn)行攔截,攔截后怎么處理。
2)、切面(Aspect)
類是對(duì)物體特征的抽象,切面就是對(duì)橫切關(guān)注點(diǎn)的抽象。
3)、連接點(diǎn)(JoinPoint)
被攔截到的點(diǎn)(方法、字段、構(gòu)造器)。
4)、切入點(diǎn)(PointCut)
對(duì)JoinPoint進(jìn)行攔截的定義。
5)、通知(Advice)
攔截到JoinPoint后要執(zhí)行的代碼,分為前置、后置、環(huán)繞三種類型。
準(zhǔn)備 首先,為了在Android使用AOP埋點(diǎn)需要引入AspectJ,在項(xiàng)目根目錄的build.gradle下加入:
classpath 'com.hujiang.aspectjx:gradle-android-plugin- aspectjx:2.0.0'
然后,在app目錄下的build.gradle下加入:
apply plugin: 'android-aspectjx'
implement 'org.aspectj:aspectjrt:1.8.+'
AOP埋點(diǎn)實(shí)戰(zhàn) JoinPoint一般定位在如下位置:
- 函數(shù)調(diào)用
- 獲取、設(shè)置變量
- 類初始化 使用PointCut對(duì)我們指定的連接點(diǎn)進(jìn)行攔截,通過(guò)Advice,就可以攔截到JoinPoint后要執(zhí)行的代碼。 Advice通常有以下幾種類型:
- Before:PointCut之前執(zhí)行
- After:PointCut之后執(zhí)行
- Around:PointCut之前、之后分別執(zhí)行
首先,我們舉一個(gè)小栗子:
@Before("execution(* android.app.Activity.on**(..))")
public void onActivityLifeCalled(JoinPoint joinPoint) throws Throwable {
...
}
在execution中的是一個(gè)匹配規(guī)則,第一個(gè)*代表匹配任意的方法返回值,后面的語(yǔ)法代碼匹配所有Activity中on開頭的方法。
處理Join Point的類型:
- call:插入在函數(shù)體里面
- execution:插入在函數(shù)體外面
如何統(tǒng)計(jì)Application中的所有方法耗時(shí)?
@Aspect
public class ApplicationAop {
@Around("call (* com.application.BaseApplication.**(..))")
public void getTime(ProceedingJoinPoint joinPoint) {
Signature signature = joinPoint.getSignature();
String name = signature.toShortString();
long time = System.currentTimeMillis();
try {
joinPoint.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
Log.i(TAG, name + " cost" + (System.currentTimeMillis() - time));
}
}
注意
當(dāng)Action為Before、After時(shí),方法入?yún)镴oinPoint。 當(dāng)Action為Around時(shí),方法入?yún)镻roceedingPoint。
Around和Before、After的最大區(qū)別:
ProceedingPoint不同于JoinPoint,其提供了proceed方法執(zhí)行目標(biāo)方法。
總結(jié)AOP特性:
- 無(wú)侵入性
- 修改方便
強(qiáng)烈推薦結(jié)合第三節(jié)講解的Systrace工具使用,可以非??焖俚囟ㄎ坏胶臅r(shí)方法,上線的時(shí)候,可以考慮屏蔽掉AOP功能。
//apply plugin: 'android-aspectjx'
二、耗時(shí)分析工具 — TraceView
使用方式
方式1
檢測(cè)開始代碼處添加:
Debug.startMethodTracing();
檢測(cè)結(jié)束代碼處添加:
Debug.stopMethodTracing();
使用adb pull將生成的**.trace文件導(dǎo)出到電腦,然后使用Android Studio的Profiler加載
方式2
打開Profiler -> CPU -> 點(diǎn)擊 Record -> 點(diǎn)擊 Stop -> 查看Profiler下方Top Down/Bottom Up 區(qū)域找出耗時(shí)的熱點(diǎn)方法。
Profile CPU
1、Trace types
Trace Java Methods
會(huì)記錄每個(gè)方法的時(shí)間、CPU信息。對(duì)運(yùn)行時(shí)性能影響較大。
Sample Java Methods
相比于Trace Java Methods會(huì)記錄每個(gè)方法的時(shí)間、CPU信息,它會(huì)在應(yīng)用的Java代碼執(zhí)行期間頻繁捕獲應(yīng)用的調(diào)用堆棧,對(duì)運(yùn)行時(shí)性能的影響比較小,能夠記錄更大的數(shù)據(jù)區(qū)域。
Sample C/C++ Functions
需部署到Android 8.0及以上設(shè)備,內(nèi)部使用simpleperf跟蹤應(yīng)用的native代碼,也可以命令行使用simpleperf。
Trace System Calls
檢查應(yīng)用與系統(tǒng)資源的交互情況。 查看所有核心的CPU瓶。 內(nèi)部采用systrace,也可以使用systrace命令。
2、Event timeline 顯示應(yīng)用程序中在其生命周期中轉(zhuǎn)換不同狀態(tài)的活動(dòng),如用戶交互、屏幕旋轉(zhuǎn)事件等。
3、CPU timeline 顯示應(yīng)用程序?qū)崟r(shí)CPU使用率、其它進(jìn)程實(shí)時(shí)CPU使用率、應(yīng)用程序使用的線程總數(shù)。
4、Thread activity timeline 列出應(yīng)用程序進(jìn)程中的每個(gè)線程,并使用了不同的顏色在其時(shí)間軸上指示其活動(dòng)。
綠色:線程處于活動(dòng)狀態(tài)或準(zhǔn)備好使用CPU。 黃色:線程正等待IO操作。(重要) 灰色:線程正在睡眠,不消耗CPU時(shí)間。
Profile提供的檢查跟蹤數(shù)據(jù)窗口有四種
1、Call Chart
提供函數(shù)跟蹤數(shù)據(jù)的圖形表示形式。
水平軸:表示調(diào)用的時(shí)間段和時(shí)間。 垂直軸:顯示被調(diào)用方。 橙色:系統(tǒng)API。 綠色:應(yīng)用自有方法 藍(lán)色:第三方API(包括Java API) 提示:右鍵點(diǎn)擊Jump to source跳轉(zhuǎn)至指定函數(shù)。
Call Chart 是 Traceview 和 systrace 默認(rèn)使用的展示方式。它按照應(yīng)用程序的函數(shù)執(zhí)行順序來(lái)展示,適合用于分析整個(gè)流程的調(diào)用。舉一個(gè)最簡(jiǎn)單的例子,A 函數(shù)調(diào)用 B 函數(shù),B 函數(shù)調(diào)用 C 函數(shù),循環(huán)三次,就得到了下面的 Call Chart。

Call Chart 就像給應(yīng)用程序做一個(gè)心電圖,我們可以看到在這一段時(shí)間內(nèi),各個(gè)線程的具體工作,比如是否存在線程間的鎖、主線程是否存在長(zhǎng)時(shí)間的 I/O 操作、是否存在空閑等。
2、Flame Chart
將具有相同調(diào)用方順序的完全相同的方法收集起來(lái)。
水平軸:執(zhí)行每個(gè)方法的相對(duì)時(shí)間量。 垂直軸:顯示被調(diào)用方。 注意:看頂層的哪個(gè)函數(shù)占據(jù)的寬度最大(平頂),可能存在性能問(wèn)題。
Flame Chart 也就是大名鼎鼎的火焰圖。它跟 Call Chart 不同的是,F(xiàn)lame Chart 以一個(gè)全局的視野來(lái)看待一段時(shí)間的調(diào)用分布,它就像給應(yīng)用程序拍 X 光片,可以很自然地把時(shí)間和空間兩個(gè)維度上的信息融合在一張圖上。上面函數(shù)調(diào)用的例子,換成火焰圖的展示結(jié)果如下。

當(dāng)我們不想知道應(yīng)用程序的整個(gè)調(diào)用流程,只想直觀看出哪些代碼路徑花費(fèi)的 CPU 時(shí)間較多時(shí),火焰圖就是一個(gè)非常好的選擇。例如,之前我的一個(gè)反序列化實(shí)現(xiàn)非常耗時(shí),通過(guò)火焰圖發(fā)現(xiàn)耗時(shí)最多的是大量 Java 字符串的創(chuàng)建和拷貝,通過(guò)將核心實(shí)現(xiàn)轉(zhuǎn)為 Native,最終使性能提升了很多倍。
3、Top Down
遞歸調(diào)用列表,提供self、children、total時(shí)間和比率來(lái)表示被調(diào)用的函數(shù)信息。 Flame Chart是Top Down列表數(shù)據(jù)的圖形化。
4、Bottom Up
展開函數(shù)會(huì)顯示其調(diào)用方。 按照消耗CPU時(shí)間由多到少的順序?qū)瘮?shù)排序。 注意點(diǎn):
Wall Clock Time:程序執(zhí)行時(shí)間。 Thread Time:CPU執(zhí)行的時(shí)間。
TraceView小結(jié)
特點(diǎn)
- 圖形的形式展示執(zhí)行時(shí)間、調(diào)用棧等。
- 信息全面,包含所有線程。
- 運(yùn)行時(shí)開銷嚴(yán)重,整體都會(huì)變慢,得出的結(jié)果并不真實(shí)。
- 找到最耗費(fèi)時(shí)間的路徑:Flame Chart、Top Down。
- 找到最耗費(fèi)時(shí)間的節(jié)點(diǎn):Bottom Up。
作用 主要做熱點(diǎn)分析,得到兩種數(shù)據(jù):
- 單次執(zhí)行最耗時(shí)的方法。
- 執(zhí)行次數(shù)最多的方法。
三、耗時(shí)分析工具 — Systrace
使用方式:代碼插樁
定義Trace靜態(tài)工廠類,將Trace.begainSection(),Trace.endSection()封裝成i、o方法,然后再在想要分析的方法前后進(jìn)行插樁即可。
在命令行下執(zhí)行systrace.py腳本:
python /Users/yourname-xxx/Library/Android/sdk/platform-tools/systrace/systrace.py -t 20 sched gfx view wm am app webview -a "com.mypackageName" -o ~/Documents/systrace_data/start_1.html
具體參數(shù)含義如下:
- -t:指定統(tǒng)計(jì)時(shí)間為20s。
- shced:cpu調(diào)度信息。
- gfx:圖形信息。
- view:視圖。
- wm:窗口管理。
- am:活動(dòng)管理。
- app:應(yīng)用信息。
- webview:webview信息。
- -a:指定目標(biāo)應(yīng)用程序的包名。
- -o:生成的systrace.html文件。
如何查看數(shù)據(jù)?
在UIThread一欄可以看到核心的系統(tǒng)方法時(shí)間區(qū)域和我們自己使用代碼插樁捕獲的方法時(shí)間區(qū)域。
Systrace原理
在系統(tǒng)的一些關(guān)鍵鏈路(如SystemServcie、虛擬機(jī)、Binder驅(qū)動(dòng))插入一些信息(Label); 通過(guò)Label的開始和結(jié)束來(lái)確定某個(gè)核心過(guò)程的執(zhí)行時(shí)間; 把這些Label信息收集起來(lái)得到系統(tǒng)關(guān)鍵路徑的運(yùn)行時(shí)間信息,最后得到整個(gè)系統(tǒng)的運(yùn)行性能信息; Android Framework里面一些重要的模塊都插入了label信息,用戶App中可以添加自定義的Lable。
Systrace小結(jié)
特性
結(jié)合Android內(nèi)核的數(shù)據(jù),生成Html報(bào)告。 系統(tǒng)版本越高,Android Framework中添加的系統(tǒng)可用Label就越多,能夠支持和分析的系統(tǒng)模塊也就越多。 必須手動(dòng)縮小范圍,會(huì)幫助你加速收斂問(wèn)題的分析過(guò)程,進(jìn)而快速地定位和解決問(wèn)題。
作用
主要用于分析繪制性能方面的問(wèn)題。 分析系統(tǒng)關(guān)鍵方法和應(yīng)用方法耗時(shí)。
結(jié)合AOP,可以在方法的前后,非常方便地批量插入以下代碼。最后從運(yùn)行生成Html報(bào)告后,可以快速查找出耗時(shí)的方法。
Trace.begainSection();
//your code
Trace.endSection();
四 插件MethodTraceMan
該插件,是可配置式地插樁,可視化界面查看耗時(shí),支持搜索、排序。一旦集成,小白也能通過(guò)界面找出耗時(shí)方法。

五、總結(jié)
Traceview 和 systrace 都是我們比較常用的排查卡頓的工具,從實(shí)現(xiàn)上這些工具分為兩個(gè)流派。
第一個(gè)流派是 instrument。獲取一段時(shí)間內(nèi)所有函數(shù)的調(diào)用過(guò)程,可以通過(guò)分析這段時(shí)間內(nèi)的函數(shù)調(diào)用流程,再進(jìn)一步分析待優(yōu)化的點(diǎn)。
第二個(gè)流派是 sample。有選擇性或者采用抽樣的方式觀察某些函數(shù)調(diào)用過(guò)程,可以通過(guò)這些有限的信息推測(cè)出流程中的可疑點(diǎn),然后再繼續(xù)細(xì)化分析。
Traceview
Traceview是吐槽的比較多的工具。它利用 Android Runtime 函數(shù)調(diào)用的 event 事件,將函數(shù)運(yùn)行的耗時(shí)和調(diào)用關(guān)系寫入 trace 文件中。
由此可見,Traceview 屬于 instrument 類型,它可以用來(lái)查看整個(gè)過(guò)程有哪些函數(shù)調(diào)用,但是工具本身帶來(lái)的性能開銷過(guò)大,有時(shí)無(wú)法反映真實(shí)的情況。比如一個(gè)函數(shù)本身的耗時(shí)是 1 秒,開啟 Traceview 后可能會(huì)變成 5 秒,而且這些函數(shù)的耗時(shí)變化并不是成比例放大。
在 Android 5.0 之后,新增了startMethodTracingSampling方法,可以使用基于樣本的方式進(jìn)行分析,以減少分析對(duì)運(yùn)行時(shí)的性能影響。新增了 sample 類型后,就需要我們?cè)陂_銷和信息豐富度之間做好權(quán)衡。
systrace
systrace是 Android 4.1 新增的性能分析工具。我通常使用 systrace 跟蹤系統(tǒng)的 I/O 操作、CPU 負(fù)載、Surface 渲染、GC 等事件。
systrace 利用了 Linux 的ftrace調(diào)試工具,相當(dāng)于在系統(tǒng)各個(gè)關(guān)鍵位置都添加了一些性能探針,也就是在代碼里加了一些性能監(jiān)控的埋點(diǎn)。Android 在 ftrace 的基礎(chǔ)上封裝了atrace,并增加了更多特有的探針,例如 Graphics、Activity Manager、Dalvik VM、System Server 等。
systrace 工具只能監(jiān)控特定系統(tǒng)調(diào)用的耗時(shí)情況,所以它是屬于 sample 類型,而且性能開銷非常低。但是它不支持應(yīng)用程序代碼的耗時(shí)分析,所以在使用時(shí)有一些局限性。
由于系統(tǒng)預(yù)留了Trace.beginSection接口來(lái)監(jiān)聽?wèi)?yīng)用程序的調(diào)用耗時(shí),那我們有沒(méi)有辦法在 systrace 上面自動(dòng)增加應(yīng)用程序的耗時(shí)分析呢?
劃重點(diǎn)了,我們可以通過(guò)編譯時(shí)給每個(gè)函數(shù)AOP插樁的方式來(lái)實(shí)現(xiàn),也就是在重要函數(shù)的入口和出口分別增加Trace.beginSection和Trace.endSection。當(dāng)然出于性能的考慮,我們會(huì)過(guò)濾大部分指令數(shù)比較少的函數(shù),這樣就實(shí)現(xiàn)了在 systrace 基礎(chǔ)上增加應(yīng)用程序耗時(shí)的監(jiān)控。
通過(guò)這樣方式的好處有:
- 可以看到整個(gè)流程系統(tǒng)和應(yīng)用程序的調(diào)用流程。包括系統(tǒng)關(guān)鍵線程的函數(shù)調(diào)用,例如渲染耗時(shí)、線程鎖,GC 耗時(shí)等。
- 性能損耗可以接受。由于過(guò)濾了大部分的短函數(shù),而且沒(méi)有放大 I/O,所以整個(gè)運(yùn)行耗時(shí)不到原來(lái)的兩倍,基本可以反映真實(shí)情況。
Profiler
在 Android Studio 3.2 的 Profiler 中直接集成了幾種性能分析工具,其中:
- Sample Java Methods 的功能類似于 Traceview 的 sample 類型。
- Trace Java Methods 的功能類似于 Traceview 的 instrument 類型。
- Trace System Calls 的功能類似于 systrace。
- SampleNative (API Level 26+) 的功能類似于 Simpleperf,用來(lái)分析Native方法耗時(shí)。
選擇哪種工具,需要看具體的場(chǎng)景。我來(lái)匯總一下,如果需要分析 Native 代碼的耗時(shí),可以選擇 Simpleperf;如果想分析系統(tǒng)調(diào)用,可以選擇 systrace;如果想分析整個(gè)程序執(zhí)行流程的耗時(shí),可以選擇 Traceview 或者插樁版本的 systrace(優(yōu)先推薦)。