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-beta02"
接著,初始化並啟用每個 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
- 報告問題,幫助我們修正錯誤。 ,瞭解如何調查及移除這項存取權。
為你推薦
建立基準設定檔
Plan to create quality apps and features from the start by understanding best practices and requirements.
Microbenchmark 檢測引數
Plan to create quality apps and features from the start by understanding best practices and requirements.
Macrobenchmark 檢測引數
Plan to create quality apps and features from the start by understanding best practices and requirements.