Google I/O :Android Jetpack 最新變化(二) Performance

語言: CN / TW / HK

theme: devui-blue highlight: atelier-sulphurpool-light


前言

本系列文章從 Architecture,UI,Performance 和 Compose 這四個方向帶大家瞭解本次 I/O 上 Jetpack 的最新內容。

本文是第二篇:Performance 篇。

1. JankStats 卡頓檢測

JankStats 用來追蹤和分析應用效能,發現 Jank 卡頓問題,它最低向下相容到 API 16,可以在絕大多數機器裝置上使用,有了它我們不必再求助 BlockCanery 等三方工具了。 groovy implementation "androidx.metrics:metrics-performance:1.0.0-alpha01" 我們需要為每個 Window 建立一個 JankStats 例項,並通過 OnFrameListener 回撥獲取包含是否卡頓在內的幀資訊,示例如下: ```kotlin class JankLoggingActivity : AppCompatActivity() {

private lateinit var jankStats: JankStats

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    // ...
    // metricsStateHolder可以收集環境資訊,跟隨幀資訊返回
    val metricsStateHolder = PerformanceMetricsState.getForHierarchy(binding.root)

    // 基於當前 Window 建立 JankStats 例項
    jankStats = JankStats.createAndTrack(
        window,
        Dispatchers.Default.asExecutor(),
        jankFrameListener,
    )

    // 設定 Activity 名字到環境資訊
    metricsStateHolder.state?.addState("Activity", javaClass.simpleName)
    // ...
}

private val jankFrameListener = JankStats.OnFrameListener { frameData ->
    // 監聽到的幀資訊
    Log.v("JankStatsSample", frameData.toString())
}

} PerformanceMetricsState 用來收集你希望跟隨 frameData 一起返回的狀態資訊,比如上面例子中設定了當前 Activity 名稱,下面是 frameData 的列印日誌:txt JankStats.OnFrameListener: FrameData(frameStartNanos=827233150542009, frameDurationUiNanos=27779985, frameDurationCpuNanos=31296985, isJank=false, states=[Activity: JankLoggingActivity]) ```

更多參考:https://medium.com/androiddevelopers/jankstats-goes-alpha-8aff942255d5

2. Baseline Profiles 基準配置

Android 8.0 之後預設開啟 ART 虛擬機器。ART 最初版本在安裝應用時會對全部程式碼進行 AOT 預編譯,將位元組碼轉換為機器碼存在本地,這提升了執行時的速度,但是會導致安裝過程變慢。因此後來 ART 改進為 JIT 和 AOT 相結合的方式,在應用安裝時只將熱點程式碼編譯成機器碼,縮短安裝時間。

Baselin Profiles 基準配置檔案允許我們配置哪些程式碼成為熱點程式碼。基準配置檔案將在 APK 的 assets/dexopt/baseline.prof 中編譯為二進位制形式,例如如果我們想提升首幀的效能,可以將應用啟動或幀渲染期間使用的方法配置到 prof 檔案中。

prof 檔案可以通過自動或手動方式生成,我們可以編寫 JUnit4 測試用例,通過執行 BaselineProfileRule 在測試中發現待優化的瓶頸程式碼,並生成對應的 prof 檔案

```kotlin @ExperimentalBaselineProfilesApi @RunWith(AndroidJUnit4::class) class BaselineProfileGenerator { @get:Rule val baselineProfileRule = BaselineProfileRule()

@Test
fun startup() =
    baselineProfileRule.collectBaselineProfile(packageName = "com.example.app") {
        pressHome()
        startActivityAndWait()
    }

} 我們也可以手動建立 prof 檔案,只需遵循一些簡單的語法規則。例如下面展示了 Jetpack Compose 庫中包含的一些 Prof 規則,txt HSPLandroidx/compose/runtime/ComposerImpl;->updateValue(Ljava/lang/Object;)V HSPLandroidx/compose/runtime/ComposerImpl;->updatedNodeCount(I)I HLandroidx/compose/runtime/ComposerImpl;->validateNodeExpected()V PLandroidx/compose/runtime/CompositionImpl;->applyChanges()V HLandroidx/compose/runtime/ComposerKt;->findLocation(Ljava/util/List;I)I Landroidx/compose/runtime/ComposerImpl; `` 上述配置遵循[FLAGS][CLASS_DESCRIPTOR]->[METHOD_SIGNATURE]` 格式,其中 FLAGS 中的 H/S/P 代表方法的呼叫實際,比如是否是啟動時呼叫等。

