△ 插圖作者: Virginia Poltrack
作者 / Chet Haase, Android 開發技術推廣工程師
卡頓 (名詞): 指應用效能糟糕,可能導致丟幀、介面動畫不連貫和使用者體驗不佳等問題。請參閱 "不開心的使用者" 詞條。
效能問題很難除錯。我們常常不清楚要從何下手、使用何種工具、使用者遇到了什麼問題,以及那些問題在現實的裝置上有何表現。
過去幾年間,Android 團隊一直努力推出更多工具,用於除錯各種問題,從分析 啟動效能 到測試 具體程式碼路徑,再到測試和優化特定 用例 及 IDE 中的視覺化分析器,各領域均有涉獵。所有這些工具均針對開發期間的測試設計,用於幫助您除錯和修復在本地執行時發現的問題。
同時,Google Play 的 Android Vitals 和 Firebase 均提供資訊中心,供開發者瞭解其應用在實際使用者裝置上的執行情況。
儘管如此,在實際情況中,我們仍然很難發現應用中可能存在的問題,尤其是使用者裝置上可能出現的問題。這可不是您坐在座椅中用著熟悉的開發機器能碰到的問題。雖說效能資訊中心可提供一定幫助,但在使用者遇到問題時,它卻未必能讓您充分了解所發生情況的詳細資訊。
JankStats 應運而生: 這是首個專為在使用者裝置上檢測及報告應用的效能問題而構建的 AndroidX 庫。
JankStats 是佔用空間相對較小的 API,主要有三大目標: 捕獲每幀的效能資訊、在使用者裝置 (不僅是開發裝置) 上執行、以及在應用出現效能問題時啟用檢測,並報告所發生的情況。
每幀效能
Android 平臺已提供多種方法,用於獲取幀效能資料。例如,從 API 24 開始就可以使用 FrameMetrics 獲取相關資料,後續多個版本也在進一步豐富該功能,以便為您提供更多詳細資訊。如果在更早期的版本上執行應用,也有多種方法可供您獲取時間資訊,雖說不夠準確,但仍十分實用。
因此,如果您想確保自己的幀持續時間邏輯適用於所有版本,就需要在不同的 API 版本中實現不同的測試和報告機制。現在,您可以使用統一的 JankStats API 來實現這些功能。除此之外,它還提供了更多驚喜 (請繼續閱讀本文!)。
JankStats 通過提供單一 API 來報告每幀的時間,從而簡化您的工作,並會在內部委派適當機制 (比如 API 24 以上會委派給 FrameMetrics)。您不必關心這些資料的來源,只需讓 JankStats 告訴您完成特定事項花費的時間,然後便可在回撥中獲取相關資訊。
建立和監聽 JankStats 資料就是這麼簡單: 只需完成建立,然後就可以坐下來 (好吧,是您的程式碼 "坐" 下來) 監聽。以下是 JankStats 的示例 JankLoggingActivity 中的步驟範例:
val jankFrameListener = JankStats.OnFrameListener { frameData ->
// real app would do something more interesting than log this...
Log.v("JankStatsSample", frameData.toString())
}
jankStats = JankStats.createAndTrack(
window,
Dispatchers.Default.asExecutor(),
jankFrameListener,
)
此處的 Log.v() 呼叫僅作範例使用,並非您在應用中應採取的操作。在實際操作中,您可能應彙整/儲存/上傳資料,以供日後分析使用,而非將資料釋出於日誌中。無論如何,下面是在 API 30 模擬器上執行時產生的輸出示例 (為便於閱讀,已刪除部分 logcat 的輸出內容,並新增了空白行):
JankStats.OnFrameListener: FrameData(frameStartNanos=827233150542009, frameDurationUiNanos=27779985, frameDurationCpuNanos=31296985, isJank=false, states=[Activity: JankLoggingActivity])
JankStats.OnFrameListener: FrameData(frameStartNanos=827314067288736, frameDurationUiNanos=89903592, frameDurationCpuNanos=94582592, isJank=true, states=[RecyclerView: Dragging, Activity: JankLoggingActivity])
JankStats.OnFrameListener: FrameData(frameStartNanos=827314167288732, frameDurationUiNanos=88641926, frameDurationCpuNanos=91526926, isJank=true, states=[RecyclerView: Settling, RecyclerView: Dragging, Activity: JankLoggingActivity])
JankStats.OnFrameListener: FrameData(frameStartNanos=827314183945923, frameDurationUiNanos=4731405, frameDurationCpuNanos=8283405, isJank=false, states=[RecyclerView: Settling, Activity: JankLoggingActivity])
您可以在日誌的 frameData
中看到一些有趣的內容:
- 其中有部分幀帶有
isJank=true
標記。該日誌取自執行的示例應用JankLoggingActivity
,您可檢視 完整示例 瞭解更多。該應用會強制產生一些長幀 (沒錯,用了Thread.sleep()
!),從而讓 JankStats 判定其為卡頓。 - 幀的時間資訊中同時包含介面和 CPU 資料,但在 API 24 (
FrameMetrics
被引入的版本) 之前的版本中,此資訊僅包含介面持續時間。 - 該日誌是從我在應用中開始滑動 RecyclerView 時獲取的。當 RecyclerView 開始移動 (被 "拖動") 以及 RecyclerView 開始自由滾動 (被 "放置") 時,我們可在開始之前看到與介面狀態相關的資訊 (僅列出
Activity
狀態)。有關這些介面狀態的詳細資訊,請閱讀下文。
真實資料
不同於最近的基準庫,建立 JankStats 的目的是為您提供來自使用者裝置的結果。能在開發機器上除錯問題固然很好,但在現實中,使用者會根據迥異的約束條件,在不同的裝置上使用您的應用,對於這類情況,本地除錯可能並不能發現和解決問題。
JankStats 提供 API 來檢測您的應用,以提供您所需的效能資料和報告機制,以便您能上傳這些資料並離線進行分析。
應用狀態
最後 (請注意,這才是 JankStats 庫的新亮點),JankStats 提供了一種方法,可讓您瞭解出現效能問題時應用中實際發生的情況。我們經常聽到的抱怨是: 現有的工具、資訊中心和方法均未能提供足夠的背景資訊,不足以讓您知曉使用者實際遭遇到的效能問題。
例如,FrameMetrics API (在 API 24 版本中推出,JankStats 內部也有使用) 可以告訴您繪製幀需要多長時間,而您也可從中獲取卡頓資訊,但它無法讓您知曉當時應用中的具體情況。當您嘗試檢測程式碼,並將其與 FrameMetrics 或其他效能測量工具整合時,該問題就需要開發者自行解決。但是,除非必須要在內部構建這種基礎架構,那每個人都有許多別的工作要做。因此,卡頓問題通常得不到量化測試,而效能問題自然也無法解決。
同樣,Android Vitals 資訊中心也可以告訴您,應用存在效能問題,但無法告訴您問題發生時應用的具體執行情況。因此,您很難通過這些資訊來知曉應該如何處理出現的問題。
JankStats 推出了 PerformanceMetricsState
API,這套簡單的方法可讓您通過成對的字串告訴系統在任意時刻您的應用所發生的事情。例如,您可能想知道,某個特定的 Activity
或 Fragment
在何時處於活動狀態,或 RecyclerView 何時處於滾動狀態。
例如,下面是 JankStats 示例中的程式碼,表明該工具如何檢測 RecyclerView
,以向 JankStats 提供此資訊:
val scrollListener = object : RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView,
newState: Int)
{
val metricsState = metricsStateHolder?.state ?: return
when (newState) {
RecyclerView.SCROLL_STATE_DRAGGING -> {
metricsState.addState("RecyclerView", "Dragging")
}
RecyclerView.SCROLL_STATE_SETTLING -> {
metricsState.addState("RecyclerView", "Settling")
}
else -> {
metricsState.removeState("RecyclerView")
}
}
}
}
此狀態可在您應用中的任何地方 (甚至從其他庫) 注入,當其報告結果時,會被 JankStats 接收到。這樣一來,當您從 JankStats 獲取報告時,不僅可以知道每幀裡各種事件花費的時間,還可以瞭解使用者在那一幀期間做了什麼,這可能會是相當有用的資訊。
資源
下面這些資源可以幫助您瞭解有關 JankStats 的更多資訊:
AndroidX 專案 : JankStats 位於 AndroidX 的 androidx.metrics 庫中。
文件 : 開發者網站提供了新的 開發者指南,其中介紹了 JankStats 的用法。
示例程式碼 : 示例專案 展示瞭如何將 JankStats 物件例項化並進行偵聽,以及如何針對重要的介面狀態資訊來監測應用。
錯誤報告 : 若您對該庫有任何疑問,或是想提出 API 需求,歡迎向我們 提交錯誤報告。
Alpha -> 1.0
JankStats 剛剛釋出了首個 alpha 版本,這次釋出的用意是: "我們認為這個 API 和功能會對 1.0 版本的釋出頗有幫助,請先試用,並和我們分享您的反饋。"
今後我們還想針對 JankStats 做其他事情,包括新增某種聚合機制,甚至與現有的上傳服務同步。不過,在推出首個版本之前,我們希望瞭解大家的使用情況,以及蒐集大家想要的其他功能。我們希望這一版本在當前的基本狀態下能對大家有所幫助。僅僅是輕鬆檢測並記錄介面狀態資訊這個功能,應該就可以為大家提供一些便利。
現在就請大家 獲取 並試用此版本,我們恭候大家提出的 反饋。最重要的是,我們希望大家能借助 JankStats 找出並修復效能問題!您的使用者正等著您呢,別讓他們等太久了!
歡迎您 點選這裡 向我們提交反饋,或分享您喜歡的內容、發現的問題。您的反饋對我們非常重要,感謝您的支援!