各位戰士,歡迎來到第二十七天的戰場。昨天,我們部署了 StrictMode
這位「憲兵」,在開發階段監督我們的行為,防止我們寫出違反主執行緒紀律的程式碼。
StrictMode
能抓到那些「非黑即白」的嚴重違規,但它無法回答一些更細微、卻同樣致命的問題:
為了防止這種「慢性病」,我們需要從「主觀感覺」走向「客觀數據」。我們需要一套標準化、自動化的「體能測驗」,來為我們的應用建立一個性能基準 (Baseline)。而 Jetpack 推出的 Macrobenchmark 函式庫,就是我們建造這座測驗場的官方工具。
Jetpack 提供了兩種基準測試函式庫,我們必須先分清它們的作戰目標:
對於我們關心的應用啟動速度和UI 流暢度,Macrobenchmark 是我們需要使用的唯一指定武器。
與 Baseline Profile 產生器類似,Macrobenchmark 測試必須在一個獨立的 Gradle 模組中執行。
:app
)。Android Studio 會為你配置好一切,包括建立一個 com.android.test
類型的模組,並添加 androidx.benchmark:benchmark-macro-junit4
等必要依賴。
⚠️ 重要作戰前提:
為了得到最真實、最穩定的性能數據,Macrobenchmark 測試必須:
release
版本(isMinifyEnabled = true
, isDebuggable = false
)。因為 Debug 版本包含了大量調試程式碼,其性能表現與使用者實際安裝的版本有天壤之別。現在,讓我們來編寫兩個最核心的測驗項目:應用啟動速度和列表滾動流暢度。
// In your :benchmark module
import androidx.benchmark.macro.StartupMode
import androidx.benchmark.macro.StartupTimingMetric
import androidx.benchmark.macro.junit4.MacrobenchmarkRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class StartupBenchmark {
@get:Rule
val benchmarkRule = MacrobenchmarkRule()
@Test
fun startupCold() = benchmarkRule.measureRepeated(
packageName = "com.your.package.name", // 替換成你的應用包名
metrics = listOf(StartupTimingMetric()),
iterations = 5,
startupMode = StartupMode.COLD,
setupBlock = {
// 在每次測量前,確保應用處於完全關閉的狀態
pressHome()
}
) { // 這是 `measureBlock`,定義了要測量的操作
startActivityAndWait()
}
}
上面的程式碼建立了一個測量冷啟動的基準測試。measureRepeated
會自動為我們運行 5 次測試,殺死 App,再冷啟動,收集數據,最後給出結果。
現在,讓我們加入一個測量 RecyclerView
滾動性能的測試:
// In the same file, or a new one
import androidx.benchmark.macro.FrameTimingMetric
import androidx.test.uiautomator.By
import androidx.test.uiautomator.Direction
@Test
fun scrollList() = benchmarkRule.measureRepeated(
packageName = "com.your.package.name",
metrics = listOf(FrameTimingMetric()), // 我們關心的是「幀時序」,即 Jank
iterations = 5,
setupBlock = {
// 先啟動到包含 RecyclerView 的那個 Activity
startActivityAndWait(Intent("..."))
}
) { // measureBlock
// 找到你的 RecyclerView
val list = device.findObject(By.res("your_recycler_view_id"))
// 設定邊界,讓 fling 動作更穩定
list.setGestureMargin(device.displayWidth / 5)
// 執行一次快速滑動 (fling)
list.fling(Direction.DOWN)
}
像運行普通儀器測試一樣,點擊函式旁邊的綠色箭頭來運行你的基準測試。測試完成後,Android Studio 會在 Run 視窗中印出 JSON 格式的詳細報告。
StartupTimingMetric
),關注:timeToInitialDisplayMs
: 首幀顯示時間。報告會給出 min
, median
, max
值,median
(中位數) 是最值得我們長期追蹤的指標。
FrameTimingMetric
),關注:frameDurationCpuMs
: 每一幀在 CPU 上的處理時間。
P50
(中位數), P90
, P95
, P99
(百分位數) 這些指標。P99
代表了最差的 1% 的影格耗時,是衡量 Jank 的關鍵指標。這個數字越高,代表使用者體驗到的嚴重卡頓越多。
今天,我們從「被動防守」轉向了「主動測量」,為我們的應用程式建立了自動化的性能體能測驗。
我們區分了 Macrobenchmark (測量使用者互動) 和 Microbenchmark (測量函式) 的用途。
我們學會了如何建立一個 Benchmark Moudle,並編寫了測量應用啟動和列表滾動性能的測試腳本。
我們知道了如何解讀測試報告中的關鍵指標,如 timeToInitialDisplayMs
和 frameDurationCpuMs (P99)
。
現在,我們擁有了一把客觀的「碼尺」,可以量化我們的性能優化成果,並將這些數據作為一個基準。當未來有新的程式碼提交時,我們可以再次運行測試,用數據來判斷它是否對性能造成了負面影響。
但手動運行終究不是長久之計。明天,我們將學習如何將這套「體能測驗」整合到我們的 CI/CD (持續整合/持續部署) 流程中,打造一個全自動化的性能哨兵,為我們的應用程式品質站崗。
我們明天見!