blog

Flutterページ更新プロセス解析

ページを更新する必要があるときは、setStateメソッドを呼び出すことになりますが、ここではこれを突破口として、ページの更新プロセスを勉強していきます。 ここでのメソッドは、JavaのJNIメソッド...

Mar 8, 2020 · 7 min. read
シェア

ブロガー関連記事一覧

Flutter フレームワーク実装原則

Flutterイベントハンドリングソースコード解析

フラッタールーティングのソースコード解析

Flutterアンドロイドプラットフォームのソースコードの解析

Flutter ルーティングソースコード解析

Flutterページ更新プロセスの解剖学

更新プロセス

ページを更新する必要がある場合、ここでは突破口としてsetStateメソッドを呼び出し、ページ更新のプロセスを勉強します。

flutter\lib\src\widgets\framework.dart

/// [State]
void setState(VoidCallback fn) {
 final dynamic result = fn() as dynamic;
 _element.markNeedsBuild();
}

最初の行は入力されたクロージャを実行し、続いてmarkNeedsBuild()メソッドを呼び出します。

/// [Element]
void markNeedsBuild() {
 if (!_active)
 return;
 if (dirty)
 return;
 // 現在の要素を_dirty,再構築の必要性を示す
 _dirty = true;
 // 現在のエレメントをダーティエレメントリストに追加する。[WidgetsBinding.drawFrame] [buildScope]をクリックすると再構築される。
 owner.scheduleBuildFor(this);
}

BuildOwnerのscheduleBuildForメソッドはここで呼び出されます。

/// [BuildOwner]
void scheduleBuildFor(Element element) {
 if (element._inDirtyList) {
 _dirtyElementsNeedsResorting = true;
 return;
 }
 if (!_scheduledFlushDirtyElements && onBuildScheduled != null) {
 _scheduledFlushDirtyElements = true;
 //  _handleBuildScheduled 
 onBuildScheduled();
 }
 // ダーティエレメントリストに追加する
 _dirtyElements.add(element);
 element._inDirtyList = true;
}

WidgetsBindingの初期化については、「フレームワーク・スタートアップのソースコード分解」の記事で解説しています。

/// [WidgetsBinding]
void initInstances() {
 super.initInstances();
 _instance = this;
 _buildOwner = BuildOwner();
 buildOwner.onBuildScheduled = _handleBuildScheduled;
 /// ......省略......
}
void _handleBuildScheduled() {
 // ダーティな要素がビルドされている場合、変更は新しいフレームをトリガーしてはならない。
 ensureVisualUpdate();
}
// オブジェクトが現在フレームを生成していない場合は、scheduleFrameを使って新しいフレームをスケジュールする。
void ensureVisualUpdate() {
 switch (schedulerPhase) {
 case SchedulerPhase.idle:
 case SchedulerPhase.postFrameCallbacks:
 scheduleFrame();
 return;
 case SchedulerPhase.transientCallbacks:
 case SchedulerPhase.midFrameMicrotasks:
 case SchedulerPhase.persistentCallbacks:
 return;
 }
}
/// [SchedulerBinding]
void scheduleFrame() {
 // アプリケーションがフォアグラウンドで表示されているとき,_framesEnabledもし
 // ここで、アプリがバックグラウンドに切り替わると、直接
 if (_hasScheduledFrame || !framesEnabled)
 return;
 ensureFrameCallbacksRegistered();
 // 実行エンジンのスケジューリングメソッド
 window.scheduleFrame();
 _hasScheduledFrame = true;
}
void ensureFrameCallbacksRegistered() {
 window.onBeginFrame ??= _handleBeginFrame;
 window.onDrawFrame ??= _handleDrawFrame;
}

ここでのscheduleFrameメソッドは、JavaのJNIメソッドと同様に、C++で実装されたエンジンのネイティブメソッドです。このメソッドの公式な説明は

onBeginFrameコールバックとonDrawFrameコールバックが次の適切な機会に呼び出されるようにリクエストします!

void scheduleFrame() native 'Window_scheduleFrame';

C++の実装を追跡する代わりに、onBeginFrameとonDrawFrameコールバックの説明に従ってください。

/// [SchedulerBinding]
void _handleBeginFrame(Duration rawTimeStamp) {
 if (_warmUpFrame) {
 _ignoreNextEngineDrawFrame = true;
 return;
 }
 handleBeginFrame(rawTimeStamp);
}
void _handleDrawFrame() {
 if (_ignoreNextEngineDrawFrame) {
 _ignoreNextEngineDrawFrame = false;
 return;
 }
 handleDrawFrame();
}

ここでは、"Framework Startup Source Code Analysis "のレンダリングセクションで分析されたロジックに対応しています。 handleDrawFrameへの後続の呼び出しについては、 スキップして、主にbuildOwnerのbuildScopeメソッドで、ここで懸念されるロジックを直接見てください。

