Android 快速定位耗時方法

gerrard0898發表於2019-12-31

一、啟動耗時檢測

1、檢視Logcat

在Android Studio Logcat中過濾關鍵字“Displayed”,可以看到對應的Activity啟動耗時日誌。

2、adb shell

使用adb shell獲取應用的啟動時間

adb shell am start -W [packageName]/[AppstartActivity全路徑]

執行後會得到三個時間:ThisTime、TotalTime和WaitTime,詳情如下:

ThisTime 最後一個Activity啟動耗時。

TotalTime 所有Activity啟動耗時。

WaitTime AMS啟動Activity的總耗時。

一般檢視得到的TotalTime,即應用的啟動時間,包括建立程式 + Application初始化 + Activity初始化到介面顯示的過程。

特點:

  • 線下使用方便,不能帶到線上。
  • 非嚴謹、精確時間。

3、AOP(Aspect Oriented Programming)打點

面向切面程式設計,通過預編譯和執行期動態代理實現程式功能統一維護的一種技術。

作用

利用AOP可以對業務邏輯的各個部分進行隔離,從而使得業務邏輯各部分之間的耦合性降低,提高程式的可重用性,同時大大提高了開發效率。

AOP核心概念

1)、橫切關注點

對哪些方法進行攔截,攔截後怎麼處理。

2)、切面(Aspect

類是對物體特徵的抽象,切面就是對橫切關注點的抽象。

3)、連線點(JoinPoint)

被攔截到的點(方法、欄位、構造器)。

4)、切入點(PointCut)

對JoinPoint進行攔截的定義。

5)、通知(Advice)

攔截到JoinPoint後要執行的程式碼,分為前置、後置、環繞三種型別。

準備 首先,為了在Android使用AOP埋點需要引入AspectJ,在專案根目錄的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埋點實戰 JoinPoint一般定位在如下位置:

  • 函式呼叫
  • 獲取、設定變數
  • 類初始化 使用PointCut對我們指定的連線點進行攔截,通過Advice,就可以攔截到JoinPoint後要執行的程式碼。 Advice通常有以下幾種型別:
  • Before:PointCut之前執行
  • After:PointCut之後執行
  • Around:PointCut之前、之後分別執行

首先,我們舉一個小栗子:

@Before("execution(* android.app.Activity.on**(..))")
public void onActivityLifeCalled(JoinPoint joinPoint) throws Throwable {
...
}
複製程式碼

在execution中的是一個匹配規則,第一個*代表匹配任意的方法返回值,後面的語法程式碼匹配所有Activity中on開頭的方法。

處理Join Point的型別:

  • call:插入在函式體裡面
  • execution:插入在函式體外面

如何統計Application中的所有方法耗時?

@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));
    }
}
複製程式碼

注意

當Action為Before、After時,方法入參為JoinPoint。 當Action為Around時,方法入參為ProceedingPoint。

Around和Before、After的最大區別:

ProceedingPoint不同於JoinPoint,其提供了proceed方法執行目標方法。

總結AOP特性:

  • 無侵入性
  • 修改方便

強烈推薦結合第三節講解的Systrace工具使用,可以非常快速地定位到耗時方法,上線的時候,可以考慮遮蔽掉AOP功能。

//apply plugin: 'android-aspectjx'

二、耗時分析工具 — TraceView

使用方式

方式1

檢測開始程式碼處新增:

Debug.startMethodTracing();

檢測結束程式碼處新增:

Debug.stopMethodTracing();

使用adb pull將生成的**.trace檔案匯出到電腦,然後使用Android Studio的Profiler載入

方式2

開啟Profiler -> CPU -> 點選 Record -> 點選 Stop -> 檢視Profiler下方Top Down/Bottom Up 區域找出耗時的熱點方法。

Profile CPU

1、Trace types

Trace Java Methods

會記錄每個方法的時間、CPU資訊。對執行時效能影響較大。

Sample Java Methods

相比於Trace Java Methods會記錄每個方法的時間、CPU資訊,它會在應用的Java程式碼執行期間頻繁捕獲應用的呼叫堆疊,對執行時效能的影響比較小,能夠記錄更大的資料區域。

Sample C/C++ Functions

需部署到Android 8.0及以上裝置,內部使用simpleperf跟蹤應用的native程式碼,也可以命令列使用simpleperf。

Trace System Calls

