📝

Now in Androidの読み解き - JankStatsについて

2022/09/02に公開

はじめに

キャッチアップがてら、以下リポジトリを読んでいて気になって調べたことを残していきます。
https://github.com/android/nowinandroid
今回はJankStatsについて気になったので調べました

JankStatsとは

アプリのパフォーマンス、特に画面の描画周りの問題を追跡分析するのに役立つとのこと。

詳しくは公式の説明をご覧ください
https://developer.android.com/topic/performance/jankstats

Jankとはなに

Jank(ジャンク): 描画に時間がかかりすぎる状況のこと

時間がかかりすぎる状況とは
デフォルトではリフレッシュレートの2倍の時間がかかるフレームがジャンクとして定義
jankHeuristicMultiplierプロパティを使用変更することは可能です

リフレッシュレートとは
画面に表示されている内容を更新する回数
単位はHz(ヘルツ)で表し、1秒間に何回の行われたかを示してます

Pixel 6aの場合
リフレッシュレート: 60Hz (最大)
1 (秒間) / 60 (Hz) = 0.01666 の2倍なので
0.01666 * 2 = 0.033333
0.033333秒以上かかるとジャンクしているとみなされます

導入方法

ドキュメントと以下のソースコードを確認いただけるとわかるかと思います。

Stateの設定

導入時に一番重要なのはStateの設定になるかと思います。
設定することによりどの画面でどの操作をしている時に発生するかが把握できます。
Now In Androidでは3つのStateを設定しておりました。

  • 画面状態
  • スクロール状態
  • タブの状態

ドキュメントでの説明はこちら
metricsHolder.state?.putState(状態名, 状態の値)で設定できます。

// 現在の画面状態
JankMetricDisposableEffect(navController) { metricsHolder ->
    val listener = NavController.OnDestinationChangedListener { _, destination, _ ->
        metricsHolder.state?.putState("Navigation", destination.route.toString())
    }
    navController.addOnDestinationChangedListener(listener)
    onDispose {
        navController.removeOnDestinationChangedListener(listener)
    }
}

// スクロール状態
JankMetricEffect(scrollableState) { metricsHolder ->
    snapshotFlow { scrollableState.isScrollInProgress }.collect { isScrollInProgress ->
        metricsHolder.state?.apply {
            if (isScrollInProgress) {
                putState(stateName, "Scrolling=true")
            } else {
                removeState(stateName)
            }
        }
    }
}

// タブの状態
JankMetricDisposableEffect(tabState) { metricsHolder ->
    metricsHolder.state?.putState("Interests:TabState", "currentIndex:${tabState.currentIndex}")
    onDispose {
        metricsHolder.state?.removeState("Interests:TabState")
    }
}

できること/できないこと

できること

  • ジャンクに関するフレームごとの情報(FrameData)の取得

取得できる情報

- isjank: フレームでジャンクが発生したかどうかを示すブール値のフラグ
- frameDurationUiNanos: フレームの長さ(ナノ秒単位)
- frameStartNanos: フレームの開始時刻(ナノ秒単位)
- states: フレーム中のアプリの状態
Android 12以降を使用している場合は以下情報も取得可能
- frameDurationCpuNanos: フレームの GPU 以外の部分に費やされた時間を表示する
- frameOverrunNanos: フレーム期限を過ぎてから完了するまでにかかった時間を表示する

実際にアプリを動かして取得した内容は以下になります
ナノ秒(1秒の10億分の1) で表示されているの分かりにくいですが

FrameData(frameStartNanos=3062391870248, frameDurationUiNanos=4093295, frameDurationCpuNanos=22276087, frameOverrunNanos=22276795, isJank=true, states=[Interests:TabState: currentIndex:0, Navigation: interests_destination])
FrameData(frameStartNanos=3062395963543, frameDurationUiNanos=5753088, frameDurationCpuNanos=18803755, frameOverrunNanos=2137755, isJank=false, states=[Interests:TabState: currentIndex:0, Navigation: interests_destination])
FrameData(frameStartNanos=3062401716631, frameDurationUiNanos=4236922, frameDurationCpuNanos=18301547, frameOverrunNanos=1635714, isJank=false, states=[Interests:TabState: currentIndex:0, Navigation: interests_destination])

できないこと

  • データを集約
  • データの保存やアップロード
    ※アルファ版リリースでは提供しないとのことなので、今後そのような機能も追加されるかもしれないです。

集約や保存・アップロードをしたい場合は自前で作る必要がありますが、以下の実装を参考にすればよいとのことです。

感想

操作していて体感で遅い箇所を、数値としてみれるのはよさそうです。
ただ、データ量が膨大になるので集約部分をどのようにするかは課題に感じたが、UIテストと組み合わせて使えるとよい気がしました。

Discussion