Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Inside Jetpack Compose

takahirom
October 21, 2021

Inside Jetpack Compose

takahirom

October 21, 2021
Tweet

More Decks by takahirom

Other Decks in Programming

Transcript

  1. 自分について  takahirom • Takahiro Menju • Androidに関する技術が好き • DroidKaigi co-organizer

    • Google Developers Expert for Android • CyberAgent.Inc ABEMA • Twitter takahirom (@new_runnable) • GitHub takahirom
  2. Jetpack Composeは Android以外でも使える WebやDesktopでも同じ仕組みを使っ ているプロダクトが存在する Compose for Web Compose for

    Desktop Mosaic (Compose for console UI) すごくシンプルな Jetpack Composeを使うツールを 用意することで 仕組みを知りやすくできるのでは?
  3. シンプルに木構造をコンソールに出力する • stateの変更をどうやって検知している の?🤔 • 変更があったときに再実行が必要な場 所ってどうやって見つけているの? 🤔 • MutableStateを非同期で

    変更したら可変ステートだから危ないん じゃないの?🤔 • Node2()もう一回実行されちゃいそうだけ ど差分で実行されてないんじゃない? 🤔 → あとで説明します まずはJetpack Composeで このコードを動かすための他の部品について説 明します。 https://github.com/takahirom/simple-compose-for-learning-inside-compose
  4. シンプルに木構造をコンソールに出力する に必要なコードたち Nodeクラス • 木構造を作るので、そのための Nodeク ラスを用意します。 • ただのシンプルなクラスで、何も Jetpack

    Composeのクラスなどを継承したりして いません。 • 子ノードへの参照(children)を持っていて 木が作れるようになっています。 https://github.com/takahirom/simple-compose-for-learning-inside-compose
  5. シンプルに木構造をコンソールに出力する に必要なコードたち NodeをComposeにEmitするComposable関数 • NodeをEmitするとComposeの中でNodeが 追加、管理される • ReusableComposableNode()というものを使 う ◦

    “Reusable” については ”ReusableContent”(LazyColumnなど で利用される)とセットで 意味を成すとみられるので、 一旦無視。 • ReusableComposableNode()の引数 • Nodeを作るfactoryを渡す • 変更があったときに利用するラムダを updateで渡す • ここで先程作ったNodeApplierを指定する • 同様のコードをNode2に対してもにも書く https://github.com/takahirom/simple-compose-for-learning-inside-compose
  6. ステップを追って見ていこう 0. Composable関数をCompose Kotlin Compiler PluginでSlotTableを作れるようにする 1. Composable関数を呼び出して、情報を SlotTableに格納する 2.

    3秒後のMutableStateへの変更 3. Snapshot Systemが変更をキャッチ 4. Recompose 5. GapBufferを使ったSlotTableへの反映 ビルド時 : 実行時: https://github.com/takahirom/inside-jetpack-compose-diagram
  7. ステップ0. Composable関数をCompose Kotlin Compiler PluginでSlotTableを作れるようにする 0. Composable関数をCompose Kotlin Compiler PluginでSlotTableを作れるようにする

    1. Composable関数を呼び出して、情報を SlotTableに格納する 2. 3秒後のMutableStateへの変更 3. Snapshot Systemが変更をキャッチ 4. Recompose 5. GapBufferを使ったSlotTableへの反映 ビルド時 : 実行時: https://github.com/takahirom/inside-jetpack-compose-diagram
  8. ステップ0. Composable関数をCompose Kotlin Compiler PluginでSlotTableを作れるようにする 最近KotlinコンパイラではJavaバイトコードに変換する とき一度Kotlin IRと呼ばれる中間表現に変換するよう になりました。 このKotlin

    IRをKotlin Compiler Pluginで書き換えるこ とができます。 これによってJavaScriptなどにも変換できるのでマルチ プラットフォームにも対応できます。
  9. ステップ1. Composable関数を呼び出して、情報を SlotTableに格納する 0. Composable関数をCompose Kotlin Compiler PluginでSlotTableを作れるようにする 1. Composable関数を呼び出して、情報を

    SlotTableに格納する 2. 3秒後のMutableStateへの変更 3. Snapshot Systemが変更をキャッチ 4. Recompose 5. GapBufferを使ったSlotTableへの反映 ビルド時 : 実行時: https://github.com/takahirom/inside-jetpack-compose-diagram
  10. SlotTableのgroupsは何が入っているか? index: 0, key: 100, groupInfo: 2, parentAnchor: -1, size:

    16, dataAnchor: 0 index: 1, key: 1000, groupInfo: 2, parentAnchor: 0, size: 15, dataAnchor: 1 index: 2, key: 200, groupInfo: 536870914, parentAnchor: 1, size: 14, dataAnchor: 1 index: 3, key: -985533309, groupInfo: 2, parentAnchor: 2, size: 13, dataAnchor: 2 index: 4, key: -337788314, groupInfo: 268435458, parentAnchor: 3, size: 12, dataAnchor: 4 index: 5, key: -3687241, groupInfo: 268435456, parentAnchor: 4, size: 1, dataAnchor: 6 index: 6, key: -3686930, groupInfo: 268435456, parentAnchor: 4, size: 1, dataAnchor: 8 index: 7, key: 1036442245, groupInfo: 268435456, parentAnchor: 4, size: 2, dataAnchor: 11 index: 8, key: -3686930, groupInfo: 268435456, parentAnchor: 7, size: 1, dataAnchor: 12 index: 9, key: -337788167, groupInfo: 1, parentAnchor: 4, size: 4, dataAnchor: 15 index: 10, key: 1815931657, groupInfo: 1, parentAnchor: 9, size: 3, dataAnchor: 15 index: 11, key: 1546164276, groupInfo: 268435457, parentAnchor: 10, size: 2, dataAnchor: 16 index: 12, key: 125, groupInfo: 1073741824, parentAnchor: 11, size: 1, dataAnchor: 17 index: 13, key: 1815931930, groupInfo: 1, parentAnchor: 4, size: 3, dataAnchor: 19 index: 14, key: 1546164276, groupInfo: 268435457, parentAnchor: 13, size: 2, dataAnchor: 20 index: 15, key: 125, groupInfo: 1073741824, parentAnchor: 14, size: 1, dataAnchor: 21 index: 16, key: 0, groupInfo: 0, parentAnchor: 0, size: 0, dataAnchor: 0 index: 17, key: 0, groupInfo: 0, parentAnchor: 0, size: 0, dataAnchor: 0 index: 18, key: 0, groupInfo: 0, parentAnchor: 0, size: 0, dataAnchor: 0 index: 19, key: 0, groupInfo: 0, parentAnchor: 0, size: 0, dataAnchor: 0 index: 20, key: 0, groupInfo: 0, parentAnchor: 0, size: 0, dataAnchor: 0 index: 21, key: 0, groupInfo: 0, parentAnchor: 0, size: 0, dataAnchor: 0 index: 22, key: 0, groupInfo: 0, parentAnchor: 0, size: 0, dataAnchor: 0 index: 23, key: 0, groupInfo: 0, parentAnchor: 0, size: 0, dataAnchor: 0 1行1行が1グループになっている
  11. SlotTableのgroupsは何が入っているか? index: 0, key: 100, groupInfo: 2, parentAnchor: -1, size:

    16, dataAnchor: 0 index: 1, key: 1000, groupInfo: 2, parentAnchor: 0, size: 15, dataAnchor: 1 index: 2, key: 200, groupInfo: 536870914, parentAnchor: 1, size: 14, dataAnchor: 1 index: 3, key: -985533309, groupInfo: 2, parentAnchor: 2, size: 13, dataAnchor: 2 index: 4, key: -337788314, groupInfo: 268435458, parentAnchor: 3, size: 12, dataAnchor: 4 index: 5, key: -3687241, groupInfo: 268435456, parentAnchor: 4, size: 1, dataAnchor: 6 index: 6, key: -3686930, groupInfo: 268435456, parentAnchor: 4, size: 1, dataAnchor: 8 index: 7, key: 1036442245, groupInfo: 268435456, parentAnchor: 4, size: 2, dataAnchor: 11 index: 8, key: -3686930, groupInfo: 268435456, parentAnchor: 7, size: 1, dataAnchor: 12 index: 9, key: -337788167, groupInfo: 1, parentAnchor: 4, size: 4, dataAnchor: 15 index: 10, key: 1815931657, groupInfo: 1, parentAnchor: 9, size: 3, dataAnchor: 15 index: 11, key: 1546164276, groupInfo: 268435457, parentAnchor: 10, size: 2, dataAnchor: 16 index: 12, key: 125, groupInfo: 1073741824, parentAnchor: 11, size: 1, dataAnchor: 17 index: 13, key: 1815931930, groupInfo: 1, parentAnchor: 4, size: 3, dataAnchor: 19 index: 14, key: 1546164276, groupInfo: 268435457, parentAnchor: 13, size: 2, dataAnchor: 20 index: 15, key: 125, groupInfo: 1073741824, parentAnchor: 14, size: 1, dataAnchor: 21 index: 16, key: 0, groupInfo: 0, parentAnchor: 0, size: 0, dataAnchor: 0 index: 17, key: 0, groupInfo: 0, parentAnchor: 0, size: 0, dataAnchor: 0 index: 18, key: 0, groupInfo: 0, parentAnchor: 0, size: 0, dataAnchor: 0 index: 19, key: 0, groupInfo: 0, parentAnchor: 0, size: 0, dataAnchor: 0 index: 20, key: 0, groupInfo: 0, parentAnchor: 0, size: 0, dataAnchor: 0 index: 21, key: 0, groupInfo: 0, parentAnchor: 0, size: 0, dataAnchor: 0 index: 22, key: 0, groupInfo: 0, parentAnchor: 0, size: 0, dataAnchor: 0 index: 23, key: 0, groupInfo: 0, parentAnchor: 0, size: 0, dataAnchor: 0 親を指し示すAnchorを持っているので 木構造が作られている。
  12. SlotTableのgroupsは何が入っているか? index: 0, key: 100, groupInfo: 2, parentAnchor: -1, size:

    16, dataAnchor: 0 index: 1, key: 1000, groupInfo: 2, parentAnchor: 0, size: 15, dataAnchor: 1 index: 2, key: 200, groupInfo: 536870914, parentAnchor: 1, size: 14, dataAnchor: 1 index: 3, key: -985533309, groupInfo: 2, parentAnchor: 2, size: 13, dataAnchor: 2 index: 4, key: -337788314, groupInfo: 268435458, parentAnchor: 3, size: 12, dataAnchor: 4 index: 5, key: -3687241, groupInfo: 268435456, parentAnchor: 4, size: 1, dataAnchor: 6 index: 6, key: -3686930, groupInfo: 268435456, parentAnchor: 4, size: 1, dataAnchor: 8 index: 7, key: 1036442245, groupInfo: 268435456, parentAnchor: 4, size: 2, dataAnchor: 11 index: 8, key: -3686930, groupInfo: 268435456, parentAnchor: 7, size: 1, dataAnchor: 12 index: 9, key: -337788167, groupInfo: 1, parentAnchor: 4, size: 4, dataAnchor: 15 index: 10, key: 1815931657, groupInfo: 1, parentAnchor: 9, size: 3, dataAnchor: 15 index: 11, key: 1546164276, groupInfo: 268435457, parentAnchor: 10, size: 2, dataAnchor: 16 index: 12, key: 125, groupInfo: 1073741824, parentAnchor: 11, size: 1, dataAnchor: 17 index: 13, key: 1815931930, groupInfo: 1, parentAnchor: 4, size: 3, dataAnchor: 19 index: 14, key: 1546164276, groupInfo: 268435457, parentAnchor: 13, size: 2, dataAnchor: 20 index: 15, key: 125, groupInfo: 1073741824, parentAnchor: 14, size: 1, dataAnchor: 21 index: 16, key: 0, groupInfo: 0, parentAnchor: 0, size: 0, dataAnchor: 0 index: 17, key: 0, groupInfo: 0, parentAnchor: 0, size: 0, dataAnchor: 0 index: 18, key: 0, groupInfo: 0, parentAnchor: 0, size: 0, dataAnchor: 0 index: 19, key: 0, groupInfo: 0, parentAnchor: 0, size: 0, dataAnchor: 0 index: 20, key: 0, groupInfo: 0, parentAnchor: 0, size: 0, dataAnchor: 0 index: 21, key: 0, groupInfo: 0, parentAnchor: 0, size: 0, dataAnchor: 0 index: 22, key: 0, groupInfo: 0, parentAnchor: 0, size: 0, dataAnchor: 0 index: 23, key: 0, groupInfo: 0, parentAnchor: 0, size: 0, dataAnchor: 0 Slotのデータを指し示す dataAnchorを 持っている slots: Arrray<Any?>
  13. SlotTable#asString() Group(0) key=100, nodes=2, size=16, slots=[0: {}] Group(1) key=1000, nodes=2,

    size=15 Group(2) key=200, nodes=2, size=14 objectKey=OpaqueKey(key=provider) Group(3) key=-985533309, nodes=2, size=13, slots=[2: androidx.compose.runtime.RecomposeScopeImpl@4fb4ae6, androidx.compose.runtime.i Group(4) key=-337788314, nodes=2, size=12 aux=C(Content), slots=[5: androidx.compose.runtime.RecomposeScopeImpl@b882ad4] Group(5) key=-3687241, nodes=0, size=1 aux=C(remember):Composables.kt#9igjgp, slots=[7: MutableState(value=false)@167707773] Group(6) key=-3686930, nodes=0, size=1 aux=C(remember)P(1):Composables.kt#9igjgp, slots=[9: MutableState(value=false)@167707773, Fun Group(7) key=1036442245, nodes=0, size=2 aux=C(LaunchedEffect)P(1)336@14101L58:Effects.kt#9igjgp Group(8) key=-3686930, nodes=0, size=1 aux=C(remember)P(1):Composables.kt#9igjgp, slots=[13: kotlin.Unit, androidx.compose.runtime.Lau Group(9) key=-337788167, nodes=1, size=4 Group(10) key=1815931657, nodes=1, size=3, slots=[15: androidx.compose.runtime.RecomposeScopeImpl@7421fc3] Group(11) key=1546164276, nodes=1, size=2 aux=C(ReusableComposeNode):Composables.kt#9igjgp Group(12) key=125, nodes=0, size=1 node=Node1(name=node1), slots=[18: node1] Group(13) key=1815931930, nodes=1, size=3, slots=[19: androidx.compose.runtime.RecomposeScopeImpl@81cf51f] Group(14) key=1546164276, nodes=1, size=2 aux=C(ReusableComposeNode):Composables.kt#9igjgp Group(15) key=125, nodes=0, size=1 node=Node2(name=node2), slots=[22: node2] SlotTable#asString()を呼ぶと木構造が分かる文字列を 作って返してくれる このSlotTableというクラスに上記データが起動後に保持される
  14. 0. Composable関数をCompose Kotlin Compiler PluginでSlotTableを作れるようにする 1. Composable関数を呼び出して、情報を SlotTableに格納する 2. 3秒後のMutableStateへの変更

    3. Snapshot Systemが変更をキャッチ 4. Recompose 5. GapBufferを使ったSlotTableへの反映 ビルド時 : 実行時: ステップ 2. 3秒後のMutableStateへの変更 ステップ 3. Snapshot Systemが変更をキャッチ https://github.com/takahirom/inside-jetpack-compose-diagram
  15. ステップ 2. 3秒後のMutableStateへの変更 ステップ 3. Snapshot Systemが変更をキャッチ `remember{}`はインスタンスの生存期間を伸ばしてくれ るもので、 `by`

    はKotlinのDelegated Propertyによるも ので、 細かいところは違いますが、基本的には同じです。 だいたい同じ LaunchedEffectによる別の場所からの MutableStateの変更を どのようにJetpack Composeがキャッチしているのか?
  16. Snapshot Systemについて学ぼう この仕組みを使って Jetpack Composeは フレームごとに溜まった変更を処理していきます。 LaunchedEffect{}内でのMutableStateへの変更もここ で見つかります。 フレームの間はいいとして Composeが実際にRecompose中、

    つまりContent()などを呼んでいる時に別スレッドから MutableState が変更されたらどうなってしまうのか? 実際のComposeのコードで変更を受け取るデバッグ画面 フレームごとに溜まった変更を 処理できそう!
  17. Snapshot Systemとはなんなのか? multiversion concurrency control (MVCC)と呼ばれるもののようです。 https://ja.wikipedia.org/wiki/MultiVersion_Concurrency_Control > MultiVersion Concurrency

    Control (MVCC, マルチバージョン コンカレンシー コントロール) は、データ ベース管理システムの可用性を向上させる制御技術のひとつ。複数のユーザから同時に処理要求が行われ た場合でも同時並行性を失わずに処理し、かつ情報の一貫性を保証する仕組みが提供される。日本では多 版型同時実行制御、多重バージョン並行処理制御などと訳される。また単にマルチバージョンとも呼ばれる。 データベースでよく利用される技術のようです。 Snapshotクラスのapply()関数のコメントには以下の論文へのリンクもあります。 https://arxiv.org/pdf/1412.2324.pdf
  18. ステップ 4. Recompose 0. Composable関数をCompose Kotlin Compiler PluginでSlotTableを作れるようにする 1. Composable関数を呼び出して、情報を

    SlotTableに格納する 2. 3秒後のMutableStateへの変更 3. Snapshot Systemが変更をキャッチ 4. Recompose 5. GapBufferを使ったSlotTableへの反映 ビルド時 : 実行時: https://github.com/takahirom/inside-jetpack-compose-diagram
  19. 0. Composable関数をCompose Kotlin Compiler PluginでSlotTableを作れるようにする 1. Composable関数を呼び出して、情報を SlotTableに格納する 2. 3秒後のMutableStateへの変更

    3. Snapshot Systemが変更をキャッチ 4. Recompose 5. GapBufferを使ったSlotTableへの反映 ビルド時 : 実行時: ステップ 5. GapBufferを使ったSlotTableへの反映 https://github.com/takahirom/inside-jetpack-compose-diagram
  20. ステップ 5. GapBufferを使ったSlotTableへの反映 Gap bufferとは? > A gap buffer in

    computer science is a dynamic array that allows efficient insertion and deletion operations clustered near the same location. Gap buffers are especially common in text editors, where most changes to the text occur at or near the current location of the cursor. https://en.wikipedia.org/wiki/Gap_buffer 同じ場所にたくさんのデータの挿入や削除がある場合に効率的に操作できる。 カーソルの近くで変更が多く起きるテキストエディタでよく用いられる。
  21. まとめ Kotlin Compiler Pluginによる Kotlin IRを利用したComposable関数への変更 Snapshot System(MVCC)を利用した MutableStateの購読場所の保持、 MutableStateの変更の検知

    Composable関数の再度呼び出し (Recompose) SlotTableとの比較による実行スキップ (Donut-hole skipping) Gap bufferのデータ構造、アルゴリズムを利用した SlotTableの更新 ビルド時 : 実行時: https://github.com/takahirom/inside-jetpack-compose-diagram Jetpack Composeはすごく面白い!!!
  22. 参考 Code Reading Materials https://qiita.com/takahirom/items/b29b7db652efe277498a https://qiita.com/takahirom/items/d2a89560f8ff2065a7c0 https://qiita.com/takahirom/items/0e72bee081de8cf4f05f https://qiita.com/takahirom/items/11e3ed72eb2f83440b12 https://qiita.com/takahirom/items/0e0a3559d95b49399c3b https://qiita.com/takahirom/items/8e978eeb6d85bf48a330

    https://qiita.com/takahirom/items/64bd9aa3278035671558 References CustomTreeCompositionSamples https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/runtime/runtim e/samples/src/main/java/androidx/compose/runtime/samples/CustomTreeCompositionSamples.kt Jetpack Compose Internals https://jorgecastillo.dev/book/ Under the hood of Jetpack Compose https://medium.com/androiddevelopers/under-the-hood-of-jetpack-compose-part-2-of-2-37b2c20c6cdd Introduction to the Compose Snapshot system https://dev.to/zachklipp/introduction-to-the-compose-snapshot-system-19cn What is “donut-hole skipping” in Jetpack Compose? https://www.jetpackcompose.app/articles/donut-hole-skipping-in-jetpack-compose