強力なスキップモード

強力なスキップは、Compose コンパイラで利用可能なモードです。有効にすると、次の 2 つの方法でコンパイラの動作が変更されます。

強いスキップモードを有効にする

Gradle モジュールで強力なスキップを有効にするには、Gradle 構成の composeCompiler ブロックに次のオプションを含めます。

composeCompiler {
   enableStrongSkipping = true
}

コンポーズ可能なスキップ機能

強力なスキップモードでは、スキップとコンポーズ可能な関数のスキップに関して、Compose コンパイラが通常適用する安定性ルールの一部が緩和されます。デフォルトでは、すべての引数が安定した値を持つコンポーズ可能な関数が、Compose コンパイラによってスキップ可能としてマークされます。ストロング スキップ モードはこれを変えます。

強力なスキップを有効にすると、再起動可能なコンポーズ可能な関数はすべてスキップ可能になります。これは、パラメータが不安定かどうかに関係なく適用されます。再起動できないコンポーズ可能な関数はスキップできません。

スキップするタイミング

再コンポーズの際にコンポーザブルをスキップするかどうかを決定するために、Compose は各パラメータの値を以前の値と比較します。比較のタイプは、パラメータの安定性によって異なります。

  • 不安定なパラメータはインスタンスの等価性(===)を使用して比較されます
  • Stable パラメータはオブジェクトの等価性(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 サイズへの影響はごくわずかです。

Now In Android で強力なスキップを有効にすると、APK のサイズが 4 KB 増加しました。サイズの違いは、対象アプリに存在するスキップ不可能なコンポーザブルの数に大きく依存しますが、比較的小さいものにする必要があります。