If we want to understand the rendering process of FLUTTER, we must know the three most important tree widgets of Flutter and the Render tree. Their relationship, and what it means to be there. In fact, the process of rendering can also be said to be the process of creating these three tree node objects.

I. The relationship between three trees

You can see that in the picture abovewidget treeelement treeIt’s a one-to-one correspondence, butRender treeIt doesn’t correspond to them. This is because only inherited fromRenderObjectWidgetthewidgetThat will eventually be rendered to the screen. Some of the components that we use a lot likeContainerOr is itTextThis category is a combinationwidgetBecause they didn’t inherit itRenderObjectWidget.ContainerThe interior is actually made up ofPadding ColoredBox AlignAnd so onTextInternal actuallyRichText.

  • WidgetDescribe the final appearance of the View, such as how big it is, what color it is and how far it is from the edge of the screen, etc
  • ElementOne-to-one correspondence with widgets to maintain the stable shape of the tree while holdingwidgetandrenderObject
  • RenderRender tree objects, which ultimately provide rendering methods such asmarkNeedsLayout performLayout markNeedsPaint paintSuch methods as rendering on screen, withelementIt’s not one to one.

The process of object creation

1. WidgetObject creation

Widget object creation is very simple when you call the constructor, as shown in the following code

void main() {
   runApp(MyApp());
 }
Copy the code

MyApp() is the Widget object created, and it’s very simple.

2. ElementObject creation

Following the source code, we can see that each Widget class has a createElement() method, which is called when the Element object is created. We found this code in the runApp() method, but I’ve omitted some of the extraneous code for the sake of understanding the process

RenderObjectToWidgetElement<T> attachToRenderTree(BuildOwner owner, [ RenderObjectToWidgetElement<T>? element ]) { ...... element = createElement(); . element! .mount(null, null); . return element! ; }Copy the code

So createElement() is called in this method, and the root Element is created. The mount method is then called to insert the Element into the corresponding Element Tree. We then call the inflateWidget() method to create it, which calls the createElement() method of the child element, which then calls the mount method, and so on to form the Element Tree. Briefly review the process

  1. attachToRenderTree()The method callcreateElement()To create arootElement
  2. rootElementThe objectmount()Method will berootElementInsert into the node of the tree and callinflateWidget()
  3. ininflateWidget()In the methodThe child widgetscallcreateElement()andmount()Repeat the above steps untilelement treeHas been created
Element inflateWidget(Widget newWidget, dynamic newSlot) { .... final Element newChild = newWidget.createElement(); . newChild._activateWithParent(this, newSlot); . newChild.mount(this, newSlot); . return newChild; }Copy the code

3. RenderObject creation

Like Element, the RenderObject class has an important method called createRenderObject(), which means that the time to call this method is when the RenderObject is created. As mentioned earlier, only widgets that inherit from RenderObject will eventually be rendered. Before the RenderObject object is created. Most of the widgets in flutter have their corresponding Element as shown below

So objects that really inherit from RenderObjectElement will actually call createRenderObject(). Tracing the source code, we found that the corresponding renderObject was created when the Element object was inserted into the tree node when mount was called.

void mount(Element? parent, dynamic newSlot) { super.mount(parent, newSlot); . _renderObject = widget.createRenderObject(this); . attachRenderObject(newSlot); _dirty = false; }Copy the code

3.buildMethod execution flow

There is an important method in Element called markNeedsBuild() that does two main things

  1. tagelementfordirty
  2. The currentelementPut objects globally_dirtyElementsIn the

Execute the process

  1. whenelementAfter inserting into the corresponding tree node, the currentelementIs marked asactiveState, and then executeactivate()
  2. inactivate()Method to executemarkNeedsBuild()Mark the object asdirty
  3. And then the system calls it_handleBuildScheduled() scheduleWarmUpFrame() scheduleFrame() window.scheduleFrame();A series of methods, the purpose of which is to registervsyncSignal that registration is complete and wait for the next timevsyncThe arrival of the
  4. The next timevsyncWill trigger upon arrivalWidgetsBinding.drawFrame buildScopeBuildScope ()This method will get the one that was just marked asdirtyThat’s the list of_dirtyElementsWork through the list one by onerebuild()
  5. inrebuild()Method to call the correspondingperformRebuild(), and finally callWidget build() => widget.build(this);End of the process

In the above flow, one detail is when will the next vsync come? We use a metric called Frames Per Second, which is the number of Frames Per Second that need to be transmitted, to determine if the UI is stuck. We usually measure it by 60 FPS, and if it’s below 60 it’s going to stall, and the lower it goes, the more it’s going to stall. This is because most devices refresh at 60 FPS, or 60 frames per second, so it takes about 1/60s or 0.0166s to process a frame of data. This vsync is the signal the device sends when it starts rendering the first frame. So if the device is refreshed at 60fps, ideally the next vsync signal will come in 0.0166 seconds.

4. To summarize

Now that we know when the Widget Element Render object is created and how the build() method is executed, we can thread through all the points to get the following flow

  1. First createwidgetObject and then inattachToRenderTreeMethod to createrootElement
  2. rootElementInserted into theelement tree, and create therenderObjectObject, followed by executionmarkNeedsBuild()methods
  3. throughbuildMethod gets the widget of the next child node and executesinflateWidget()createelementobject
  4. performmount()Method, insertelement tree, create the correspondingrenderObjectObject, and then execute thiselementthemarkNeedsBuild()Method, repeat the above steps until the creation of the three trees is complete.