Android / Programming

Jetpack ComposeがUIを描画するまでの流れ

こちらの記事は「Jetpack Compose Phases」の内容をまとめています。

ざっくり要約
Jetpack Composeは「Composition」、「Layout」、「Drawing」3つのフェーズを通してUIを描画しています。

Compositionフェーズ: コンポーザブル関数を実行して、UIをツリー構造で表したアウトプットを出力
Layoutフェーズ: 出力されたツリー構造を使用して高さ・幅・x座標・y座標を計測後、レイアウトノードに保持
Drwingフェーズ: 出力されたツリー構造を使用して描画

パフォーマンスの観点から、Layout・Drwingフェーズは、不要な場合は発生しません。

フェーズ詳細
Compositionコンポーザブル関数を実行して、UIをツリー構造で表したアウトプットを出力
Layout出力されたツリー構造を使用して、各コンポーネントの高さ・幅・x座標・y座標を計測
Drwing出力されたツリー構造を使用して描画

3つのフェーズ

Jetpack Composeは「Composition(構成・構築)」、「Layout(レイアウト)」、「Drawing(描画)」の3フェーズを通してUIを表示しています。

Composition(構成・構築)

UIとして何を表示するかを決定するフェーズです。コンポーザブル関数(@Composableが付いている関数)を実行し、表示するUIを決定します。

Layout(レイアウト)

UIの設置場所を決めます。このフェーズでは「measurement(測定)」と「placement(設置)」の2ステップを踏んでいます。ButtonやColumnなどの各要素は、自身の大きさと子要素の大きさを測定し、設置します。

Drawing(描画)

最後のフェーズでは描画の方法を決めます。

基本的に、これらは上記の順番で実行されるため、下の画像のように一方向のデータの流れとなります。

Jetpack Compose Phases – Figure1より

しかし、BoxWithConstraintsLazyColumn・LazyRowは、子要素のCompositionフェーズが親要素のLayoutフェーズに依存しているため、一方向のデータの流れにはなっていません。

これら3つのフェーズは毎フレームごとに発生することもあれば、パフォーマンスの観点から発生しないこともあります。
Jetpack Composeは、すべてのフェーズにおいて同じインプットから同じ結果を返す反復処理を避けるため、前回の内容を再利用できる場合はコンポーザブル関数の実行をスキップします。これにより、不要なLayoutフェーズとDrawingフェーズは発生しません。この最適化は、Jetpack Composeが各フェーズ内でのstate(状態)の読み取りを追跡しているため可能になっています。

各フェーズの深掘り

UI描画までの各フェーズがどのように行われているかをまとめています。

Compositionフェーズ

このフェーズではコンポーザブル関数を実行し、UI構成を表すツリー構造を生成します。
下記の動画はツリー構造のイメージです。緑色の丸をlayout node(レイアウトノード)と言い、次のフェーズで必要な情報を保持しています。

Jetpack Compose Phases – Figure 2より

実際のコードを使用し、簡単に表すと下記のようになります。動画内の緑の丸で示されていたものはコンポーザブル関数であり、各コンポーザブル関数がそれぞれノードになります。

Jetpack Compose Phases – Figure3より

Layoutフェーズ

このフェーズでは、先ほどのCompositionフェーズで出力したツリー構造を使用して、各ノードのサイズや位置を決定します。

Jetpack Compose Phases – Figure 4より

ツリー構造は「子要素の測定」、「子要素の測定による自サイズの決定」、「子要素の設置」に使用されます。
このフェーズの終了時点で各ノードは、高さ・幅・x座標・y座標の値を保持しています。

先ほどのツリー構造の画像を例にまとめると下記のとおりです。

Jetpack Compose Phases – Figure3より
  1. 最上部の親ノードRowは、子ノードのImageとColumnの大きさを順に測ります。
  2. Imageは子ノードを持っていないため、測定後にサイズをRowノードに報告します。
  3. Columnは子ノードを持っているため、先に子ノードの大きさを順に測ります。
  4. 左のTextは子ノードを持っていないため、測定後にサイズをColumnノードに報告します。
    1. 右のTextは子ノードを持っていないため、測定後にサイズをColumnノードに報告します。
  5. Columnは受け取った子ノードのサイズを使用して、自身のサイズを決定します。
    幅は子ノードの中の最大値を、高さは子ノードの合計値を使用します。
  6. Columnの子ノードを、自身を基準として上から順に設置していきます。
  7. Rowは受け取った子ノードのサイズを使用して、自身のサイズを決定します。
    幅は子ノードの合計値を、高さは子ノードの中の最大値を使用します。

Drawingフェーズ

先ほどのツリー構造を再度使用してスクリーンに描画していきます。

Jetpack Compose Phases – Figure 5より

先ほどの例を使用すると下記の順番で描画されます。

Jetpack Compose Phases – Figure3より
  1. Rowの背景色などのコンテンツを描画します。
  2. Imageを描画します。
  3. Columnを描画します。
  4. 左のTextを描画します。
  5. 右のTextを描画します。

Jetpack Compose Phases – Figure 6より

まとめ

Jetpack Composeを使用する際に、これらの概念を理解していなくてもUI構築は可能です。しかし、どのようにしてUIが描画されているかを知ることは、課題解決の糸口になると思います。
また、パフォーマンス向上などを行う際には、この概念なしでは難しくなるため、時間があるときに勉強しておくことをお勧めします。

私も勉強途中のため、偉そうに言える身分ではないですが。。。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です