檢查應用與系統資源的互動情況。 檢視所有核心的CPU瓶。 內部採用systrace,也可以使用systrace命令。

2、Event timeline 顯示應用程式中在其生命週期中轉換不同狀態的活動,如使用者互動、螢幕旋轉事件等。

3、CPU timeline 顯示應用程式實時CPU使用率、其它程式實時CPU使用率、應用程式使用的執行緒總數。

4、Thread activity timeline 列出應用程式程式中的每個執行緒,並使用了不同的顏色在其時間軸上指示其活動。

綠色:執行緒處於活動狀態或準備好使用CPU。 黃色:執行緒正等待IO操作。(重要) 灰色:執行緒正在睡眠,不消耗CPU時間。

Profile提供的檢查跟蹤資料視窗有四種

1、Call Chart

提供函式跟蹤資料的圖形表示形式。

水平軸:表示呼叫的時間段和時間。 垂直軸:顯示被呼叫方。 橙色:系統API。 綠色:應用自有方法 藍色:第三方API(包括Java API) 提示:右鍵點選Jump to source跳轉至指定函式。

Call Chart 是 Traceview 和 systrace 預設使用的展示方式。它按照應用程式的函式執行順序來展示,適合用於分析整個流程的呼叫。舉一個最簡單的例子,A 函式呼叫 B 函式,B 函式呼叫 C 函式,迴圈三次,就得到了下面的 Call Chart。

Android 快速定位耗時方法

Call Chart 就像給應用程式做一個心電圖,我們可以看到在這一段時間內,各個執行緒的具體工作,比如是否存線上程間的鎖、主執行緒是否存在長時間的 I/O 操作、是否存在空閒等。

2、Flame Chart

將具有相同呼叫方順序的完全相同的方法收集起來。

水平軸:執行每個方法的相對時間量。 垂直軸:顯示被呼叫方。 注意:看頂層的哪個函式佔據的寬度最大(平頂),可能存在效能問題。

Flame Chart 也就是大名鼎鼎的火焰圖。它跟 Call Chart 不同的是,Flame Chart 以一個全域性的視野來看待一段時間的呼叫分佈,它就像給應用程式拍 X 光片,可以很自然地把時間和空間兩個維度上的資訊融合在一張圖上。上面函式呼叫的例子,換成火焰圖的展示結果如下。

Android 快速定位耗時方法

當我們不想知道應用程式的整個呼叫流程,只想直觀看出哪些程式碼路徑花費的 CPU 時間較多時,火焰圖就是一個非常好的選擇。例如,之前我的一個反序列化實現非常耗時,通過火焰圖發現耗時最多的是大量 Java 字串的建立和拷貝,通過將核心實現轉為 Native,最終使效能提升了很多倍。

3、Top Down

遞迴呼叫列表,提供self、children、total時間和比率來表示被呼叫的函式資訊。 Flame Chart是Top Down列表資料的圖形化。

4、Bottom Up

展開函式會顯示其呼叫方。 按照消耗CPU時間由多到少的順序對函式排序。 注意點:

Wall Clock Time:程式執行時間。 Thread Time:CPU執行的時間。

TraceView小結

特點

  • 圖形的形式展示執行時間、呼叫棧等。
  • 資訊全面,包含所有執行緒。
  • 執行時開銷嚴重,整體都會變慢,得出的結果並不真實。
  • 找到最耗費時間的路徑:Flame Chart、Top Down。
  • 找到最耗費時間的節點:Bottom Up。

作用 主要做熱點分析,得到兩種資料:

  • 單次執行最耗時的方法。
  • 執行次數最多的方法。

三、耗時分析工具 — Systrace

使用方式:程式碼插樁

定義Trace靜態工廠類,將Trace.begainSection(),Trace.endSection()封裝成i、o方法,然後再在想要分析的方法前後進行插樁即可。

在命令列下執行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
複製程式碼

具體引數含義如下:

  • -t:指定統計時間為20s。
  • shced:cpu排程資訊。
  • gfx:圖形資訊。
  • view:檢視。
  • wm:視窗管理。
  • am:活動管理。
  • app:應用資訊。
  • webview:webview資訊。
  • -a:指定目標應用程式的包名。
  • -o:生成的systrace.html檔案。

如何檢視資料?

在UIThread一欄可以看到核心的系統方法時間區域和我們自己使用程式碼插樁捕獲的方法時間區域。

