分析應用程式啟動

語言: CN / TW / HK

一旦我們建立了觸發應用程式緩慢啟動的指標和場景,下一步就是提高效能。

要了解是什麼導致應用程式啟動緩慢,我們需要對其進行分析。 Android Studio 提供了幾種型別的分析器錄製配置:

Trace System Calls(又名 systrace、perfetto):對執行時的影響很小,非常有助於瞭解應用程式如何與系統和 CPU 互動,但不瞭解應用程式 VM 內部發生的 Java 方法呼叫。

Sample C/C++ Functions(又名 Simpleperf):我不感興趣,我處理的應用程式執行的位元組碼比本機程式碼多得多。 在 Q+ 上,這現在也應該以低開銷的方式對 Java 堆疊進行取樣,但我還沒有設法讓它工作。

Trace Java Methods:這會捕獲所有 VM 方法呼叫,這些呼叫引入瞭如此多的開銷,結果沒有多大意義。

Sample Java Methods:開銷比跟蹤少,但顯示了 VM 內部發生的 Java 方法呼叫。 這是我在分析應用程式啟動時的首選選項。

在應用程式啟動時開始錄製

Android Studio profiler 有通過連線到已經執行的程序來啟動跟蹤的 UI,但沒有明顯的方式在應用程式啟動時開始記錄。

該選項存在但隱藏在應用程式的 run configuration:在 profiling 選項卡中選中啟動時啟動此記錄。

然後通過 Run > Profile app 部署應用程式。

分析 release builds

Android 開發人員通常在日常工作中使用除錯構建型別,除錯構建通常包括 LeakCanary 等額外庫等。

開發人員應該分析釋出版本而不是除錯版本,以確保他們正在解決客戶面臨的實際問題。

不幸的是,釋出版本是不可除錯的,因此 Android 分析器無法記錄釋出版本的跟蹤。

以下是解決該問題的幾個選項。

1. 建立可除錯的釋出版本

