Analysis of ListView construction process and performance problems in use

Study the most avoid blind, no plan, fragmentary knowledge can not be strung into a system. Where did you learn, where did you forget? I can’t remember the interview. Here I have collated the most frequently asked questions in the interview and several core pieces of knowledge in the Flutter framework, which are analyzed in about 20 articles. Welcome to pay attention and make progress together. ! [Flutter framework]

Welcome to the public account: Offensive Flutter or Runflutter, which collects the most detailed guide to advance and optimize Flutter. Follow me and get my latest articles ~

Guide language:

Recently, after scouring all the articles on the Internet for the performance optimization related to Flutter, I saw the High performance, multi-functional full scene rolling container of The Flutter by Idler Fish. However, this component is not open source, so I’m going to try to research and develop a high performance ScrollView from the ideas presented in this article. This series is expected to be divided into 4-5 articles, with the first three focusing on research and analysis of existing problems and the last two on actual development.

Principle of the article:

1. How do Widget, Element and Render trees form?

2. ListView construction process and performance analysis

To analyze ListView performance, we first need to understand the ListView building process. Before reading this article, it is best to be familiar with the three Flutter trees and the basic layout principle and slide principle of the Flutter, otherwise the understanding of the construction process may remain superficial. How exactly are widgets, Elements, and Render trees formed? And after 30 examples, I understand the layout principle of the Flutter, and go into the next step – a diagram to clarify the sliding principle of the Flutter


ListView class structure relationship

In the last article, we studied the construction process of three trees in Flutter, which leads to an important design idea in the SYSTEM of Flutter UI, namely:

Widget -> Abstraction of every page element for developers to use. Element -> Manages the entire UI build, bridging widgets and renderObjects, and providing an efficient refresh mechanism. RenderObject -> mask the specific layout and render details of each element.

The animation phase, build phase, Layout phase and paint phase of any control in the Flutter can be learned from these three classes. Again, we’ll explore the ListView using these three classes as clues. Let’s start with an overview!

See this picture is not scalp numbness, don’t worry, we analyze step by step.


ListView nested structure

First up, we’ll shine a spotlight on our star of the day, the ListView

As you can see in the figure above, the ListView first inherits from BoxScrollView from ScrollView from StatelessWidget. We know that a StatelessWidget is a Widget of a composite class,It simply combines widgets in the build() method to form a nested structure. The build() method is implemented by ScollView in this inheritance relationship.

In this method, we first call the abstract method List

buildSlivers(BuildContext Context) to get a Widget collection of slivers. This method is ultimately implemented by ListView, which returns a SliverList (the only element in the set of Slivers). This collection is passed as a parameter to buildViewport, and the result of this method is nested in the Scrollable

This method returns a component of the Viewport class, which is a component that can display multiple widgets (using the Sliver layout protocol), displaying different areas based on the slide offset provided by the Scrollable collection of slide gestures. The main nested structure for the entire Widger is ScollView(ListView) -> Scrollable -> Viewport -> SliverList. After reading these two pieces of source code, go back and see the small figure above, is it much clearer?


The formation of three trees

As we know, with the Flutter, the widget is just a configuration file, and the main overhead in the build process is the Element tree setup and update, managed by BuildOwner. Based on the Widget nesting structure outlined above, we can find out the structure of the corresponding three trees (ignoring the minor nested structure among them).

As mentioned in the section on how the Widget, Element, and Render trees are formed, the Element tree is formed by recursively inserting itself into the tree by calling the element.mount () method for each node nested in the Widget. The core of the mount() process is the updateChild(_child, built, slot) method. When first built, this method calls the child node’s inflateWidget(newWidget, newSlot) to generate the corresponding Element object and insert it into the tree.

For the widget-ViewportElement of the rendering class, as mentioned in the previous article, the Element collection of the Child node is mounted to its Children property, and the RenderObject object is managed by a two-way linked list. Here as there is only one child that is under the Viewport SliverList, so here he is only a child node SliverMutiBoxAdaptorElement. And finally SliverMultiBoxAdaptorElement node, we found out that he did not rewrite the mount () method

So what we’re doing here is mount() to RenderObjectElement.

RenderObjectElement. Mount () we have in the previous analysis, this method first calls the super. Mount () to mount the Element in the tree. The core logic that follows is the method of marking in the diagram. This method will go up to the nearest RenderObject and mount itself to it, forming a RenderObject tree.

In fact, the Element and Render tree only go down to the ListView level, not associated with each item. So how does each item node get inserted into the tree when we use the ListView?


Lazy ListView loading process

To solve the above confusion, consider two essential questions first.

1. In the current UI system of Flutter, are there widgets that can bypass the Element tree and be displayed directly on the screen (regardless of the underlying Api such as Scene)?

2. What happens if ListView items are all mounted to the Element tree during the mount phase?

The first problem is that with such a Widget, each item in the ListView might not need to be mounted to display. But as far as I know, there is no such thing. Widgets that are rendered to the screen will eventually implement the drawing details through RenderObject. Looking at the markNeedsPaint() method on RenderObject, one of the key points in its invocation is that it relies on the tree structure. The RenderObject tree depends on RenderObjectElement. So each item in the ListView must be merged into the Element and RenderObject trees at some stage.

The second problem is that when we use ListView, we tend to have a large number of items. If all the nodes are mounted at one time in the mount phase, then the nodes under construction are prone to lag. There is also an important design method of lazy loading that borrows from the original idea.

Lazy loading can be understood as loading on demand. What is the meaning of “on demand”? “On demand” is a page element that needs to be displayed on the screen, so how do we know that this element needs to be displayed on the page? The simplest way to think about it is to keep laying out children until the current window scope is full or has no children. In The Flutter, an additional cache (double cacheExtent) was added, so the range becomes the window size plus the cache size (default is 250).


