TOP(About this memo)) > 一覧(Flutter) > (参考)内部処理の理解(コードリーディング)
Pump the build and rendering pipeline to generate a frame.
dart:ui/PlatformDispatcher
The most basic interface to the host operating system’s interface.This is the central entry point for platform messages and configuration events from the platform.It exposes the core scheduler API, the input event callback, the graphics drawing API, and other such core services.
SchedulerBinding.scheduleFrame()
The “Vsync” signal, or vertical synchronization signal, was historically related to the display refresh, at a time when hardware physically moved a beam of electrons vertically between updates of the display. The operation of contemporary hardware is somewhat more subtle and complicated, but the conceptual “Vsync” refresh signal continue to be used to indicate when applications should update their rendering.
void scheduleFrame() => _scheduleFrame();
@Native<Void Function()>(symbol: 'PlatformConfigurationNativeApi::ScheduleFrame')
external static void _scheduleFrame();
(参考)engineのコード
runAppによって以下が順に実行される。(これらの処理は同期的に行われる)
初回のツリー構築(attachRootWidget())
初回のdrawFrameの処理
以降のdrawFrameの処理
以下はrunAppの実行順番を確認するサンプルコードである。
import "package:flutter/widgets.dart";
main() {
debugPrintScheduleFrameStacks = true;
debugPrintBuildScope = true;
debugPrint("before runApp");
Future(() => debugPrint("future before runApp"));
runApp(const MyWidget());
debugPrint("after runApp");// 初回のツリーの構築はTimer.runにて実行されるため、initStateやbuildよりもこちらのprintが先に実行される。
Future(() => debugPrint("future after runApp"));
Future.microtask(() => debugPrint("microtask after runtApp"));// Timer.runより早く実行される。
}
class MyWidget extends StatefulWidget {
const MyWidget({super.key});
@override
State<MyWidget> createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
@override
void initState() {
debugPrint("initState");
super.initState();
}
@override
Widget build(BuildContext context) {
debugPrint("build");
return const Placeholder();
}
}
/*
flutter: before runApp
flutter: after runApp
flutter: microtask after runtApp
flutter: future before runApp
flutter: buildScope called with context [root](dirty); dirty list is: []
flutter: scheduleFrame() called. Current phase is SchedulerPhase.idle.
flutter: #0 debugPrintStack (package:flutter/src/foundation/assertions.dart:1201:29)
flutter: #1 SchedulerBinding.scheduleFrame.<anonymous closure> (package:flutter/src/scheduler/binding.dart:875:9)
...
flutter: #25 BuildOwner.buildScope (package:flutter/src/widgets/framework.dart:2835:19)
flutter: #26 RootWidget.attach (package:flutter/src/widgets/binding.dart:1255:13)
flutter: #27 WidgetsBinding.attachToBuildOwner (package:flutter/src/widgets/binding.dart:1083:27)
flutter: #28 WidgetsBinding.attachRootWidget (package:flutter/src/widgets/binding.dart:1065:5)
flutter: #29 WidgetsBinding.scheduleAttachRootWidget.<anonymous closure> (package:flutter/src/widgets/binding.dart:1051:7)
flutter: #33 _RawReceivePort._handleMessage (dart:isolate-patch/isolate_patch.dart:184:12)
flutter: (elided 3 frames from class _Timer and dart:async-patch)
flutter: initState
flutter: build
flutter: buildScope finished
flutter: future after runApp
*/
Widgetの親子同士の参照情報
Elementの親子同士の参照情報
RenderObjectの親子同士の参照情報
初回ビルド(マウント)
リビルド
Element.mount(), deactivate(), activate(), unmount() と _ElementLifecycle
Element.rebuild()
rebuild()
lifecycleがactiveではない または(!dirty && !force)の場合はreturn
performRebuild()
Element.performRebuild()
void performRebuild() {
_dirty = false;
}
Element.inflateWidget(Widget newWidget, Object? newSlot)
inflateWidget(Widget newWidget, Object? newSlot)
Key? key = newWidget.key;
if (key is GlobalKey)
final Element? newChild = _retakeInactiveElement(key, newWidget);
if (newChild != null)
newChild._activateWithParent(this, newSlot);
updatedChild = updateChild(newChild, newWidget, newSlot);
return updatedChild
newWidget.createElement()
newChild.mount(this, newSlot);
return newChild;
_retakeInactiveElement(GlobalKey key, Widget newWidget)
//...
final Element? element = key._currentElement;
if (element == null) return null;
if (!Widget.canUpdate(element.widget, newWidget)) return null;
if (element._parent != null ) parent.forgetChild(element);parent.deactivateChild(element);
owner!._inactiveElements.remove(element);
return element;
Element.mount(Element? parent, Object? newSlot)
Element.update(covariant Widget newWidget)
void update(covariant Widget newWidget)
// ...
_widget = newWidget;
Element.updateChild(Element? child, Widget? newWidget, Object? newSlot)
abstract class Widget extends DiagnosticableTree {
// ...
@override
@nonVirtual
bool operator ==(Object other) => super == other; // Objectの==を利用。
// ...
}
updateChild(Element? child, Widget? newWidget, Object? newSlot)
if newWidget == null
if (child != null) deactivateChild(child);
return null;
// ウィジェットがnullの場合は何もelementを返さない。
// もし、Elementが存在する場合は非アクティブ化する。
// TODO: この分岐は、おそらくupdateChildren()の方の経路から呼び出した際に通ると推測
final Element newChild;
if child != null
// assert内でhotreload用の処理を行っている
if child.widget == newWidget // 同一オブジェクトの場合
if (child.slot != newSlot) {
updateSlotForChild(child, newSlot);
}
newChild = child;
else if Widget.canUpdate(child.widget, newWidget)
// 同一オブジェクトではない場合でもupdate扱いとなる分岐
if (child.slot != newSlot) {
updateSlotForChild(child, newSlot);
}
child.update(newWidget);
newChild = child;
else // 上記のどちらでもない場合はupdate対象外のため古いものをdeactivateして新しいものをinflateする
deactivateChild(child);
newChild = inflateWidget(newWidget, newSlot);
else
newChild = inflateWidget(newWidget, newSlot);
return newChild
static bool canUpdate(Widget oldWidget, Widget newWidget) {
return oldWidget.runtimeType == newWidget.runtimeType
&& oldWidget.key == newWidget.key;
}
Element.updateChildren(List
Element.unmount()
unmount()
assert(_lifecycleState == _ElementLifecycle.inactive);
//...
final Key? key = _widget?.key;
if (key is GlobalKey) owner!._unregisterGlobalKey(key, this);
_widget = null;
_dependencies = null;
_lifecycleState = _ElementLifecycle.defunct;
以下はStatefulElement/Stateに関連するクラスとメソッド、処理に焦点を当てて表記した図となる。
State.didUpdateWidget()
State.didChangeDependencies()
State.dispose()
ルートとなるRootElementからの処理は以下のようになる。
RootWidget
A widget for the root of the widget tree.
View
Bootstraps a render tree that is rendered into the provided FlutterView.
The View determines into what FlutterView the app is rendered into. This is currently PlatformDispatcher.implicitView from platformDispatcher.
FlutterView
A view into which a Flutter Scene is drawn. Each FlutterView has its own layer tree that is rendered whenever render is called on it with a Scene.
Updates the view’s rendering on the GPU with the newly provided Scene.
以下は上記に続く_RawViewからRenderViewが生成されるまでの処理の流れとなる
PipelineOwnerのオブジェクトは下記のような構成となる。
The root of the render tree.
ContainerRenderObjectMixin<ChildType extends RenderObject, ParentDataType extends ContainerParentDataMixin<ChildType>> on RenderObject {
//..
}
ContainerParentDataMixin<ChildType extends RenderObject> on ParentData {
//...
}
Information set by parent to define where this child fits in its parent’s child list.
bool operator ==(Object other) {
if (other.runtimeType != runtimeType) {
return false;
}
return other is IndexedSlot
&& index == other.index
&& value == other.value;
}
Element? updateChild(Element? child, Widget? newWidget, Object? newSlot) {
// ...
if (child.slot != newSlot) {
updateSlotForChild(child, newSlot);
}
// ...
}
具体的な活用としては、RenderObjectの子の順番が変わった際に、リンクドリストに挿入するためにこのslotが利用されている。
下記のMultiChildRenderObjectElementがContainerParentDataMixinであるRenderObjectに対してinsert()を実行する際にIndexedSlotのデータが利用されている。
@override
void insertRenderObjectChild(RenderObject child, IndexedSlot<Element?> slot) {
final ContainerRenderObjectMixin<RenderObject, ContainerParentDataMixin<RenderObject>> renderObject = this.renderObject;
assert(renderObject.debugValidateChild(child));
renderObject.insert(child, after: slot.value?.renderObject);// slot.valueには親Elementが保持する子リスト上の手前の子が設定されている。これによって、その子のRenderObjectのafterに挿入することを実現できている。
assert(renderObject == this.renderObject);
}
https://api.flutter.dev/flutter/widgets/Element/updateChildren.html
When the slot value of an Element changes, its associated renderObject needs to move to a new position in the child list of its parents. If that RenderObject organizes its children in a linked list (as is done by the ContainerRenderObjectMixin) this can be implemented by re-inserting the child RenderObject into the list after the RenderObject associated with the Element provided as IndexedSlot.value in the slot object.
Using the previous sibling as a slot is not enough, though, because child RenderObjects are only moved around when the slot of their associated RenderObjectElements is updated. When the order of child Elements is changed, some elements in the list may move to a new index but still have the same previous sibling. For example, when [e1, e2, e3, e4] is changed to [e1, e3, e4, e2] the element e4 continues to have e3 as a previous sibling even though its index in the list has changed and its RenderObject needs to move to come before e2’s RenderObject. In order to trigger this move, a new slot value needs to be assigned to its Element whenever its index in its parent’s child list changes. Using an IndexedSlot
achieves exactly that and also ensures that the underlying parent RenderObject knows where a child needs to move to in a linked list by providing its new previous sibling.
https://api.flutter.dev/flutter/widgets/RenderObjectElement-class.html
Each child Element corresponds to a RenderObject which should be attached to this element’s render object as a child.However, the immediate children of the element may not be the ones that eventually produce the actual RenderObject that they correspond to. For example, a StatelessElement (the element of a StatelessWidget) corresponds to whatever RenderObject its child (the element returned by its StatelessWidget.build method) corresponds to. Each child is therefore assigned a slot token. This is an identifier whose meaning is private to this RenderObjectElement node. When the descendant that finally produces the RenderObject is ready to attach it to this node’s render object, it passes that slot token back to this node, and that allows this node to cheaply identify where to put the child render object relative to the others in the parent render object.A child’s slot is determined when the parent calls updateChild to inflate the child (see the next section). It can be updated by calling updateSlotForChild.
The root of the render tree.
main() => testWidgets("", (tester) async {
await tester.pumpWidget(Flexible(child: Container()));
});
// The following assertion was thrown while applying parent data.:
// Incorrect use of ParentDataWidget.
// ...
// RenderObjectElement._updateParentData.<anonymous closure> (package:flutter/src/widgets/framework.dart:6512:11)
main() {
testWidgets("", (tester) async {
await tester.pumpWidget(Directionality(
textDirection: TextDirection.ltr,
child: Column(
children: [Flexible(child: ListView())],
)));
debugDumpRenderTree();
});
}
// ...
// child: RenderFlex#99a23
// ...
// └─child 1: RenderRepaintBoundary#b97ba relayoutBoundary=up1
// ...
// │ parentData: offset=Offset(0.0, 0.0); flex=1; fit=FlexFit.loose
abstract class GlobalKey<T extends State<StatefulWidget>> extends Key {
//...
Element? get _currentElement => WidgetsBinding.instance.buildOwner!._globalKeyRegistry[this];
BuildContext? get currentContext => _currentElement;
//...
}
class BuildOwner {
// ..
final Map<GlobalKey, Element> _globalKeyRegistry = <GlobalKey, Element>{};
// ...
void _registerGlobalKey(GlobalKey key, Element element) {
//...
_globalKeyRegistry[key] = element;
}
//...
}
// 例: ウィジェットのSizeを仮計算するコード
final widget = .....
// Create dummy BuildOwner.
BuildOwner owner = BuildOwner(focusManager: FocusManager());
// Create dummy render tree with inner widgets.
// Using lockState, buildScope function to avoid assert error.
_DummyTreeRootElement? element;
owner.lockState(() {
element = widget.createElement();
element!.assignOwner(owner);
});
owner.buildScope(element!, () {
element!.mount(null, null);
});
// Find _WrapColumn widget and get Size
final columnSize = element!.computeDryLayout(const BoxConstraints());
// Dispose dummy tree
element!.deactivate();
element!.unmount();
WidgetsBinding.instance.buildOwner!._globalKeyRegistry[this];
を参照しており、runApp()が実行されている時のみ利用可能と成る。main() {
GlobalKey().currentContext;
// assert error: Binding has not yet been initialized.
}
final GlobalKey _boxKey = GlobalKey();
// ...
referenceBox: _boxKey.currentContext!.findRenderObject()! as RenderBox,
class _RawView extends RenderObjectWidget {
//...
@override
RenderObject createRenderObject(BuildContext context) {
return _deprecatedRenderView ?? RenderView(
view: view,
);
}
//...
}
void runApp(Widget app) {
// ...
binding
..scheduleAttachRootWidget(binding.wrapWithDefaultView(app))
..scheduleWarmUpFrame();
}
mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, RendererBinding, SemanticsBinding {
//...
Widget wrapWithDefaultView(Widget rootWidget) {
return View(
view: platformDispatcher.implicitView!,
deprecatedDoNotUseWillBeRemovedWithoutNoticePipelineOwner: pipelineOwner, // これはRendererBinding.pipelineOwnerを指す
deprecatedDoNotUseWillBeRemovedWithoutNoticeRenderView: renderView, // これは RendererBinding.renderViewを指す
child: rootWidget,
);
}
// ...
}
@Deprecated(/* ... */)
late final PipelineOwner pipelineOwner = PipelineOwner(/* ... */);
@Deprecated(/* ... */)
// TODO(goderbauer): When this deprecated property is removed also delete the _ReusableRenderView class.
late final RenderView renderView = _ReusableRenderView(
view: platformDispatcher.implicitView!,
);
class _RawViewElement extends RenderTreeRootElement {
//...
late final PipelineOwner _pipelineOwner = PipelineOwner(
onSemanticsOwnerCreated: _handleSemanticsOwnerCreated,
onSemanticsUpdate: _handleSemanticsUpdate,
onSemanticsOwnerDisposed: _handleSemanticsOwnerDisposed,
);
PipelineOwner get _effectivePipelineOwner => (widget as _RawView)._deprecatedPipelineOwner ?? _pipelineOwner;
//...
@override
void mount(Element? parent, Object? newSlot) {
//...
_effectivePipelineOwner.rootNode = renderObject;
//...
}