Library JankStats membantu Anda melacak dan menganalisis masalah performa di aplikasi. Jank mengacu pada frame aplikasi yang memerlukan waktu terlalu lama untuk dirender, dan library JankStats memberikan laporan tentang statistik jank aplikasi Anda.
Kemampuan
JankStats dibangun berdasarkan kemampuan platform Android yang ada, termasuk FrameMetrics API di Android 7 (level API 24) dan yang lebih baru atau OnPreDrawListener pada versi sebelumnya. Mekanisme ini dapat membantu aplikasi melacak waktu yang diperlukan untuk menyelesaikan frame. Library JankStats memberikan dua kemampuan tambahan yang membuatnya lebih dinamis dan lebih mudah digunakan: heuristik jank dan status UI.
Heuristik jank
Meskipun Anda dapat menggunakan FrameMetrics untuk melacak durasi frame, FrameMetrics tidak memberikan bantuan apa pun dalam menentukan jank aktual. Namun, JankStats memiliki mekanisme internal yang dapat dikonfigurasi untuk menentukan kapan jank terjadi sehingga laporan menjadi lebih berguna.
Status UI
Sering kali Anda perlu mengetahui konteks masalah performa di aplikasi Anda. Misalnya, jika Anda mengembangkan aplikasi multilayar yang kompleks yang menggunakan FrameMetrics dan menemukan bahwa aplikasi tersebut sering memiliki banyak frame yang mengalami jank, Anda perlu melakukan kontekstualisasi informasi tersebut dengan mengetahui lokasi terjadinya masalah, apa yang dilakukan pengguna, dan cara mereplikasinya.
JankStats mengatasi masalah ini dengan memperkenalkan state
API yang memungkinkan Anda
berkomunikasi dengan library untuk memberikan informasi tentang Activity aplikasi. Saat
JankStats melakukan log informasi tentang frame yang mengalami jank, log akan menyertakan status aplikasi
saat ini dalam laporan jank.
Penggunaan
Untuk mulai menggunakan JankStats, buat instance dan aktifkan library untuk setiap
Window
. Setiap objek JankStats hanya melacak data
dalam Window
. Pembuatan instance library memerlukan instance
Window
dan pemroses OnFrameListener
,
yang keduanya digunakan untuk mengirim metrik ke klien. Pemroses dipanggil dengan
FrameData
pada setiap frame
dan menjelaskan:
- Waktu mulai frame
- Nilai durasi
- Apakah frame harus dianggap sebagai jank atau tidak
- Serangkaian pasangan String yang berisi informasi tentang status aplikasi selama frame
Agar JankStats lebih berguna, aplikasi harus mengisi library dengan
informasi status UI yang relevan untuk pelaporan di FrameData. Anda dapat melakukannya
melalui
PerformanceMetricsState
API (bukan JankStats secara langsung), tempat semua
logika pengelolaan status dan API berada.
Inisialisasi
Untuk mulai menggunakan library JankStats, tambahkan dependensi JankStats terlebih dahulu ke file Gradle Anda:
implementation "androidx.metrics:metrics-performance:1.0.0-beta01"
Berikutnya, lakukan inisialisasi dan aktifkan JankStats untuk setiap Window
. Anda juga harus menjeda pelacakan JankStats jika suatu Activity masuk ke latar belakang. Buat dan aktifkan objek JankStats dalam penggantian Activity Anda:
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)
// ...
}
Contoh di atas memasukkan informasi status tentang Activity saat ini setelah membuat objek JankStats. Semua laporan FrameData mendatang yang dibuat untuk objek JankStats ini kini juga mencakup informasi Activity.
Metode JankStats.createAndTrack
mengambil referensi ke objek
Window
, yang merupakan proxy untuk hierarki Tampilan di dalam Window
tersebut serta
untuk Window
itu sendiri. jankFrameListener
dipanggil pada thread yang sama dengan yang digunakan
untuk mengirimkan informasi tersebut dari platform ke JankStats secara internal.
Untuk mengaktifkan pelacakan dan pelaporan pada objek JankStats,
panggil isTrackingEnabled = true
. Meskipun diaktifkan secara default,
menjeda aktivitas akan menonaktifkan pelacakan. Dalam hal ini, pastikan untuk mengaktifkan kembali
pelacakan sebelum melanjutkan. Untuk menghentikan pelacakan, panggil isTrackingEnabled = false
.
override fun onResume() {
super.onResume()
jankStats.isTrackingEnabled = true
}
override fun onPause() {
super.onPause()
jankStats.isTrackingEnabled = false
}
Pelaporan
Library JankStats melaporkan semua pelacakan data Anda, untuk setiap frame, ke
OnFrameListener
untuk objek JankStats yang diaktifkan. Aplikasi dapat menyimpan dan menggabungkan data
ini untuk diupload di lain waktu. Untuk mengetahui informasi selengkapnya, lihat
contoh yang diberikan di bagian Agregasi.
Anda harus membuat dan menyediakan OnFrameListener
untuk aplikasi agar dapat menerima
laporan per frame. Pemroses ini dipanggil di setiap frame untuk menyediakan data
jank yang sedang berlangsung ke aplikasi.
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())
}
Pemroses menyediakan informasi per frame tentang jank dengan objek
FrameData
. File ini
berisi informasi berikut tentang frame yang diminta:
isjank
: Flag boolean yang menunjukkan apakah jank terjadi pada frame atau tidak.frameDurationUiNanos
: Durasi frame (dalam nanodetik).frameStartNanos
: Waktu frame dimulai (dalam nanodetik).states
: Status aplikasi selama frame.
Jika menggunakan Android 12 (level API 31) atau yang lebih tinggi, Anda dapat menggunakan cara berikut untuk menampilkan lebih banyak data tentang durasi frame:
FrameDataApi24
menyediakanframeDurationCpuNanos
untuk menampilkan waktu yang dihabiskan di bagian non-GPU frame.FrameDataApi31
menyediakanframeOverrunNanos
untuk menampilkan jumlah waktu setelah batas waktu frame yang diperlukan untuk menyelesaikan frame.
Gunakan StateInfo
di
pemroses untuk menyimpan informasi tentang status aplikasi.
Perlu diperhatikan bahwa OnFrameListener
dipanggil pada thread yang sama dengan yang digunakan secara internal untuk
mengirimkan informasi per frame ke JankStats.
Di Android versi 6 (level API 23) dan yang lebih rendah, thread tersebut adalah thread (UI) Utama.
Pada Android versi 7 (level API 24) dan yang lebih baru, thread tersebut
dibuat untuk dan digunakan oleh FrameMetrics. Dalam kedua kasus tersebut, penting untuk
menangani callback dan kembali dengan cepat untuk mencegah masalah performa
pada thread tersebut.
Selain itu, perhatikan bahwa objek FrameData yang dikirim dalam callback digunakan kembali pada setiap frame agar tidak perlu mengalokasikan objek baru untuk pelaporan data. Artinya, Anda harus menyalin dan meng-cache data tersebut di tempat lain karena objek tersebut harus dianggap statis dan tidak berlaku lagi segera setelah callback ditampilkan.
Menggabungkan
Anda mungkin ingin kode aplikasi menggabungkan data per frame
agar dapat menyimpan dan mengupload informasi sesuai kebijaksanaan Anda sendiri. Meskipun detail
seputar penyimpanan dan upload berada di luar cakupan rilis alfa JankStats API,
Anda dapat melihat Activity awal untuk menggabungkan data per frame
ke dalam koleksi yang lebih besar menggunakan JankAggregatorActivity
yang tersedia di
Repositori GitHub.
JankAggregatorActivity
menggunakan class JankStatsAggregator
untuk melapisi mekanisme pelaporannya
sendiri di atas mekanisme OnFrameListener
JankStats guna memberikan abstraksi
level yang lebih tinggi untuk hanya melaporkan kumpulan informasi yang mencakup banyak
frame.
Daripada membuat objek JankStats secara langsung, JankAggregatorActivity
akan membuat
objek JankStatsAggregator,
yang membuat objek JankStats sendiri secara internal:
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)
}
Mekanisme serupa digunakan di JankAggregatorActivity
untuk menjeda dan
melanjutkan pelacakan, dengan penambahan peristiwa pause()
sebagai sinyal untuk memberikan
laporan dengan panggilan ke issueJankReport()
, karena perubahan siklus proses menjadi
waktu yang tepat untuk menangkap status jank dalam aplikasi:
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
}
Contoh kode di atas adalah semua yang diperlukan aplikasi untuk mengaktifkan JankStats dan menerima data frame.
Mengelola status
Anda mungkin ingin memanggil API lain untuk menyesuaikan JankStats, Misalnya, memasukkan informasi status aplikasi membuat data frame lebih membantu dengan memberikan konteks untuk frame tempat jank terjadi.
Metode statis ini mengambil
MetricsStateHolder
untuk hierarki View tertentu.
PerformanceMetricsState.getHolderForHierarchy(view: View): MetricsStateHolder
Setiap tampilan dalam hierarki aktif dapat digunakan. Secara internal, pemeriksaan ini akan memeriksa
apakah terdapat objek Holder
yang ada yang terkait dengan
hierarki tampilan tersebut. Informasi ini di-cache dalam tampilan di bagian atas
hierarki tersebut. Jika tidak ada objek tersebut, getHolderForHierarchy()
akan membuatnya.
Metode getHolderForHierarchy()
statis memungkinkan Anda menghindari meng-cache instance holder
di suatu tempat untuk pengambilan nanti, dan mempermudah pengambilan objek status
yang ada dari mana saja dalam kode (atau bahkan kode library, yang tidak akan
memiliki akses ke instance asli).
Perhatikan bahwa nilai yang ditampilkan adalah objek holder, bukan objek status itu sendiri. Nilai objek status di dalam holder ditetapkan hanya oleh JankStats. Artinya, jika aplikasi membuat objek JankStats untuk jendela yang berisi hierarki tampilan tersebut, objek status akan dibuat dan ditetapkan. Jika tidak, tanpa perlu JankStats melacak informasi, objek status tidak diperlukan, dan kode aplikasi atau library tidak diperlukan untuk memasukkan status.
Pendekatan ini memungkinkan untuk
mengambil holder yang dapat diisi oleh JankStats. Kode eksternal
dapat meminta holder kapan saja. Pemanggil dapat meng-cache objek Holder
ringan dan menggunakannya kapan saja untuk menetapkan status, bergantung pada nilai
properti state
internalnya, seperti pada kode contoh di bawah, dengan status hanya ditetapkan
jika properti status internal holder non-null:
val metricsStateHolder = PerformanceMetricsState.getHolderForHierarchy(binding.root)
// ...
metricsStateHolder.state?.putState("Activity", javaClass.simpleName)
Untuk mengontrol status UI/aplikasi, aplikasi dapat memasukkan (atau menghapus) status
dengan metode putState
dan removeState
. JankStats melakukan log stempel waktu untuk
panggilan ini. Jika frame tumpang-tindih dengan waktu mulai dan berakhir status,
JankStats akan melaporkan informasi tersebut bersama dengan data waktu untuk frame tersebut.
Untuk status apa pun, tambahkan dua informasi: key
(kategori status, seperti “RecyclerView”) dan value
(informasi tentang
apa yang terjadi saat itu, seperti “scrolling”).
Hapus status menggunakan metode removeState()
jika status tersebut tidak lagi
valid, untuk memastikan informasi yang salah atau menyesatkan tidak dilaporkan
dengan data frame.
Memanggil putState()
dengan key
yang ditambahkan sebelumnya akan menggantikan
value
status yang ada dengan status baru.
Versi putSingleFrameState()
API status menambahkan status yang hanya
di-log satu kali, pada frame yang dilaporkan berikutnya. Setelah itu,
sistem akan otomatis menghapusnya sehingga Anda tidak akan kehilangan status obsolete dalam kode
secara tidak sengaja. Perhatikan bahwa tidak ada singleFrame yang setara dengan
removeState()
karena JankStats menghapus status frame tunggal secara otomatis.
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")
}
}
}
}
Perhatikan bahwa kunci yang digunakan untuk status harus cukup bermakna guna memungkinkan
analisis nanti. Khususnya, karena status dengan key
yang sama seperti status yang
ditambahkan sebelumnya akan menggantikan nilai sebelumnya, Anda harus mencoba menggunakan
nama key
unik untuk objek yang mungkin memiliki instance berbeda di aplikasi atau library
Anda. Misalnya, sebuah aplikasi dengan lima RecyclerViews yang berbeda mungkin
ingin menyediakan kunci yang dapat diidentifikasi
untuk setiap kunci alih-alih hanya menggunakan
RecyclerView
untuk setiap kata kunci dan tidak dapat dengan mudah membedakannya
data yang dihasilkan instance mana yang dirujuk
oleh data {i>frame<i}.
Heuristik jank
Untuk menyesuaikan algoritme internal guna menentukan apa saja yang dianggap sebagai jank, gunakan properti jankHeuristicMultiplier
.
Secara default, sistem menentukan jank sebagai frame yang perlu dirender dua kali lebih lama dari kecepatan refresh saat ini. Sistem tidak memperlakukan jank sebagai sesuatu yang melebihi kecepatan refresh karena informasi terkait waktu rendering aplikasi tidak sepenuhnya jelas. Oleh karena itu, lebih baik menambahkan buffer dan hanya melaporkan masalah jika menyebabkan masalah performa yang terlihat.
Kedua nilai ini dapat diubah melalui metode ini agar lebih sesuai dengan situasi aplikasi, atau dalam pengujian untuk memaksa jank agar terjadi atau tidak terjadi, sebagaimana diperlukan untuk pengujian.
Penggunaan di Jetpack Compose
Saat ini ada sangat sedikit penyiapan yang diperlukan untuk menggunakan JankStats di Compose.
Untuk mempertahankan PerformanceMetricsState
di seluruh perubahan konfigurasi,
ingat seperti berikut:
/**
* 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) }
}
Untuk menggunakan JankStats, tambahkan status saat ini ke stateHolder
seperti yang ditampilkan di sini:
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")
}
}
}
Untuk mengetahui detail lengkap cara menggunakan JankStats dalam aplikasi Jetpack Compose, lihat aplikasi contoh performa kami.
Berikan masukan
Sampaikan masukan dan ide Anda kepada kami melalui resource berikut:
- Issue tracker
- Laporkan masalah agar kami dapat memperbaiki bug.
Direkomendasikan untuk Anda
- Catatan: teks link ditampilkan saat JavaScript nonaktif
- Membuat Profil Dasar Pengukuran {:#creating-profile-rules}
- Argumen Instrumentasi Microbenchmark
- Argumen Instrumentasi Macrobenchmark