Slide 1

Slide 1 text

JankStats LibraryでJankを
 検出しよう
 2023/07/11 ZOZO Tech Meetup - iOS/Android
 株式会社ZOZO
 ZOZOTOWN開発本部 ZOZOTOWNアプリ部 Android2ブロック
 Androidテックリード
 高橋啓太 Copyright © ZOZO, Inc. 1

Slide 2

Slide 2 text

© ZOZO, Inc. 2 株式会社ZOZO
 ZOZOTOWN開発本部 ZOZOTOWNアプリ部 Android2ブ ロック Androidテックリード
 高橋 啓太
 2020年新卒入社
 


Slide 3

Slide 3 text

© ZOZO, Inc. 話すこと
 3 ● JankStats Libraryの基本的な情報
 ● Jankとは
 ● Jank検出ツール
 ● JankStats Libraryの基本的な使用方法


Slide 4

Slide 4 text

© ZOZO, Inc. JankStats Libraryの基本的な情報
 4 ● アプリのレンダリングパフォーマンスに関する情報を取得することができる
 ○ 後述する「Jank」を検出可能
 ● FrameMetrics APIやOnPreDrawListenerの上に構築されている
 ● 2023/07/11時点ではApril 5, 2023リリースの1.0.0-alpha04が最新
 ● android/nowinandroidにも入っている
 
 Metrics | Jetpack | Android Developers: https://developer.android.com/jetpack/androidx/releases/metrics android/nowinandroid | GitHub: https://github.com/android/nowinandroid


Slide 5

Slide 5 text

© ZOZO, Inc. 5 Jankとは

Slide 6

Slide 6 text

© ZOZO, Inc. Jankとは
 6 遅いレンダリング | App quality | Android Developers: https://developer.android.com/topic/performance/vitals/render?hl=ja
 ● ユーザーがスムーズにアプリを操作するためには、各デバイスで決められたリフ レッシュレートを達成できるようフレーム生成時間を維持することが必要
 ○ 一般的には60fpsを達成することが必要
 ● なんらかの理由でフレームがスキップされ、アプリの動作がスムーズでなくなるこ とを「Jank(ジャンク)」と呼ぶ


Slide 7

Slide 7 text

© ZOZO, Inc. Jankとは - ANR等との関係性
 7 ジャンクのトラッキング | App quality | Android Developers: https://developer.android.com/topic/performance/vitals/tracking_jank?hl=ja
 
 遅いフレーム フリーズしたフレーム ANR 表示に要する時間 16ms~700ms 700ms~5s 5sより長い ユーザーが知覚できる現象の例 リストのスクロールがカクつく 画面遷移時がスムーズではない 画面をタップしても5秒以上反応がな い

Slide 8

Slide 8 text

© ZOZO, Inc. Jankとは - ANR等との関係性
 8 → これらは全てJankに分類される
 遅いフレーム フリーズしたフレーム ANR 表示に要する時間 16ms~700ms 700ms~5s 5sより長い ユーザーが知覚できる現象の例 リストのスクロールがカクつく 画面遷移時がスムーズではない 画面をタップしても5秒以上反応がな い ジャンクのトラッキング | App quality | Android Developers: https://developer.android.com/topic/performance/vitals/tracking_jank?hl=ja
 


Slide 9

Slide 9 text

© ZOZO, Inc. Jankとは - ANR等との関係性
 9 → これらは全てJankに分類される
  → Jankの検出・修正は、快適な操作感を維持するために重要
 遅いフレーム フリーズしたフレーム ANR 表示に要する時間 16ms~700ms 700ms~5s 5sより長い ユーザーが知覚できる現象の例 リストのスクロールがカクつく 画面遷移時がスムーズではない 画面をタップしても5秒以上反応がな い ジャンクのトラッキング | App quality | Android Developers: https://developer.android.com/topic/performance/vitals/tracking_jank?hl=ja
 


Slide 10

Slide 10 text

© ZOZO, Inc. 10 Jank検出ツール

Slide 11

Slide 11 text

© ZOZO, Inc. Perfetto
 11 ● ADBを介して、Android端末からフレームの情報を含むパフォーマンス情報を取得 するツール
 ● 取得したパフォーマンス情報はPerfetto UIで可視化して分析可能
 ● ZOZOTOWNではスクロール時のJankを分析する際に使用した
 Perfetto: https://perfetto.dev/ perfetto | Android Developers: https://developer.android.com/studio/command-line/perfetto?hl=ja Perfettoを用いたAndroidアプリのボトルネックの特定とその改善 | ZOZO TECH BLOG: https://techblog.zozo.com/entry/android-performance-improvement-with-perfetto

Slide 12

Slide 12 text

© ZOZO, Inc. AndroidStudio Profiler
 12 ● Chipmunk以降のAndroid StudioではProfilerでJankを検出可能
 ○ Android12以降の端末が必要
 
 Android Developers Japan Blog: Android Studio の CPU profiler で UI のジャンクを検出する https://android-developers-jp.googleblog.com/2022/07/spot-your-ui-jank-using-cpu-profiler-in-android-studio.html
 
 


