2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Android】Jetpack Compose Recomposition 完全解剖

2
Posted at

はじめに

Jetpack Compose が強力なのは、「状態が変わったところだけ UI を再実行する賢い再コンポーズ(Recomposition)」 にある。
しかし、「どこが・どう・どの順番で再実行されているか?」は表からは見えないため、ブラックボックスに感じる開発者も多い。

この記事では、Recomposition の内部処理を、SlotTable・Snapshot・依存関係の追跡ロジックまで踏み込んで解説する。

単なる「状態が変わると UI が更新される」ではなく
Compose がどのように 最小限の処理で UI を再構築しているのか を説明する。


1. Recomposition の全体構造をまず俯瞰する

Compose UI の実行は、大きく分けて3つのステージで構成されている:

  1. Composition
    → 初回の Composable 実行で UI ツリーを構築し SlotTable に記録
  2. Recomposition
    → 状態変化に応じて必要な部分だけ再実行
  3. Applier による差分反映
    → 実際の UI(LayoutNode など)へ更新

これらを司るのが Recomposer であり、
状態管理の要となるのが SnapshotSlotTable である。


2. 初回 Composition で行われること

setContent { MyScreen() } が呼ばれたときの処理は想像以上に複雑だが、重要なのは次の2点。

① Composable の実行結果が UI ツリーとして構築される

  • LayoutNode(Compose UI の低レベル構造)が生成され
  • Applier が UI に適用する

② SlotTable に「Composable の構造と remember の保存領域」を記録する

SlotTable には:

  • Composable の階層(Group)
  • キー (key())
  • remember の領域
  • 前回実行時の値
  • 引数情報

などが保存される。

SlotTable は言うなれば Compose の“巨大なノート”。


3. 状態を読むと「依存関係」が登録される

Compose の最大の特徴は:

State を“読んだ瞬間に”
「どの Composable がこの値に依存しているか」
が Snapshot システムに記録されること。

例:

@Composable
fun Counter() {
    var count by remember { mutableStateOf(0) }
    Text("Count: $count") // ← ここで依存関係が登録される
}

Snapshot システムは:

  • “誰が何を読んだか(Read Tracking)”
  • “誰が何を変更したか(Write Tracking)”

の物流を常時監視している。


4. 状態更新 → 再コンポーズ決定までの流れ

count++ が起きたとき、内部では次が起きる:

① mutableState の値が Snapshot に書き込まれる

内部処理:

  • 値が変化
  • 「このStateは変更された」とマーク

② Recomposer が変更通知(invalidation)を受け取る

Snapshot システムは

  • この State を読んでいた Composable のリスト
  • それが属するグループの位置(SlotTable の範囲)

を Recomposer の 再コンポーズ候補キュー に追加する。


③ 次のフレームで Recomposer が動き出す

Android の Choreographer(UI フレーム)と同期して、

「今 invalidation が来てるのはこの Composable グループだな」
→ SlotTable のその範囲だけ再実行

という極めて効率的な再実行が行われる。


④ Applier が差分を UI ツリーに反映

最終的に UI に適用されるのは:

  • 更新された Text
  • 再配置された Column
  • 新しい LayoutNode

など “必要な差分だけ”


5. Recomposition は「部分的」で「スキップ可能」

Compose の Recomposition は次のような最適化を行う:

① Group 単位の部分的な再コンポーズ

Composable は内部的に Group という単位に分解される。

Column {
    Header()     // Group A
    Content()    // Group B
    Footer()     // Group C
}

もし Content() の中の State が変わった場合:

  • A … 再コンポーズなし
  • B … 再コンポーズされる
  • C … 再コンポーズなし

UI の更新はこのグループ単位で行われる。


② 引数が変わっていない Composable はスキップされる

Compose は:

  • 前回実行時の引数
  • 今回実行時の引数
  • その型の安定性(@Stable / @Immutable

を比較して、

「この Composable はもう一度実行しなくていい」

と判断できるなら、まるごとスキップする。


③ remember が SlotTable の位置でマッピングされる

val state = remember { mutableStateOf(0) }

remember は SlotTable に次のような形で保存される:

  • 何番目の slot にあるか
  • その値を再コンポーズで再利用するか
  • 初期化ブロックを実行すべきか

このため、

remember を条件分岐内に置くと SlotTable の構造が変わり崩壊する

ので危ない。


6. derivedStateOf と再コンポーズ最適化

val filtered by remember {
    derivedStateOf { items.filter { it > 0 } }
}

derivedStateOf の特徴:

  • 依存している State が変わったときだけ再計算
  • その derived 値を読んでいる Composable だけ再コンポーズ対象になる

重い計算の最適化に最適。


7. 副作用(LaunchedEffect / SideEffect)の Recomposition ルール

副作用は Recomposition と密接に結びついている。


LaunchedEffect(key)

  • key が変わったときだけ再起動
  • 再コンポーズでは restart しない(key が同じなら)
LaunchedEffect(userId) {
    loadUser(userId)
}

SideEffect

  • Recomposition が正常に UI へ commit された瞬間に呼ばれる
  • ログや外部 API 通知に使う

DisposableEffect(key)

  • key 変更時、または Composable が Composition から外れる時
    onDispose が呼ばれる

8. Recomposition が“速い理由”まとめ

Compose が速い理由は次の5つ:

① SlotTable により「構造の差分」だけ追跡できる

DOM のようにツリー全体を探索しない。
“どこのグループがどこに対応しているか” を全て記録している。


② Snapshot による効率的な依存関係追跡

誰が何を読んだかが常に把握されているため:

→ “必要な部分だけ”
→ “必要なときだけ”

再実行される。


③ スキップ最適化が強力

引数比較 + Stable/Immutable アノテーションにより

  • 実行不要な Composable は完全スキップ
  • その子 Composable もまるごとスキップ

④ UI 差分のみを適用(リアル DOM diff より効率的)

Compose の Applier は “変わったところだけ差し替える” ことができる。
しかも SlotTable により探索が最小限。


⑤ 再コンポーズは idempotent & pure な関数として設計

Composable は UI を返す純粋関数であり、
副作用は外だし(Effect)という明確な分離がある。


まとめ

最後に Recomposition を一言でまとめると:

Compose は SlotTable と Snapshot で
「状態と UI の依存関係」を精密に管理し、
必要なグループだけをスキップ判定しながら
再実行する“差分更新エンジン”である。

この仕組みのおかげで、

  • 高速
  • 効率的
  • UI ロジックが明瞭
  • 副作用管理が楽
  • アーキテクチャが健全

という Compose の強みが成立している。

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?