Call graph
We use the following example to analyze what Flutter initiation does
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
returnContainer(); }}Copy the code
Initialize the Binding
runApp
voidrunApp(Widget app) { WidgetsFlutterBinding.ensureInitialized() .. scheduleAttachRootWidget(app) .. scheduleWarmUpFrame(); }Copy the code
We will call in runApp WidgetsFlutterBinding. The ensureInitialized () to initialize the Binding.
Binding
Binding is a series of singletons initialized at startup, including WidgetsFlutterBinding, BindingBase, GestureBinding, SchedulerBinding, ServicesBinding, etc. Here’s what they do
- BindingBase: the base class of all classes, responsible for initializing other classes, initializing some Native related information (such as whether the platform is Android or iOS), registering basic Native events (such as hot updates, exit)
- GestureBinding:
window.onPointerDataPacket
Callback, accept Native events, responsible for event conversion and distribution - SchedulerBinding: use
window.scheduleFrame
To inform Native and usewindow.onBeginFrame
andwindow.onDrawFrame
Call back to receive messages, mainly responsible for notifying Native of the event delivery and event registration of the next detection. When we call setState, such methods will be triggered and render will be performed after the event is delivered - Use ServicesBinding:
window.onPlatformMessage
Callback, responsible for channel-related initialization and communication-related processing - PaintingBinding: Function bindings related to drawing, and also handles some image rendering related caches
- SemanticsBinding: Register platform-specific helper functions
- RendererBinding: initialization
PipelineOwner
,renderView
,onMetricsChanged
,onTextScaleFactorChanged
,onPlatformBrightnessChanged
,onSemanticsEnabledChanged
onSemanticsAction
This function is used to monitor and process platform rendering related events such as font and status bar changes. It is the bridge between rendering and the Flutter engine - WidgetsBinding: initialization
BuildOwner
, the registeredwindow.onLocaleChanged
,onBuildScheduled
Such as callbacks. It is the bridge between the Flutter Widget layer and the Engine.
Rendering tree construction
attachRootWidget
[-> packages/flutter/lib/src/widgets/binding.dart]
void attachRootWidget(Widget rootWidget) {
_readyToProduceFrames = true;
_renderViewElement = RenderObjectToWidgetAdapter<RenderBox>(
container: renderView,
debugShortDescription: '[root]', child: rootWidget, ).attachToRenderTree(buildOwner! , renderViewElementasRenderObjectToWidgetElement<RenderBox>?) ; }Copy the code
It will initialize a RenderObjectToWidgetAdapter first, it is a special Widget, mainly use the Flutter of the root node initialization, including a renderView, As mentioned above, the RendererBinding is initialized when the RendererBinding is initialized. It is the root RenderObject node, and the rootWidget is the Widget passed in when we runApp.
A subsequent call to attachToRenderTree initiates a series of tree builds
attachToRenderTree
[-> packages/flutter/lib/src/widgets/binding.dart]
RenderObjectToWidgetElement<T> attachToRenderTree(BuildOwner owner, [ RenderObjectToWidgetElement<T>? element ]) {
// If element==null is executed, the first rendering of Element must be null
if (element == null) {
owner.lockState(() {
Call createElement to create an Element
element = createElement();
assert(element ! =null);
// Pass owner to Element so that Element holds the ownerelement! .assignOwner(owner); }); owner.buildScope(element! , () { element! .mount(null.null); }); SchedulerBinding.instance! .ensureVisualUpdate(); }else {
element._newWidget = this;
element.markNeedsBuild();
}
returnelement! ; }Copy the code
Create an Element object based on createElement and pass it to the owner. BuildOwner is a class that handles updates to Flutter. This class collects and redraws dirty nodes. There is only one BuildOwner object globally, and once passed in here, it is passed on to the child nodes.
Owner. buildScape is then called. BuildScape provides two main functions, one is to set up a scope for Widget Tree updates and perform a callback (as in the case above, but callbacks can be ignored), and the other is when there are dirty nodes in the scope, It calls the element. rebuild method to rebuild (at which point the Widget’s build being called re-renders the Element).
Mount, an important element method, is used to build the three trees. The simplest function of mount is to bind the parent object to the current object and bind _inheritedWidgets to the current object. Is the Element of the InheritedWidget used for data sharing in Flutter. Normally our child Element overrides the mount method for the initialization of the child node.
mount
[-> packages/flutter/lib/src/widgets/framework.dart]
@override
void mount(Element? parent, dynamic newSlot) {
assert(parent == null);
super.mount(parent, newSlot);
_rebuild();
}
void _rebuild() {
/ /...
_child = updateChild(_child, widget.child, _rootChildSlot);
/ /...
}
Copy the code
So in RenderObjectToWidgetElement invokes _rebuild then call updateChild to initialize the child node.
updateChild
[-> packages/flutter/lib/src/widgets/framework.dart]
@protected
Element? updateChild(Element? child, Widget? newWidget, dynamic newSlot) {
// ...
final Element newChild;
if(child ! =null) {
bool hasSameSuperclass = true;
if (hasSameSuperclass && child.widget == newWidget) {
if(child.slot ! = newSlot) updateSlotForChild(child, newSlot); newChild = child; }else if (hasSameSuperclass && Widget.canUpdate(child.widget, newWidget)) {
/ /...
child.update(newWidget);
/ /...
newChild = child;
} else{ deactivateChild(child); newChild = inflateWidget(newWidget, newSlot); }}else {
newChild = inflateWidget(newWidget, newSlot);
}
return newChild;
}
Copy the code
The updateChild method is used to initialize or update the child node when the child! If = null, the child is updated mainly by calling child.update, otherwise the inflateWidget is called, which of course will be called for the first rendering of the inflateWidget
inflateWidget
[-> packages/flutter/lib/src/widgets/framework.dart]
@protected
Element inflateWidget(Widget newWidget, dynamic newSlot) {
// ...
final Element newChild = newWidget.createElement();
// ...
newChild.mount(this, newSlot);
return newChild;
}
Copy the code
The inflateWidget method simply generates an Element from the Widget and calls child.mount. This completes a loop call to the tree.
The Widget tree building
Someone asked why the Build methods for StatelessWidget and StatefulWidget are not called. The corresponding elements of StatelessWidget and StatefulWidget are StatelessElement and StatefulElement, which are inherited from ComponentElement. The mount method is the starting point for ComponentElement.
_firstBuild
[-> packages/flutter/lib/src/widgets/framework.dart]
@override
void mount(Element? parent, dynamic newSlot) {
super.mount(parent, newSlot);
assert(_child == null);
assert(_lifecycleState == _ElementLifecycle.active);
_firstBuild();
assert(_child ! =null);
}
Copy the code
The _firstBuild method is called, then the Rebuild method on Element is called, then the performRebuild method on ComponentElement is called
performRebuild
[-> packages/flutter/lib/src/widgets/framework.dart]
void rebuild() {
performRebuild();
}
/ /...
@override
void performRebuild() {
Widget? built;
try {
// ...
built = build();
} catch (e, stack) {
// ...
} finally {
_dirty = false;
}
try {
_child = updateChild(_child, built, slot);
} catch (e, stack) {
// ...
_child = updateChild(null, built, slot);
}
if(! kReleaseMode && debugProfileBuildsEnabled) Timeline.finishSync(); }Copy the code
PerformRebuild is the parent class of the ComponentElement implementation, which calls the Build method to return a Widget and then sees the familiar updateChild method, which picks up the analysis above.
RenderObject tree building
RenderObjectWidget
RenderObject is the class of Flutter that is used for the actual drawing. There is no corresponding RenderObject node for StatefulWidget and StatelessWidget. In fact, they only provide a container for combining various drawings.
Only widgets that inherit from RenderObjectWidget have corresponding RenderObject nodes. To facilitate Flutter, there are three classes that inherit from RenderObjectWidget, each with a different purpose
- LeafRenderObjectWidget is used for only rendering without child nodes (e.g. Switch, Radio, etc.)
- SingleChildRenderObjectWidget contains only a child node (e.g. SizedBox, Padding, etc.)
- MultiChildRenderObjectWidget contains more child nodes (such as Column, Stack, etc.)
These objects just provide a container for developers to use, but that’s not the focus of our analysis. Let’s look at RenderObjectWidget first
[-> packages/flutter/lib/src/widgets/framework.dart]
abstract class RenderObjectWidget extends Widget {
const RenderObjectWidget({ Key? key }) : super(key: key);
@override
@factory
RenderObjectElement createElement();
@protected
@factory
RenderObject createRenderObject(BuildContext context);
@protected
void updateRenderObject(BuildContext context, covariant RenderObject renderObject) { }
@protected
void didUnmountRenderObject(covariant RenderObject renderObject) { }
}
Copy the code
It provides the createRenderObject method to build the RenderObject. See the mount method of its corresponding Element->RenderObjectElement
createRenderObject
[-> packages/flutter/lib/src/widgets/framework.dart]
@override
void mount(Element? parent, dynamic newSlot) {
super.mount(parent, newSlot);
_renderObject = widget.createRenderObject(this);
attachRenderObject(newSlot);
_dirty = false;
}
@override
void attachRenderObject(dynamic newSlot) {
_slot = newSlot;
_ancestorRenderObjectElement = _findAncestorRenderObjectElement();
// Insert the renderObject generated in the previous step into the parent renderObject_ancestorRenderObjectElement? .insertRenderObjectChild(renderObject, newSlot);final ParentDataElement<ParentData>? parentDataElement = _findAncestorParentDataElement();
if(parentDataElement ! =null)
_updateParentData(parentDataElement.widget);
}
Copy the code
CreateRenderObject is called to build the RenderObject, and attachRenderObject is called to insert the RenderObject generated in the previous step into the parent RenderObject. Through _findAncestorParentDataElement here to find the parent class, because of the above said, not every Element has a corresponding RenderObject node.
_findAncestorRenderObjectElement
[-> packages/flutter/lib/src/widgets/framework.dart]
RenderObjectElement? _findAncestorRenderObjectElement() {
Element? ancestor = _parent;
while(ancestor ! =null && ancestor is! RenderObjectElement)
ancestor = ancestor._parent;
return ancestor asRenderObjectElement? ; }Copy the code
So I’m using the parent Element to find a class in the Element tree called RenderObjectElement, And insertRenderObjectChild is from what is said above SingleChildRenderObjectWidget, MultiChildRenderObjectWidget to implementation, You essentially insert the generated RenderObject into the parent RenderObject you just found. Because the root Element node RenderObjectToWidgetElement is RenderObjectElement inheritance, so, As long as through RenderObjectToWidgetElement. RenderObject can get whole renderObject tree.
conclusion
We have analyzed the general process of Flutter when it is first started. WidgetsFlutterBinding is the general foreman, and almost everything in the App is managed through it. BuildOwner is responsible for building and updating the tree. RenderObjectToWidgetAdapter is a special Widget, it is apply colours to a drawing the root node of the tree, but also a starting point of the build tree, the Widget is responsible for docking with the developers, it is a tool, the developers put data in here, All the rest are processed through the Element and renderObjects are generated (some widgets don’t necessarily generate renderObjects).
Now we just created a render tree, but how is it that interface rendering, back to the above attachToRenderTree method, will call after the component tree generated SchedulerBinding. The instance! The. EnsureVisualUpdate method, which finally calls window.scheduleFrame, sends a notification to Native saying: “My tree is generated, so send me a notification at the next session that I need to start rendering the page.”
To see the rendering process, listen to the next breakdown.