Slide 13

Slide 13 text

© ZOZO, Inc. JankStats Library
 13 ● androidx.metrics.performance
 ● 下記の機能を提供する
 ○ Jankの識別
 ■ Jankの発生タイミングに関する情報を取得できる
 ○ Jankが発生した時点でのアプリの状態取得
 ■ Jankが発生した際にユーザーがどのような操作をしていたかを確認できる
 ○ 結果のレポート
 ■ フレームに関する情報(完了にかかった時間など)を取得できる


Slide 14

Slide 14 text

© ZOZO, Inc. JankStats Libraryの何が良いのか?
 14 ● 開発環境でのJank検出はPerfettoやAndroid StudioのProfilerで十分可能
 ● しかし、本番環境(ユーザーの手元)で実際に発生しているJankを検出することは できない
 ● JankStats Libraryをプロダクトに導入し、データを収集することで、本番環境で発 生しているJankの原因が分析可能になる
 ○ 取得したデータの保存やデータを送信する仕組みは提供されていない
 ※「保存とアップロードに関する詳細情報は、JankStats API アルファ版リリースでは提供されません」
 とあるので将来的になんらかのサポートが追加される可能性はある
 https://developer.android.com/topic/performance/jankstats?hl=ja
 


Slide 15

Slide 15 text

© ZOZO, Inc. 15 JankStats Libraryの基本的な使用方法

Slide 16

Slide 16 text

© ZOZO, Inc. 16 JankStats Libraryの基本的な使用方法 ※alpha版のため今後APIが変更される場合があります

Slide 17

Slide 17 text

© ZOZO, Inc. 導入
 17 implementation "androidx.metrics:metrics-performance:1.0.0-alpha04" 何はともあれimplementation


Slide 18

Slide 18 text

