このシリーズの前回の投稿を読んだ後、実際にどのようにwidgetを再構築する方法に疑問を抱いているかと思います。 各widgetには独自のビルド関数があり、頻繁に呼び出されます。 この再構築から完了までの間に、パフォーマンスの何パーセントを消費しましょうか?

これらすべてに答えられるために、Element TreeWidget Tree と Render Treeを理解する必要があります。そして、Flutterの build(){…}メソッドを実行する方法、画面上の情報を再構築または変更する方法も理解することも必要です。

本当に何が起こったのでしょうか?

Flutterは、build(){…} メソッドが呼び出されるたびに、UI全体を再描画または再作成するわけではありません。 それは、詳しく話しましょう。

  • Flutterで作成されたアプリケーションのデフォルト構成は60FPS(Frames Per Second)です。 つまり、Flutterシステムは毎秒60回インターフェイスを再描画します。
  • これは、Flutterが1秒間に60回レイアウト全体を再計算しなければならない場合にのみ、非効率になります。
  • Flutterが初めて画面に何かを描画する場合、画面上のすべてのコンポーネントの位置、色、テキストなどを計算する必要があります。要するに、最初のインターフェイスを描画する時、Flutterは画面上のそれぞれのピクセルを編集しなければなりません。
  • 次回の編集/描画では、何も変更されていない場合にUIを更新するために、Flutterは既存の古い情報を取得し、画面上に超高速かつ非常に効率的に描画します。
  • その為に、再描画の速度が問題になく、Flutterが更新のたびに画面上のすべてを再計算する必要がある場合にのみ、問題になります。
  • では、この記事で、Flutterがbuild(){…}メソッドが呼び出されるたびにすべてを再計算するかどうかという内容を詳細に理解しましょう!
Widget Tree‌‌
  • 簡単に言えば、Widget Tree というのは、アプリケーションの構築に使用されているすべてのWidgetです。つまり、書いたコードからwidget treeが作成されます。
  • それは完全に管理できます。widgetを宣言して、それらを一緒にネストすることで要望のレイアウトを作成します。
  • build(){…}メソッドを呼び出す時に、FlutterでWidget Treeがビルドされます。 Flutterシステムは、この部分を独自に処理します。
  • 画面に表示されるだけではありません。 代わりに、Flutterまでに次回画面に何を描画するかと指示します。 Widget treeは頻繁に再構築されます。
Element Tree‌‌
  • Element Tree はWidget Treeにバインドされ, 実際に表示されているオブジェクト/要素と設定される情報です。再構築することはめったにありません。
  • Element Tree は別の方法で管理され、build(){…} メソッドが呼び出されても再構築されません。
  • Widget Treeの各 Widgetで、FlutterはそのWidgetのelementを自動的に作成します。 FlutterがWidgetを初めて処理するとすぐに実行されます。
  • ここで、elementは、Flutterによってメモリ内で管理されるオブジェクトであると言えます。Widget TreeのWidgetに関連します。
  • Elementは、ターミナルインターフェイスパラメータがあるWidget(Widget Tree内)への参照を1つだけ保持します。
Render Tree‌‌
  • Render Tree は、画面に表示されるelement /オブジェクトを表します。
  • Render Tree も頻繁に再構築されません!
  • Element Tree は、Render Treeにもバインドされています。 Element TreeのElementは、画面に表示されるrender object を指しています。
  • Flutterは、何時でも以前にレンダリングされたことがないelementを検出すると、Widget Tree内のWidgetを参照して設定します。そして、element tree内にelement を作成します。
  • Flutterには、画面スペース、方向、サイズなどのものを計算して取得するlayout phaseもあります。
  • また、イベントなどを操作できるように、Widgetでlistenersを設定する別のphaseもあります。

簡単に言えば、レンダリングされていないelementが画面にレンダリングされます。後の Element (element tree内)には、画面上のRender Object (Render Tree内)へのポインターが作成されます。 また、Widget(Widget Tree内)への、このelementの構成情報を持っているポインターもあります。

