安定性の問題を診断する

不要な再コンポジションや過剰な再コンポジションが原因でパフォーマンスの問題が発生している場合は、アプリの安定性をデバッグする必要があります。このガイドでは、そのための方法をいくつか紹介します。

Layout Inspector

Android Studio の Layout Inspector を使用すると、アプリ内で再コンポーズされているコンポーザブルを確認できます。Compose がコンポーネントを再コンポーズまたはスキップした回数が表示されます。

Layout Inspector での再コンポーズとスキップの回数

Compose Compiler レポート

Compose コンパイラは、安定性推論の結果を出力して検査できます。この出力を使用すると、スキップ可能なコンポーザブルとスキップできないコンポーザブルを特定できます。以下のサブセクションでは、これらのレポートの使用方法を要約していますが、詳細については技術ドキュメントをご覧ください。

設定

Compose コンパイラ レポートは、デフォルトでは有効になっていません。コンパイラ フラグを使用して有効にできます。正確な設定はプロジェクトによって異なりますが、Compose コンパイラ Gradle プラグインを使用するプロジェクトの場合は、各モジュールの build.gradle ファイルに以下を追加します。

  android { ... }

  composeCompiler {
    reportsDestination = layout.buildDirectory.dir("compose_compiler")
    metricsDestination = layout.buildDirectory.dir("compose_compiler")
  }

プロジェクトのビルド時に Compose コンパイラ レポートが生成されるようになります。

出力例

reportsDestination は 3 つのファイルを出力します。JetSnack の出力例を次に示します。

  • <modulename>-classes.txt: このモジュールのクラスの安定性に関するレポート。サンプル。
  • <modulename>-composables.txt: モジュール内のコンポーザブルの再開可能性とスキップ可能性に関するレポート。サンプル。
  • <modulename>-composables.csv: スプレッドシートにインポートしたり、スクリプトを使用して処理したりできる、コンポーザブル レポートの CSV バージョン。サンプル

コンポーザブル レポート

composables.txt ファイルには、パラメータの安定性、再起動可能かどうか、スキップ可能かどうかなど、特定のモジュールのコンポーズ可能な関数ごとに詳細が記載されています。JetSnack の架空の例を次に示します。

restartable skippable scheme("[androidx.compose.ui.UiComposable]") fun SnackCollection(
  stable snackCollection: SnackCollection
  stable onSnackClick: Function1<Long, Unit>
  stable modifier: Modifier? = @static Companion
  stable index: Int = @static 0
  stable highlight: Boolean = @static true
)

この例では、SnackCollection のすべてのパラメータが stable とマークされているため、Compose は構造的等価性(Object.equals())を使用して各パラメータを比較します。stable パラメータは、プリミティブ値や、.equals() チェックが低コストの小規模でフラットなデータクラスには最適ですが、コレクションを保持するモデル(List<Snack> をラップする SnackCollection など)を stable に強制すると、Compose は再コンポーズごとに各アイテムで .equals() を呼び出すことになります。強いスキップモードを有効にした場合は、コレクションを保持するモデルを unstable にしておくことをおすすめします。そうすると、Compose は代わりにインスタンスの等価性(===)を使用して比較します。

別の例として、強力なスキップモードを無効にして生成されたコードを考えてみましょう。

restartable scheme("[androidx.compose.ui.UiComposable]") fun HighlightedSnacks(
  stable index: Int
  unstable snacks: List<Snack>
  stable onSnackClick: Function1<Long, Unit>
  stable modifier: Modifier? = @static Companion
)

強いスキップモードが無効になっている場合(Kotlin 2.0.20 より前)、HighlightedSnacks コンポーザブルはスキップできません。unstable パラメータ snacks が原因で、パラメータが変更されていなくても、Compose は再コンポーズ中にスキップしません。

強いスキップモードを有効にすると(Kotlin 2.0.20 以降のデフォルト)、HighlightedSnacks は skippable とマークされ、Compose はインスタンスの等価性(===)を使用して unstable パラメータ snacks を比較します。その結果、Compose は、同じ List 参照が渡された場合は HighlightedSnacks をスキップし、新しく割り当てられた List インスタンスが渡された場合にのみ再コンポーズします。

クラスレポート

classes.txt ファイルには、指定されたモジュールのクラスに関する同様のレポートが含まれています。次のスニペットは、クラス Snack の出力です。

unstable class Snack {
  stable val id: Long
  stable val name: String
  stable val imageUrl: String
  stable val price: Long
  stable val tagline: String
  unstable val tags: Set<String>
  <runtime stability> = Unstable
}

参考までに、Snack の定義は次のとおりです。

data class Snack(
    val id: Long,
    val name: String,
    val imageUrl: String,
    val price: Long,
    val tagline: String = "",
    val tags: Set<String> = emptySet()
)

Compose コンパイラが Snack を不安定としてマークしました。これは、tags パラメータの型が Set<String> であるためです。これは MutableSet ではないため、不変型です。ただし、Set、List、Map などの標準コレクション クラスは、最終的にはインターフェースです。そのため、基盤となる実装は変更可能な場合があります。

たとえば、val set: Set<String> = mutableSetOf("foo") と記述できます。変数は定数であり、宣言された型は可変ではありませんが、その実装は依然として可変です。Compose コンパイラは、宣言された型のみを参照するため、このクラスの不変性を確認できません。そのため、tags は不安定とマークされます。