© ZOZO, Inc. class MainActivity : ComponentActivity() { private lateinit var jankStats: JankStats override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { ... } jankStats = JankStats.createAndTrack(window) { frameData -> if (frameData.isJank) { // Jankと判定されたフレームかどうか Log.v("Jank detected!", frameData.toString()) } } } } 基本的な使用方法 - 初期化
 18 JankStats.createAndTrackでJankStatsのインスタンスを生成
 


Slide 19

Slide 19 text

© ZOZO, Inc. class MainActivity : ComponentActivity() { private lateinit var jankStats: JankStats override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { ... } jankStats = JankStats.createAndTrack(window) { frameData -> if (frameData.isJank) { // Jankと判定されたフレームかどうか Log.v("Jank detected!", frameData.toString()) } } } } 基本的な使用方法 - 初期化
 
 JankStats.createAndTrackでJankStatsのインスタンスを生成
 
 19 JankStatsのインスタンス生成時にdecorViewが生成されていない場合、 IllegalStateExceptionが発生するので注意

Slide 20

Slide 20 text

© ZOZO, Inc. 基本的な使用方法 - 初期化
 
 20 class MainActivity : ComponentActivity() { private lateinit var jankStats: JankStats override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { ... } jankStats = JankStats.createAndTrack(window) { frameData -> if (frameData.isJank) { // Jankと判定されたフレームかどうか Log.v("Jank detected!", frameData.toString()) } } } } JankStats.createAndTrackの第二引数には、フレーム情報を受け取るコールバック (JankStats.OnFrameListener)を渡す


Slide 21

Slide 21 text

© ZOZO, Inc. 21 isTrackingEnabledでフレーム情報収集の有効/無効を切り替える(デフォルト値はtrue)
 class MainActivity : ComponentActivity() { ... override fun onResume() { super.onResume() ... jankStats.isTrackingEnabled = true } override fun onPause() { super.onPause() jankStats.isTrackingEnabled = false } } 基本的な使用方法 - 初期化
 


Slide 22

Slide 22 text

© ZOZO, Inc. 基本的な使用方法 - FrameData
 22 FrameData(フレームの情報)がLogcatに出力される


Slide 23

Slide 23 text

© ZOZO, Inc. 基本的な使用方法 - FrameData
 23 ● frameStartNanos
 ○ フレームの開始時刻(ns)
 ● frameDurationUiNanos
 ○ フレームの長さ(ns)
 ● isJank
 ○ ジャンクかどうかを示すフラグ
 ● states
 ○ アプリの状態 (StateInfo)
 JankStats ライブラリ | App quality | Android Developers: https://developer.android.com/topic/performance/jankstats?hl=ja#reporting
 
 


Slide 24

Slide 24 text

© ZOZO, Inc. 基本的な使用方法 - FrameData
 24 ● frameStartNanos
 ○ フレームの開始時刻(ns)
 ● frameDurationUiNanos
 ○ フレームの長さ(ns)
 ● isJank
 ○ ジャンクかどうかを示すフラグ
 ● states
 ○ アプリの状態 (StateInfo)
 JankStats ライブラリ | App quality | Android Developers: https://developer.android.com/topic/performance/jankstats?hl=ja#reporting
 
 


Slide 25

Slide 25 text

© ZOZO, Inc. 基本的な使用方法 - FrameData
 25

Slide 26

Slide 26 text

© ZOZO, Inc. 基本的な使用方法 - FrameData
 26 空だが...?

Slide 27

Slide 27 text

© ZOZO, Inc. @Composable fun rememberMetricsStateHolder(): PerformanceMetricsState.Holder { val view = LocalView.current return remember(view) { PerformanceMetricsState.getHolderForHierarchy(view) } } 基本的な使用方法 - PerformanceMetricsState
 27 PerformanceMetricsState.Holderを使用する


Slide 28

Slide 28 text

© ZOZO, Inc. 基本的な使用方法 - PerformanceMetricsState
 28 @Composable fun JankyScreen() { val holder = rememberMetricsStateHolder() val key = "画面名" LaunchedEffect(holder) { // 状態を設定 holder.state?.putState(key, "JankyScreen") } JankyLazyColumn() // Jankが発生するComposable } PerformanceMetricsState#putStateにkey-value形式で状態をアプリの設定する


Slide 29

Slide 29 text

© ZOZO, Inc. 基本的な使用方法 - PerformanceMetricsState
 29

Slide 30

Slide 30 text

© ZOZO, Inc. 基本的な使用方法 - PerformanceMetricsState
 30 フレーム情報と アプリの状態が対応する🎉

Slide 31

Slide 31 text

© ZOZO, Inc. 基本的な使用方法 - PerformanceMetricsState
 31 @Composable fun JankyScreen() { ... Column { Button(onClick = { // 無効になった状態を削除 holder.state?.removeState(key) }) { Text(text = "状態削除") } JankyLazyColumn() } }

Slide 32

Slide 32 text

© ZOZO, Inc. 基本的な使用方法 - PerformanceMetricsState
 32 ボタンを押す前 ボタンを押した後 状態の削除も明示的に行う必要がある


Slide 33

Slide 33 text

© ZOZO, Inc. 基本的な使用方法 - nowinandroidの場合
 33 nowinandroidではJankStatsを使用したJank検出用のComposableを作成し、
 画面遷移時とスクロール時にputState/removeStateを実行している


Slide 34

Slide 34 text

© ZOZO, Inc. 基本的な使用方法 - nowinandroidの場合
 34 nowinandroid | NiaAppState.kt:  https://github.com/android/nowinandroid/blob/main/app/src/main/java/com/google/samples/apps/nowinandroid/ui/NiaAppState.kt
 @Composable private fun NavigationTrackingSideEffect(navController: NavHostController) { TrackDisposableJank(navController) { metricsHolder -> val listener = NavController.OnDestinationChangedListener { _, destination, _ -> metricsHolder.state?.putState("Navigation", destination.route.toString()) } navController.addOnDestinationChangedListener(listener) onDispose { navController.removeOnDestinationChangedListener(listener) } } }

Slide 35

Slide 35 text

© ZOZO, Inc. 基本的な使用方法 - nowinandroidの場合
 35 nowinandroid | JankStatsExtensions.kt:  https://github.com/android/nowinandroid/blob/4d65946f95f55505a21a1651c2fd5587cd5bb533/core/ui/src/main/java/com/google/samples/apps/nowinandroid/core/ui/JankStatsExtensions.kt @Composable fun TrackScrollJank(scrollableState: ScrollableState, stateName: String) { TrackJank(scrollableState) { metricsHolder -> snapshotFlow { scrollableState.isScrollInProgress }.collect { isScrollInProgress -> metricsHolder.state?.apply { if (isScrollInProgress) { putState(stateName, "Scrolling=true") } else { removeState(stateName) } } } } }

Slide 36

Slide 36 text

© ZOZO, Inc. JankStats Libraryの使用方法まとめ
 36 ● フレーム情報の収集はコールバックを登録するだけなのでお手軽
 ● フレームの情報とアプリの状態を紐づけるには PerformanceMetricsState#putStateを使用する
 ● 無効になったアプリの状態はremoveStateで削除する
 ● 状態管理にはDisposableEffectやLifecycleObserverを活用するのが良さそう
 ※ putSingleFrameStateという1フレーム分の状態を登録し、自動で削除するAPIもあります(使い所が難しい)
 ※

Slide 37

Slide 37 text

© ZOZO, Inc. 37 まとめと感想

Slide 38

Slide 38 text

© ZOZO, Inc. まとめと感想
 38 ● Jankとはフレームがスキップされ、アプリがスムーズでなくなることを指す
 ● JankStats Libraryは本番環境で使用することで価値を発揮する
 ○ ユーザーの手元で発生しているJankが検出可能になる
 ○ Jankの分析に有用なデータを収集するためには、アプリのどのような情報をフ レームの情報に乗せるかが重要になりそう


Slide 39

Slide 39 text

No content