Systrace原理

在系統的一些關鍵鏈路(如SystemServcie、虛擬機器、Binder驅動)插入一些資訊(Label); 通過Label的開始和結束來確定某個核心過程的執行時間; 把這些Label資訊收集起來得到系統關鍵路徑的執行時間資訊,最後得到整個系統的執行效能資訊; Android Framework裡面一些重要的模組都插入了label資訊,使用者App中可以新增自定義的Lable。

Systrace小結

特性

結合Android核心的資料,生成Html報告。 系統版本越高,Android Framework中新增的系統可用Label就越多,能夠支援和分析的系統模組也就越多。 必須手動縮小範圍,會幫助你加速收斂問題的分析過程,進而快速地定位和解決問題。

作用

主要用於分析繪製效能方面的問題。 分析系統關鍵方法和應用方法耗時。

結合AOP,可以在方法的前後,非常方便地批量插入以下程式碼。最後從執行生成Html報告後,可以快速查詢出耗時的方法。

Trace.begainSection();
//your code
Trace.endSection();
複製程式碼

四、總結

Traceview 和 systrace 都是我們比較常用的排查卡頓的工具,從實現上這些工具分為兩個流派。

第一個流派是 instrument。獲取一段時間內所有函式的呼叫過程,可以通過分析這段時間內的函式呼叫流程,再進一步分析待優化的點。

第二個流派是 sample。有選擇性或者採用抽樣的方式觀察某些函式呼叫過程,可以通過這些有限的資訊推測出流程中的可疑點,然後再繼續細化分析。

Traceview

Traceview是吐槽的比較多的工具。它利用 Android Runtime 函式呼叫的 event 事件,將函式執行的耗時和呼叫關係寫入 trace 檔案中。

由此可見,Traceview 屬於 instrument 型別,它可以用來檢視整個過程有哪些函式呼叫,但是工具本身帶來的效能開銷過大,有時無法反映真實的情況。比如一個函式本身的耗時是 1 秒,開啟 Traceview 後可能會變成 5 秒,而且這些函式的耗時變化並不是成比例放大。

在 Android 5.0 之後,新增了startMethodTracingSampling方法,可以使用基於樣本的方式進行分析,以減少分析對執行時的效能影響。新增了 sample 型別後,就需要我們在開銷和資訊豐富度之間做好權衡。

systrace

systrace是 Android 4.1 新增的效能分析工具。我通常使用 systrace 跟蹤系統的 I/O 操作、CPU 負載、Surface 渲染、GC 等事件。

systrace 利用了 Linux 的ftrace除錯工具,相當於在系統各個關鍵位置都新增了一些效能探針,也就是在程式碼里加了一些效能監控的埋點。Android 在 ftrace 的基礎上封裝了atrace,並增加了更多特有的探針,例如 Graphics、Activity Manager、Dalvik VM、System Server 等。

systrace 工具只能監控特定系統呼叫的耗時情況,所以它是屬於 sample 型別,而且效能開銷非常低。但是它不支援應用程式程式碼的耗時分析,所以在使用時有一些侷限性。

由於系統預留了Trace.beginSection介面來監聽應用程式的呼叫耗時,那我們有沒有辦法在 systrace 上面自動增加應用程式的耗時分析呢?

劃重點了,我們可以通過編譯時給每個函式AOP插樁的方式來實現,也就是在重要函式的入口和出口分別增加Trace.beginSection和Trace.endSection。當然出於效能的考慮,我們會過濾大部分指令數比較少的函式,這樣就實現了在 systrace 基礎上增加應用程式耗時的監控。

通過這樣方式的好處有:

  • 可以看到整個流程系統和應用程式的呼叫流程。包括系統關鍵執行緒的函式呼叫,例如渲染耗時、執行緒鎖,GC 耗時等。
  • 效能損耗可以接受。由於過濾了大部分的短函式,而且沒有放大 I/O,所以整個執行耗時不到原來的兩倍,基本可以反映真實情況。

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,用來分析Native方法耗時。

選擇哪種工具,需要看具體的場景。我來彙總一下,如果需要分析 Native 程式碼的耗時,可以選擇 Simpleperf;如果想分析系統呼叫,可以選擇 systrace;如果想分析整個程式執行流程的耗時,可以選擇 Traceview 或者插樁版本的 systrace(優先推薦)。

相關文章