我們可以暫時使我們的釋出構建可除錯,或者建立一個新的釋出構建型別來進行分析。 android { buildTypes { release { debuggable true // ... } } }

如果 APK 是可除錯的,庫和 Android 框架程式碼通常會有不同的行為。 ART 禁用了許多優化以啟用連線偵錯程式,這會顯著且不可預測地影響效能。因此該解決方案並不理想。

2. 在有 root 裝置上除錯裝置

Root 裝置允許 Android Studio 分析器記錄不可除錯構建的跟蹤。

通常不建議在模擬器上進行分析 - 每個系統元件的效能都會不同(cpu 速度、快取大小、磁碟效能),因此“優化”實際上可以通過將工作轉移到手機上較慢的東西來使事情變慢 . 如果您沒有可用的 root 物理裝置,您可以建立一個沒有 Play 服務的模擬器,然後執行 adb root。

3. 在 Android Q 上使用 simpleperf

有一個名為 simpleperf 的工具,如果它們有一個特殊的清單標誌,據說可以在非根 Q+ 裝置上啟用分析版本構建。 該文件將其稱為 profileableFromShell,XML 示例有一個帶有 android:shell 屬性的 profileable 標記,官方清單文件沒有顯示任何內容。 <manifest ...> <application ...> <profileable android:shell="true" /> </application> </manifest>

我查看了 cs.android.com 上的清單解析程式碼: if (tagName.equals("profileable")) { sa = res.obtainAttributes( parser, R.styleable.AndroidManifestProfileable ); if (sa.getBoolean( R.styleable.AndroidManifestProfileable_shell, false )) { ai.privateFlags |= ApplicationInfo.PRIVATE_FLAG_PROFILEABLE_BY_SHELL; } }

如果清單具有 <profileable android:shell="true" /> (我沒有嘗試過),您似乎可以從命令列觸發分析。 據我瞭解,Android Studio 團隊仍在努力與此新功能整合。

分析下載的 APK

在 Square,我們的版本是用 CI 構建的。 正如我們之前看到的,從 Android Studio 分析應用程式啟動需要檢查執行配置中的一個選項。 我們如何使用下載的 APK 來做到這一點?

事實證明,這是可能的,但隱藏在 File > Profile or Debug APK 下。 這將開啟一個包含解壓縮 APK 的新視窗,您可以從中設定執行配置並開始分析。

Android Studio 分析器會減慢一切

不幸的是,當我在一個生產應用程式上測試這些方法時,即使在最近的 Android 版本上,Android Studio 的分析也會大大減慢應用程式的啟動速度(大約慢 10 倍)。 我不知道為什麼,也許是“高階分析”,它似乎不能被禁用。 我們需要另闢蹊徑!

從程式碼分析

我們可以直接從程式碼開始跟蹤,而不是從 Android Studio 進行分析: ``` val tracesDirPath = TODO("path for trace directory") val fileNameFormat = SimpleDateFormat( "yyyy-MM-dd_HH-mm-ss_SSS'.trace'", Locale.US ) val fileName = fileNameFormat.format(Date()) val traceFilePath = tracesDirPath + fileName // Save up to 50Mb data. val maxBufferSize = 50 * 1000 * 1000 // Sample every 1000 microsecond (1ms) val samplingIntervalUs = 1000 Debug.startMethodTracingSampling( traceFilePath, maxBufferSize, samplingIntervalUs )

// ...

Debug.stopMethodTracing() ```

然後我們可以從裝置中提取跟蹤檔案並將其載入到 Android Studio 中。

什麼時候開始取樣

我們應該在應用程式生命週期中儘早開始記錄跟蹤。 最早可以在 Android P 之前在應用程式啟動時執行的程式碼是 ContentProvider,而在 Android P+ 上它是 AppComponentFactory。

Android P / API < 28

class AppStartListener : ContentProvider() { override fun onCreate(): Boolean { Debug.startMethodTracingSampling(...) return false } // ... }

```

```

在定義提供者時,我們可以設定一個 initOrder 標記,並且最高的數字首先被初始化。

Android P+ / API 28+

``` @RequiresApi(28) class MyAppComponentFactory() : androidx.core.app.AppComponentFactory() {

@RequiresApi(29) override fun instantiateClassLoader( cl: ClassLoader, aInfo: ApplicationInfo ): ClassLoader { if (Build.VERSION.SDK_INT >= 29) { Debug.startMethodTracingSampling(...) } return super.instantiateClassLoader(cl, aInfo) }

override fun instantiateApplicationCompat( cl: ClassLoader, className: String ): Application { if (Build.VERSION.SDK_INT < 29) { Debug.startMethodTracingSampling(...) } return super.instantiateApplicationCompat(cl, className) } } ```

```

```

在哪裡儲存取樣

val tracesDirPath = TODO("path for trace directory")

  • API < 28:廣播接收器可以訪問上下文,我們可以在該上下文上呼叫 Context.getDataDir() 將跟蹤儲存在應用程式目錄中。

  • API 28: AppComponentFactory.instantiateApplication() 負責建立一個新的應用程式例項,所以目前還沒有可用的上下文。 我們可以直接硬編碼到 /sdcard/ 的路徑,但這需要 WRITE_EXTERNAL_STORAGE 許可權。

  • API 29+:當面向 API 29 時,硬編碼 /sdcard/ 停止工作。 我們可以新增 requestLegacyExternalStorage 標誌,但無論如何 API 30 都不支援它。 建議在 API 30+ 上嘗試 MANAGE_EXTERNAL_STORAGE。 無論哪種方式,AppComponentFactory.instantiateClassLoader() 都會傳遞一個 ApplicationInfo,因此我們可以使用 ApplicationInfo.dataDir 將跟蹤儲存在應用程式目錄中。

何時停止取樣

當應用程式的第一幀完全載入時,冷啟動結束。 我們可以根據這個條件停止方法跟蹤:

``` class MyApp : Application() {

override fun onCreate() { super.onCreate()

var firstDraw = false
val handler = Handler()

registerActivityLifecycleCallbacks(
  object : ActivityLifecycleCallbacks {
  override fun onActivityCreated(
    activity: Activity,
    savedInstanceState: Bundle?
  ) {
    if (firstDraw) return
    val window = activity.window
    window.onDecorViewReady {
      window.decorView.onNextDraw {
        if (firstDraw) return
        firstDraw = true
        handler.postAtFrontOfQueue {
          Debug.stopMethodTracing()
        }
      }
    }
  }
})

} } ```

我們還可以記錄比應用程式啟動時間更長的固定時間,例如 5秒:

Handler(Looper.getMainLooper()).postDelayed({ Debug.stopMethodTracing() }, 5000)

使用 Nanoscope 進行分析

另一個用於分析應用程式啟動的選項是 uber/nanoscope。 這是一個帶有內建低開銷跟蹤的 Android 影象。 它很棒,但有一些限制:

它只跟蹤主執行緒。

大型應用程式將溢位記憶體跟蹤緩衝區。

應用啟動步驟

一旦我們有了啟動跟蹤,我們就可以開始調查什麼操作耗費時間。 應該期待 3 個主要部分:

ActivityThread.handlingBindApplication() 包含 Activity 建立之前的啟動工作。 如果這很慢,那麼我們可能需要優化 Application.onCreate()。

TransactionExecutor.execute() 負責建立和恢復 Activity ,包括填充檢視層次結構。

ViewRootImpl.performTraversals() 是框架執行第一次測量、佈局和繪製的地方。 如果這很慢,則可能是檢視層次結構過於複雜,或者具有需要優化的自定義繪圖的檢視。

如果注意到服務是在第一次檢視遍歷之前啟動的,那麼延遲該服務的啟動可能是值得的,以便它在檢視遍歷之後發生。

結論

一些要點:

分析釋出版本專注於實際問題。

Android 上的分析應用程式啟動狀態遠非理想。 基本上沒有好的開箱即用解決方案,但 Jetpack Benchmark 團隊正在努力解決這個問題。

從程式碼開始取樣,以防止 Android Studio 拖慢一切。