‌‌ Flutterがbuild(){…}を実装する方法

build(){…}メソッドは、stateが変化するたびにFlutterによって呼び出されます。基本的に、Flutterの再構築をトリガーする条件は2つがあります。

  • 1つ目は、Stateful WidgetでsetState(){…}メソッドが呼び出される場合です。 自動的にsetState(){…}が呼び出される時に、すぐにbuild(){…}メソッドが呼び出されます。
  • 2つ目は、MediaQueryまたはTheme.of(…)コマンドが呼び出されるたびに、仮想キーボードが表示または非表示になります。 これらのデータが変更されるたびに、build(){…}メソッドが自動的にトリガーされます。

正確には、setState(){…}を呼び出すと、対応する要素がdirtyとしてマークされます。 毎秒60回行われる次のレンダリングでは、Flutterはbuild(){…}メソッドによって作成された新しい設定情報を確認し、画面を更新します。

DirtyとしてマークされたWidget内にネストされたすべてのWidgetには、新しいwidget/dartオブジェクトが作成されます。 それで、これらすべてのWidgetの新しいバージョンに対応する新しいWidget Treeが作成されます。

Widget Treeが不変なので、新しいWidget Treeが作成されます。つまり、既存のWidgetのプロパティを変更することはできずに、新しいWidgetでオーバーライドできます。 このメソッドは、オブジェクトが変更された時に変更をより効率的に検出できるため、Flutterによって大幅に実装されています。

例:

画面にStateful Widgetが表示されています。 テキスト内にネストされたChild Widgetがあります(Text Widget)。 Text WidgetはStateless Widgetであり、parent widgetのstateに基づいて画面にテキストを表示します。‌‌

Stateが変化するとすぐに、表示されるテキストがTrueからFalseに、またはその逆に変わります。GestureDetectorは、Text Widgetのparentです。 そのparentとして、GestureDetectorはsetState () {…}メソッドを呼び出すことで、stateの変更を処理します。

Text Widgetがクリックされるとすぐに、parentがsetStateを処理します。つまり、TestWidgetはdirtyとしてマークされます。Flutterでbuild(){…}メソッドが呼び出されると、新しいWidget Treeが作成されます。

次に、新しいWidget Tree情報が、画面に表示されている実際のコンテンツと比較されます。 Flutterは、コンテナの色が変更されずに、Text Widget内のテキストのみが変更されたことを検出します。 したがって、Render Treeは全てのコンテンツではなく、Text Widget内のテキストのみを表示します。‌‌

これはFlutterが効率的に機能する理由です。build(){…}メソッドが呼び出される時に、Element Treeを再構築する必要はありません。Widget Treeのみが再構築され、Element Treeは新しいWidget Treeへの参照を更新するだけです。 次に、Widgetの新しい情報があるかどうかを確認します。新しい情報がある場合は情報をRender Objectに渡して、変更を画面に描画できるようにします。

Logic renderについてもっと読みたい場合は、公式のFlutterドキュメントからこの記事を読んだほうがいいと思います。

クイックコメント

一部のWidgetは、Widget Treeを再構築した後でも変更されないので、ビルドプロセスをさらに最適化できます。 このWidgetが変更されないと通知をFlutterに渡せるために、それらの前にconstキーワードを使用できます。目的はFlutterがそのwidgetの再構築を完全にスキップするようにすることです。

例:

上記では、Padding Widgetの再構築をスキップするようにconstを指定しています。 また、大きなwidgetを小さなwidgetに慎重に分割することで、ビルドプロセスを最適化できます。

結論:

この記事では、Flutter仕様について詳しく説明しました。ここまで、Widgetの再構築プロセスの背後で何が起こっているのかを理解していただければ幸いです。

株式会社INTS 開発部 ハイ・アイン