Layout building process

When it comes to layout, it’s natural to start with the RenderObject tree and look at the layout of the RenderViewport first.

  ///The layout is determined by the parent node only, not the child node, and the width and height are obtained from performResize
  @override
  bool get sizedByParent => true;

  @override
  void performResize() {
    assert(() {
      if(! constraints.hasBoundedHeight || ! constraints.hasBoundedWidth) {///Throws an exception with no height or width limit
      }
      return true; } ());///Dimensions are maximum width and height
    size = constraints.biggest;
    switch (axis) {
      case Axis.vertical:
        offset.applyViewportDimension(size.height);
        break;
      case Axis.horizontal:
        offset.applyViewportDimension(size.width);
        break; }}Copy the code

Because RenderViewport sizeByParent is true, which means that its size is only determined by the parent constraint, not the child constraint. Let’s look at performLayout()

@override
  void performLayout() {
    double mainAxisExtent;
    double crossAxisExtent;
    switch (axis) {
      case Axis.vertical:
        mainAxisExtent = size.height;
        crossAxisExtent = size.width;
        break;
      case Axis.horizontal:
        mainAxisExtent = size.width;
        crossAxisExtent = size.height;
        break;
    }

    final double centerOffsetAdjustment = center.centerOffsetAdjustment;
    double correction;
    int count = 0;
    do {
      ///Try to layout
      correction = _attemptLayout(mainAxisExtent, crossAxisExtent, offset.pixels + centerOffsetAdjustment);
      if(correction ! =0.0) {
      	///Error, correct
        offset.correctBy(correction);
      } else {
      	///No error, out of the loop
        if (offset.applyContentDimensions(
              math.min(0.0, _minScrollExtent + mainAxisExtent * anchor),
              math.max(0.0, _maxScrollExtent - mainAxisExtent * (1.0 - anchor)),
           ))
          break;
      }
      count += 1;
    } while (count < _maxLayoutCycles);
  }
Copy the code

This method calls _attemptLayout and eventually layoutChildSequence for each and all of the attemptLayout children in the Viewport (using the Sliver constraint layout, Since we only have one child, RenderSliverList, let’s see how it’s laid out.

The source code is too long, so if I use Element as a clue, I draw a sequence diagram to show the main flow. RenderSliverList has a loop that insertAndLayoutChild() is called when endScrollOffset is less than targetEndScrollOffset, This method will eventually call into SliverMultiBoxAdaptorElement, generated by the agent class SliverChildBuilderDelegate child (ListView itemBuilder deliver here), Then layout each child, adding endScrollOffset.

With this understanding, go back to the previous structure diagram, is it a little clearer?

There are, of course, two details that we can focus on

In the layout of RenderSliverList, element creation of the child node is run in BuildOwner’s buildScope method

2. The ListView will nest the KeyedSubtree, AutomaticeKeepAlive, and RepaintBoundary components via a delegate for each Child node

Finally, how does upYang Manage items in the Flutter ListView? This process is shown in the two divine diagrams drawn in.

1, ListView creation

2. ListView scrolling


Analysis of performance problems when using ListView

After a rough understanding of the ListView construction process, we began to analyze the problems in the use of ListView.

1. Loading more updates

As mentioned in the article about The Flutter high performance, versatile full scene scrolling container, our use of ListView tends to combine refresh controls to add more functionality to load. As we load more, we tend to refresh the list to show more elements. Will call to SliverMultiBoxAdaptorElement. PerformRebuild ()

Clear the cache of all Child Widgets, build the Child Widget again, and update the Child Element. If data changes are encountered, such as INSERT or delete, it is likely that the element cannot be reused, and the cost of rebuild will be higher. The breakpoint shows that after setState() is called, the build of the item is revisited. If you have more granular operations on the ListView, such as adding or removing Adapter from the native ListView, you can optimize this scenario.

2. Reuse of elements after they are recycled

Second Element is the reuse, SliverMultiBoxAdaptorElement cache by _childElements elements, when scrolling is beyond the scope of the viewport display and preload or data source change, Unneeded elements are collected by calling the collectGarbage method;

This method will eventually call to SliverMultiBoxAdaptorElement. RemoveChild (RenderBox child)

The first argument to the core updateChild method is an element object for index, and the second argument is null. I’ve been using setState()? When the second argument is null, the previous Element object is unmounted (). In this case, the element object corresponding to the index will be created again. So you can create an Element cache pool and get it first from the buffer pool when you create it.

3. Frame on screen

The last point is that the frame of each item is on the screen, which I feel is more meaningful. Because even though we optimized to load more scenes above, any Widget that can be displayed on the screen will be built when the ListView is created. If the item is more complex, a build lag may occur when entering the page. It’s a good idea to render complex widgets in multiple frames with placeholder peaking, but I haven’t figured out how to do that yet.


At the end

It took a long time to sort through the source code, and it was far from as smooth as the writing, because of the long study of the early Sliver constraint layout process. But in fact, our optimization and layout relationship is not very big, according to the clues to comb the trunk source code, so as not to fall into it cannot extricate themselves.

Finally, thank you for your reference to learn the article:

French guy: big guy, silver’s lifetime enemy Flutter silver’s lifetime enemy (ExtendedList)

UpYang: Cartography guy, very deep about slide study how does the Flutter ListView scroll?

TravelingLight_ : Big guy, check out the various Slivers interpretation of Flutter – Step-by-step Sliver

For the ListView analysis to here, the following is going to be for these several problems to open dry! After understanding the principle, I also have some ideas for solving the problem. In the next issue, we will talk about the function design and planning of this ListView. Welcome to keep paying attention!!

Finally ask a like QAQ, your like is my update on the road strong motivation.