/// [WidgetsBinding]
void drawFrame() {
 // ......省略......
 try {
 if (renderViewElement != null)
 // ダーティとマークされた要素をRebuild()する。
 buildOwner.buildScope(renderViewElement);
 // 親クラスのdrawFrameメソッドを呼び出す。これは実際にはRendererBindingのdrawFrame()メソッドである。
 super.drawFrame();
 // アクティブでなくなったエレメントをアンロードして、エレメントのビルドプロセスを完了する。
 buildOwner.finalizeTree();
 }
 // ......省略......
}
/// [BuildOwner]
void buildScope(Element context, [ VoidCallback callback ]) {
 if (callback == null && _dirtyElements.isEmpty)
 return;
 Timeline.startSync('Build', arguments: timelineWhitelistArguments);
 try {
 _scheduledFlushDirtyElements = true;
 if (callback != null) {
 Element debugPreviousBuildTarget;
 _dirtyElementsNeedsResorting = false;
 try {
 callback();
 } finally {
 }
 }
 // ダーティエレメントのリストをソートする
 _dirtyElements.sort(Element._sort);
 _dirtyElementsNeedsResorting = false;
 int dirtyCount = _dirtyElements.length;
 int index = 0;
 while (index < dirtyCount) {
 try {
 // 要素にリビルドを呼び出す
 _dirtyElements[index].rebuild();
 } catch (e, stack) {
 }
 index += 1;
 if (dirtyCount < _dirtyElements.length || _dirtyElementsNeedsResorting) {
 _dirtyElements.sort(Element._sort);
 _dirtyElementsNeedsResorting = false;
 dirtyCount = _dirtyElements.length;
 while (index > 0 && _dirtyElements[index - 1].dirty) {
 index -= 1;
 }
 }
 }
 } finally {
 for (final Element element in _dirtyElements) {
 element._inDirtyList = false;
 }
 _dirtyElements.clear();
 _scheduledFlushDirtyElements = false;
 _dirtyElementsNeedsResorting = null;
 Timeline.finishSync();
 }
}
/// [Element]
void rebuild() {
 if (!_active || !_dirty)
 return;
 // リビルドを実行する
 performRebuild();
}

ここでは、performRebuild() はそのサブクラス ComponentElement で実装されています。

/// [ComponentElement]
void performRebuild() {
 if (!kReleaseMode && debugProfileBuildsEnabled)
 Timeline.startSync('${widget.runtimeType}', arguments: timelineWhitelistArguments);
 Widget built;
 try {
 // 要素に関連するウィジェットのビルドメソッドを呼び出して、コントロールツリーを再構築する。
 built = build();
 debugWidgetBuilderValue(widget, built);
 } catch (e, stack) {
 } finally {
 // ビルド呼び出し中にmarkNeedsBuild()の試みが無視されるまで、要素をクリーンとしてマークするのを遅らせる。
 _dirty = false;
 }
 try {
 // エレメントツリーを更新する
 _child = updateChild(_child, built, slot);
 } catch (e, stack) {
 }
 if (!kReleaseMode && debugProfileBuildsEnabled)
 Timeline.finishSync();
}

ここで要素ツリーを更新するためのロジックは、すべてupdateChildメソッドにあります。

また、build() メソッドの具体的な実装も見てみましょう。

/// StatelessWidgetエレメント
class StatelessElement extends ComponentElement {
 StatelessElement(StatelessWidget widget) : super(widget);
 @override
 StatelessWidget get widget => super.widget as StatelessWidget;
 // buildcontextパラメータは、実際にはウィジェットのElementオブジェクトだ。
 @override
 Widget build() => widget.build(this);
 @override
 void update(StatelessWidget newWidget) {
 super.update(newWidget);
 assert(widget == newWidget);
 _dirty = true;
 rebuild();
 }
}
/// StatefulWidgetエレメント
class StatefulElement extends ComponentElement {
 StatefulElement(StatefulWidget widget)
 : _state = widget.createState(),
 super(widget) {
 _state._element = this;
 _state._widget = widget;
 }
 @override
 Widget build() => _state.build(this);
 
 State<StatefulWidget> get state => _state;
 State<StatefulWidget> _state;
 
 // ......省略......
}

レンダリングプロセス

ページを更新する必要がある場合、アプリケーションの上位レイヤーはEngineに通知し、Engineは次のVsync信号が到着するまで待ち、次にFrameworkの上位レイヤーに通知します。Engineはレイヤーを合成し、テクスチャを生成し、最後にOpenGlインターフェイスを通してGPUにデータを送信し、GPUはデータを処理してモニタに表示します。

ビデオコース

Flutterフルスタック開発コースの完全版については、以下のアドレスをご覧ください。

Read next

もう間違ったページを削除する心配はない!--ページごみ箱」がオンラインになった!

こんにちは!Mimicからまた新機能のお知らせです。今回のアップデートでは、待望の「ページごみ箱」がプロトタイプで利用できるようになりました!また、「ストローツール」も追加されました。コラボレーションの面では、お絵かきボードの背景色属性の表示、JPG切り抜きダウンロードのサポート、その他のニーズをサポートしたいという友人もいます。

Mar 8, 2020 · 3 min read