更多參考:https://android-developers.googleblog.com/2022/01/improving-app-performance-with-baseline.html

3. Benchmark 基準測試

Jetpack 當前提供了兩套 Benchmark 庫,Microbenchmark 和 Macrobenchmark (微基準和巨集基準),分別用於不同場景下的基準測試。

Mircobenchmark 的測試物件是程式碼塊,它的依賴如下: groovy androidTestImplementation 'androidx.benchmark:benchmark-junit4:1.1.0-beta03' 我們可以在 JUnit4 中應用 BenchmarkRule,示例如下: ```kotlin @RunWith(AndroidJUnit4::class) class SampleBenchmark { @get:Rule val benchmarkRule = BenchmarkRule()

@Test
fun benchmarkSomeWork() {
    benchmarkRule.measureRepeated {
        doSomeWork() //執行待測試程式碼
    }
}

} ```

Macrobenchmark 通常面向更大粒度的場景測試,例如一個 Activity 啟動或者一個使用者操作等。由於 Macrobenchmark 不進行程式碼級別測試,我們可以建立獨立於業務程式碼的單獨模組進行測試:

下面展示了使用 MacrobenchmarkRule 測試一個 Activity 的啟動: ```kotlin @get:Rule val benchmarkRule = MacrobenchmarkRule()

@Test
fun startup() = benchmarkRule.measureRepeated(
    packageName = "mypackage.myapp",
    metrics = listOf(StartupTimingMetric()),
    iterations = 5,
    startupMode = StartupMode.COLD
) { // this = MacrobenchmarkScope
    pressHome()
    val intent = Intent()
    intent.setPackage("mypackage.myapp")
    intent.setAction("mypackage.myapp.myaction")
    startActivityAndWait(intent)
}

```
配合 2021.1.1 或更高版本的 Android Studio ,Benchmark 的測試結果會直接顯示在 IDE 視窗中。

當然,測試結果也可以匯出為 JSON 格式

更多參考:https://medium.com/androiddevelopers/measure-and-improve-performance-with-macrobenchmark-560abd0aa5bb

4. Tracing 事件追蹤

Tracing 用來在程式碼新增 trace 資訊,trace 資訊可以顯示在 Systrace 和 Perfetto 等工具中。 groovy implementation "androidx.tracing:tracing:1.1.0-beta01" 下面的例子彙總,我們通過 Trace 類的 benginSection/endSection 方法追蹤 onCreateViewHolder 和 onBindViewHolder 方法執行的起始點 ```kotlin class MyAdapter : RecyclerView.Adapter() { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder { return try { Trace.beginSection("MyAdapter.onCreateViewHolder") MyViewHolder.newInstance(parent) } finally { //endSection 放到 finally 裡,當出現異常時也會呼叫 Trace.endSection() } }

override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
    Trace.beginSection("MyAdapter.onBindViewHolder")
    try {
        try {
            Trace.beginSection("MyAdapter.queryDatabase")
            val rowItem = queryDatabase(position)
            dataset.add(rowItem)
        } finally {
            Trace.endSection()
        }
        holder.bind(dataset[position])
    } finally {
        Trace.endSection()
    }
}

} ``` 需要注意 benginSection/endSection 必須成對出現,且必須在同一執行緒中。我們 Trace 的 section 會作為新增的自定義事件出現在 Perfetto 等工具檢視中:

image.png