強力なスキップは、Compose コンパイラで利用できるモードです。有効にすると、コンパイラの動作が次の 2 つの方法で変更されます。
強いスキップモードを有効にする
強制スキップは、Kotlin 2.0.20 でデフォルトで有効になっています。
2.0.20 より前のリリースで Gradle モジュールの強制スキップを有効にするには、Gradle 構成の composeCompiler
ブロックに次のオプションを含めます。
android { ... }
composeCompiler {
enableStrongSkippingMode = true
}
コンポーザブルのスキップ設定
強力なスキップ モードでは、スキップとコンポーズ可能な関数に関して、通常 Compose コンパイラによって適用される安定性に関するルールの一部が緩和されます。デフォルトでは、Compose コンパイラは、すべての引数に安定した値が含まれている場合、コンポーズ可能な関数をスキップ可能としてマークします。強いスキップモードでは、この動作が変更されます。
強制スキップを有効にすると、再起動可能なすべてのコンポーズ可能な関数をスキップできるようになります。これは、パラメータが不安定かどうかに関係なく適用されます。再起動できないコンポーズ可能な関数はスキップできません。
スキップするタイミング
再コンポーズ中にコンポーザブルをスキップするかどうかを判断するため、Compose は各パラメータの値を以前の値と比較します。比較のタイプは、パラメータの安定性に依存します。
- 不安定なパラメータは、インスタンスの等価性(
===
)を使用して比較されます。 - 安定したパラメータは、オブジェクトの等価性(
Object.equals()
)を使用して比較されます。
すべてのパラメータがこれらの要件を満たしている場合、Compose は再コンポーズ中にコンポーザブルをスキップします。
コンポーザブルで強制スキップを無効にしたい場合があります。つまり、再起動可能でスキップ不可のコンポーザブルが必要な場合があります。この場合は、@NonSkippableComposable
アノテーションを使用します。
@NonSkippableComposable
@Composable
fun MyNonSkippableComposable {}
クラスを安定としてアノテーションする
インスタンスの等価性ではなくオブジェクトの等価性を使用するオブジェクトが必要な場合は、引き続き指定されたクラスに @Stable
のアノテーションを付けます。たとえば、オブジェクトのリストをすべて監視する場合、Room などのデータソースは、リスト内のいずれかが変更されるたびに、リスト内のすべてのアイテムに新しいオブジェクトを割り当てます。
ラムダのメモ化
強いスキップモードでは、コンポーザブル内のラムダのメモ化も可能になります。強力なスキップが有効になっている場合、コンポーズ可能な関数内のすべてのラムダが自動的に記憶されます。
例
強制スキップを使用するときにコンポーザブル内のラムダのメモ化を実現するため、コンパイラはラムダを remember
呼び出しでラップします。ラムダのキャプチャでキーが設定されます。
次の例のようなラムダがある場合を考えてみましょう。
@Composable
fun MyComposable(unstableObject: Unstable, stableObject: Stable) {
val lambda = {
use(unstableObject)
use(stableObject)
}
}
強力なスキップを有効にすると、コンパイラはラムダを remember
呼び出しでラップしてメモ化します。
@Composable
fun MyComposable(unstableObject: Unstable, stableObject: Stable) {
val lambda = remember(unstableObject, stableObject) {
{
use(unstableObject)
use(stableObject)
}
}
}
キーは、コンポーズ可能な関数と同じ比較ルールに従います。ランタイムは、インスタンスの等価性を使用して不安定なキーを比較します。オブジェクトの等価性を使用して安定したキーを比較します。
メモ化と再コンポーズ
この最適化により、再コンポーズ中にランタイムがスキップするコンポーザブルの数は大幅に増加します。メモ化がないと、再コンポーズ中にラムダ パラメータを取るコンポーザブルに、ランタイムが新しいラムダを割り当てる可能性が高くなります。その結果、新しいラムダには、最後のコンポジションと異なるパラメータが設定されます。これにより、再コンポーズが行われます。
メモ化を避ける
メモ化しないラムダがある場合は、@DontMemoize
アノテーションを使用します。
val lambda = @DontMemoize {
...
}
APK サイズ
スキップ可能なコンポーザブルをコンパイルすると、スキップできないコンポーザブルよりも生成されるコードが多くなります。強制スキップを有効にすると、コンパイラはほぼすべてのコンポーザブルをスキップ可能としてマークし、すべてのラムダを remember{...}
でラップします。そのため、強いスキップモードを有効にしても、アプリの APK サイズへの影響は非常に小さくなります。
Android で今で強制スキップを有効にすると、APK サイズが 4 kB 増加しました。サイズの差は、以前はスキップできなかったコンポーズ可能な関数がアプリに存在していた数に大きく依存しますが、比較的小さな差になるはずです。