JankStats 程式庫可協助您追蹤及分析應用程式的效能問題。卡頓是指應用程式轉譯時間過長,JankStats 程式庫則針對應用程式的卡頓統計資料提供報告。
功能
JankStats 以現有的 Android 平台功能為基礎,包括 Android 7 (API 級別 24) 以上版本的 FrameMetrics API,或較舊 Android 版本的 OnPreDrawListener。這些機制可協助應用程式追蹤完成影格所需的時間。JankStats 程式庫提供兩項額外功能,讓資源更靈活且易於使用:卡頓經驗法則和 UI 狀態。
卡頓經驗法則
雖然可以使用 FrameMetrics 來追蹤影格持續時間,但 FrameMetrics 無法協助判斷實際發生卡頓的情況。JankStats 則具備可設定的內部機制,用於判斷發生卡頓時間,讓報表更能及時幫助使用者。
UI 狀態
通常需要瞭解應用程式中效能問題的背景。例如,如果您開發使用 FrameMetrics 的複雜多螢幕應用程式,發現應用程式經常有大量卡頓的影格,您需要將資訊放到背景來分析,瞭解問題發生的位置、使用者正在執行的操作以及如何複製此情況。
為解決這個問題,JankStats 導入了 state
API,您可用來與程式庫通訊,提供應用程式活動的相關資訊。當 JankStats 記錄卡頓影格相關資訊時,會將應用程式的當前狀態納進卡頓報告。
使用方式
如要開始使用 JankStats,請為各個 Window
執行個體化並啟用資料庫。每個 JankStats 物件只會追蹤 Window
內的資料。如要將程式庫執行個體化,必須使用 Window
執行個體和 OnFrameListener
事件監聽器,兩者皆用於將指標傳送至用戶端。系統會在每個影格使用 FrameData
呼叫事件監聽器,並詳細說明:
- 影格開始時間
- 持續時間值
- 是否該認定影格卡頓
- 一組字串對,內含影格顯示期間的應用程式狀態相關資訊
如要讓 JankStats 更實用,應用程式應在資料庫中填入相關 UI 狀態資訊,用於 FrameData 中的報表功能。您可以透過 PerformanceMetricsState
API (而非直接透過 JankStats) 完成這項操作,此 API 包含所有狀態管理邏輯和 API。
初始化
如要開始使用 JankStats 程式庫,請先將 JankStats 依附元件新增至 Gradle 檔案:
implementation "androidx.metrics:metrics-performance:1.0.0-beta01"
接著,初始化並啟用每個 Window
的 JankStats。此外,當活動在背景執行時,也應該暫停 JankStats 追蹤。在活動覆寫中建立並啟用 JankStats 物件:
class JankLoggingActivity : AppCompatActivity() {
private lateinit var jankStats: JankStats
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// ...
// metrics state holder can be retrieved regardless of JankStats initialization
val metricsStateHolder = PerformanceMetricsState.getHolderForHierarchy(binding.root)
// initialize JankStats for current window
jankStats = JankStats.createAndTrack(window, jankFrameListener)
// add activity name as state
metricsStateHolder.state?.putState("Activity", javaClass.simpleName)
// ...
}
上述範例會在建構 JankStats 物件後插入目前活動的狀態資訊。所有為此 JankStats 物件所建立的 FrameData 報表日後也會包含活動資訊。
JankStats.createAndTrack
方法會參照 Window
物件,該物件是 Window
中檢視區塊階層和 Window
本身的 Proxy。系統會透過特定執行緒呼叫 jankFrameListener
,這個執行緒的用途是在內部將資訊從平台傳送至 JankStats。
如要為任何 JankStats 物件啟用追蹤和報告功能,請呼叫 isTrackingEnabled = true
。雖然系統預設為啟用,但停用活動則會停用追蹤功能。在這種情況下,請務必先重新啟用追蹤功能再繼續。如要停止追蹤,請呼叫 isTrackingEnabled = false
。
override fun onResume() {
super.onResume()
jankStats.isTrackingEnabled = true
}
override fun onPause() {
super.onPause()
jankStats.isTrackingEnabled = false
}
報表
JankStats 程式庫會將每個影格的資料追蹤內容,全數傳遞至啟用中的 JankStats 物件 OnFrameListener
。應用程式可以儲存並聚合這些資料,以供日後上傳。詳情請參閱「匯總」部分提供的範例。
您必須為應用程式建立及提供 OnFrameListener
,才能接收影格報表。每個影格都會呼叫這個事件監聽器,以便為應用程式提供持續出現的卡頓資料。
private val jankFrameListener = JankStats.OnFrameListener { frameData ->
// A real app could do something more interesting, like writing the info to local storage and later on report it.
Log.v("JankStatsSample", frameData.toString())
}
事件監聽器透過 FrameData
物件提供與卡頓情況有關的影格資訊。其中包含關於所要求影格的下列資訊:
isjank
:表示影格是否發生卡頓情形的布林值標記。frameDurationUiNanos
:影格的持續時間 (以奈秒表示)。frameStartNanos
:影格開始的時間 (以奈秒為單位)。states
:應用程式在影格顯示期間的狀態。
如果使用 Android 12 (API 級別 31) 以上版本,則可使用下列程式碼公開顯示影格持續時間的更多資料:
FrameDataApi24
提供frameDurationCpuNanos
以顯示影格在非 GPU 部分中的時間。FrameDataApi31
提供frameOverrunNanos
,以顯示超過影格完成期限的時間長度。
使用事件監聽器中的 StateInfo
,即可儲存應用程式狀態的相關資訊。
請注意,系統會透過特定執行緒呼叫 OnFrameListener
,這個執行緒的用途是在內部將每個影格的資訊傳遞至 JankStats。在 Android 6 (API 級別 23) 以下版本中,系統會採用主要 (UI) 執行緒。在 Android 7 (API 級別 24) 以上版本中,系統則會採用為了供 FrameMetrics 使用而建立的執行緒。無論是哪一種情況,都請務必迅速處理回呼並傳回資料,以免執行緒發生效能問題。
另請注意,回呼中傳送的 FrameData 物件會重複用於每個影格,這樣就不必為資料報表分配新的物件。也就是說,您必須在其他位置複製及快取資料,因為在回呼傳回後,該物件應該立即視為無狀態且已過時。
匯總
您可能希望應用程式碼匯總每個影格資料,方便您自行儲存及上傳資訊。雖然儲存及上傳作業的詳細資料不在 Alpha 版 JankStats API 的範圍內,您仍可以查看初步活動,以便使用 GitHub 存放區中提供的 JankAggregatorActivity
,將各個影格的資料匯總為更大的集合。
JankAggregatorActivity
使用 JankStatsAggregator
類別,將自身的報告機制分到 JankStats OnFrameListener
機制之上,為僅報告涵蓋許多影格的資訊集合提供更高層級的抽象層。
JankAggregatorActivity
不會直接建立 JankStats 物件,而是建立 JankStatsAggregator 物件;該物件會在內部建立專屬的 JankStats 物件:
class JankAggregatorActivity : AppCompatActivity() {
private lateinit var jankStatsAggregator: JankStatsAggregator
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// ...
// Metrics state holder can be retrieved regardless of JankStats initialization.
val metricsStateHolder = PerformanceMetricsState.getHolderForHierarchy(binding.root)
// Initialize JankStats with an aggregator for the current window.
jankStatsAggregator = JankStatsAggregator(window, jankReportListener)
// Add the Activity name as state.
metricsStateHolder.state?.putState("Activity", javaClass.simpleName)
}
JankAggregatorActivity
也採用類似的機制來暫停及繼續追蹤,而且會新增 pause()
事件做為透過呼叫 issueJankReport()
發送報表的信號,因為生命週期變更似乎是擷取應用程式卡頓狀態的合適時機:
override fun onResume() {
super.onResume()
jankStatsAggregator.jankStats.isTrackingEnabled = true
}
override fun onPause() {
super.onPause()
// Before disabling tracking, issue the report with (optionally) specified reason.
jankStatsAggregator.issueJankReport("Activity paused")
jankStatsAggregator.jankStats.isTrackingEnabled = false
}
上方的範例是應用程式必須啟用 JankStats 和接收影格資料所需的程式碼。
管理狀態
您可能會想呼叫其他 API 來自訂 JankStats。舉例來說,插入應用程式狀態資訊可為發生卡頓的影格提供背景資訊,讓影格資料更加實用。
這個靜態方法會擷取
MetricsStateHolder
敬上
特定檢視區塊階層的物件。
PerformanceMetricsState.getHolderForHierarchy(view: View): MetricsStateHolder
您可以使用有效階層中的任何檢視區塊。在內部,這項檢查會查看與這個資料檢視區塊階層連結的現有 Holder
物件。此資訊會在該階層頂端的檢視區塊中快取。如果沒有這類物件,getHolderForHierarchy()
會建立一個。
透過靜態的 getHolderForHierarchy()
方法,您就不必在某個位置快取 holder 執行個體用於之後的擷取作業,而且還能更輕鬆地從程式碼中的任何位置擷取現有狀態物件,甚至是從無法以其他方式存取原始執行個體的程式庫程式碼擷取。
請注意,傳回值是 holder 物件,而不是狀態物件。holder 內部的狀態物件值只能透過 JankStats 設定。也就是說,如果應用程式針對包含該檢視區塊階層的 Window 建立 JankStats 物件,系統就會建立及設定狀態物件。相反地,如果不使用 JankStats 追蹤資訊,就不需要狀態物件,應用程式或程式庫程式碼也不必插入狀態。
這個方法可以擷取 holder,然後可由 JankStats 填入。外部程式碼隨時都可要求 holder。呼叫端可以快取這個輕量 Holder
物件,並隨時根據其內部 state
屬性的值設定狀態 (如下方程式碼範例所示),且狀態僅會在 holder 的內部狀態屬性並非空值時設定:
val metricsStateHolder = PerformanceMetricsState.getHolderForHierarchy(binding.root)
// ...
metricsStateHolder.state?.putState("Activity", javaClass.simpleName)
如要控制 UI/應用程式狀態,應用程式可以使用 putState
和 removeState
方法插入 (或移除) 狀態。JankStats 會記錄這些呼叫的時間戳記。如果影格與狀態的開始和結束時間重疊,JankStats 就會一起回報這些狀態資訊和影格時間資料。
請針對所有狀態加入以下兩項資訊:key
(狀態類別,例如「RecyclerView」) 和 value
(當下發生的狀況,例如「scrolling」)。
狀態失效後,請使用 removeState()
方法移除狀態,確保不會連同影格資料一併回報錯誤或誤導性資訊。
如果使用先前新增的 key
呼叫 putState()
,會將狀態的現有 value
替換為新的值。
狀態 API 的 putSingleFrameState()
版本會在下一個回報的影格中添加只記錄一次的狀態。系統隨後會自動移除這個狀態,確保您不會意外將過時的狀態加入程式碼中。請注意,由於 JankStats 會自動移除單一影格狀態,因此沒有與 singleFrame 同等的 removeState()
。
private val scrollListener = object : RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
// check if JankStats is initialized and skip adding state if not
val metricsState = metricsStateHolder?.state ?: return
when (newState) {
RecyclerView.SCROLL_STATE_DRAGGING -> {
metricsState.putState("RecyclerView", "Dragging")
}
RecyclerView.SCROLL_STATE_SETTLING -> {
metricsState.putState("RecyclerView", "Settling")
}
else -> {
metricsState.removeState("RecyclerView")
}
}
}
}
請注意,用於狀態的鍵應具備足夠的意義,以便後續分析。具體來說,由於取代舊值的狀態含有先前所新增狀態的 key
,因此建議您為應用程式或程式庫中可能含有不同執行個體的物件,使用不重複的 key
名稱。舉例來說,如果應用程式有五個不同的 RecyclerView
因此想為每個金鑰提供可識別的鍵
每個情境 RecyclerView
,因此無法輕易判斷
產生的資料。
卡頓經驗法則
如要調整內部演算法以判定卡頓情況,請使用 jankHeuristicMultiplier
屬性。
根據預設,系統會將卡頓定義為影格需花費兩倍時間,才能達到目前的刷新率效果。系統不會依據刷新率以外的因素判斷是否發生卡頓,因為應用程式轉譯時間的資訊不夠清楚。因此,建議您新增緩衝區,而且只在出現明顯的效能問題時才加以回報。
您可以透過這些方法變更這兩個值,以便更貼近應用程式的情況,或是在測試期間進行更改,依據需求決定是否要強制發生卡頓。
Jetpack Compose 使用狀況
目前在 Compose 中使用 JankStats 幾乎不需要設定。請謹記以下設定,在設定變更時保持 PerformanceMetricsState
:
/**
* Retrieve MetricsStateHolder from compose and remember until the current view changes.
*/
@Composable
fun rememberMetricsStateHolder(): PerformanceMetricsState.Holder {
val view = LocalView.current
return remember(view) { PerformanceMetricsState.getHolderForHierarchy(view) }
}
如要使用 JankStats,請將目前狀態新增至 stateHolder
,如下所示:
val metricsStateHolder = rememberMetricsStateHolder()
// Reporting scrolling state from compose should be done from side effect to prevent recomposition.
LaunchedEffect(metricsStateHolder, listState) {
snapshotFlow { listState.isScrollInProgress }.collect { isScrolling ->
if (isScrolling) {
metricsStateHolder.state?.putState("LazyList", "Scrolling")
} else {
metricsStateHolder.state?.removeState("LazyList")
}
}
}
如需進一步瞭解如何在 Jetpack Compose 應用程式中使用 JankStats,請參考我們的效能範例應用程式。
提供意見
歡迎透過下列資源與我們分享意見和想法:
- Issue Tracker
- 報告問題,幫助我們修正錯誤。 ,瞭解如何調查及移除這項存取權。
為您推薦
- 注意:系統會在 JavaScript 關閉時顯示連結文字
- 建立基準設定檔 {:#create-profile-rules}
- Microbenchmark 檢測引數
- Macrobenchmark 檢測引數