How does Flutter design a high-performance, versatile ListView component

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, due to the performance optimization related to the Flutter, after collecting a lot of performance data, I found that the ListView component was prone to page stutters in some scenarios (such as loading more). I saw the Flutter as a high performance, multi-function full scene rolling container of Idler fish. However, since this component is not open source, I am going to try to research and develop a high performance ListView from the ideas presented in this article. This series is expected to be divided into 4-5 articles, with the first two focusing on research and analysis of existing problems and the last three on actual development.

Practical article:

1. How does Flutter design a high-performance, multifunctional ListView component

2. How to solve the ListView performance problems in specific scenarios

3. Open source !!!!

PS: The component has completed the development of the function, is currently continuing to do performance optimization, will open source, focus on praise do not miss the latest information!!

In the previous two articles, we learned how widgets, elements, and Render form a tree structure in the Flutter. And the Flutter enhancement list-ListView performance analysis, familiar with the ListView control structure and construction process. If this is your first time reading this series, don’t worry, this article does not require any knowledge of the principles, I will be inclined to share the implementation of the component from the perspective of functional design, and my design architecture of the component, I believe you can achieve the same functionality!!


What capabilities does a multifunctional ListView component need to provide?

Since we claim to be a high-performance, versatile ListView, what capabilities should this component contain? First of all, I think that our design should try to ensure the single and perfect function of each module no matter the component or the architecture. Despite our claims of versatility, the component is still essentially just a ListView, so the capabilities provided should start around a scrollable list. In combination with xianyu’s article and personal daily use, I think ListView still lacks the following abilities.

1. Scroll to the specified index

We can use the ScrollController in the Flutter to control the ListView to scroll to a specified position, but this position is based on offset (offset pixels) rather than index. In practice we often use the ability to jump to the specified index. For example, if you want to linkage a TAB to a list, click TAB to jump to the specified list location.At this point, if our jump can be based on index, then this feature is very easy to implement.

2, automatic exposure ability

In a business scenario, we often need to expose items in a list. Currently, we tend to do this in the build function or initState of the item, but due to the ListView’s preloading and garbage collection mechanism, some things that are not on the screen will be exposed. The exposed item may be recycled for a second build, and will go through the exposure logic again. This requires us to add extra logic to our business code, which is very unreasonable to handle.

The exposure capability is actually a derivative of getting the item visible on the screen, so components should also include this capability.

3. Callback notification for garbage collection

Our colleagues encountered this in the actual business scenario. For the list loading of multiple images, even though the image component element that marked the screen was recycled, the image cache was still accumulated in memory, which caused a large number of OOM at that time. Finally, we solved this problem by using the external texture solution. While I agree that this problem should be addressed within the control itself, it would be a little more flexible if the item in the list was replaced by a video, or some other type of control, if there was a callback notification from garbage collection.

PS :(the above functions have been implemented ~~)


2. What scenarios will the high performance ListView component solve?

Above is the design for function, so from the perspective of performance, Xianyu also mentioned some problems we encountered in the article:

1. Incremental update in the LoadMore scenario

When we use the ListView, we tend to load more functionality with the refresh component. Most of the time, we will get more data, after calling setState UI update list, but after calling setState SliverMultiBoxAdaptorElement will and buffer for the current screen all the element in the update, node at this time, Very easy to cause the list to stall.

2. Element cache

Because the list is constantly creating new items and recycling invisible items (upYang:How does the Flutter ListView manage items?)For the recycled item, the element is recreated the next time it is visible. If you add an index-based cache, fetching elements directly from the cache in the up-and-down scenario should improve partial smoothness. Note, however, that caching here does not allow holder reuse in the same way as native caching, which will be discussed in more detail in the next article.

3. Frame on screen

For a complex item, a certain strategy was used to divide its build into several frames to slow down the delay in the list construction, and I got ideas in the communication with Lwlizhe.


Three, the implementation of each function

After making clear the functional requirements, I did not rush to start the development, but first think about the basic plan of the implementation of these functions and the connection between them (this period is mainly functional analysis, the next period will be performance analysis).

  • There are many open source solutions for the function of scrolling to the specified index. I found that there are two main ideas:

1. Rebuild the window and specify that we want to jump the index Widget to the top of the current window. Such as indexed_list_view.

The idea is simple, but emmmm, the effect is a little rough.

2. Cache the height of each item and calculate the offset to be scrolled when specifying the scroll index. For example list_view_item_builder

The core idea of this scheme is:A surrogate Widget wraps our actual item, notifying the Delegate of the layout information when the item is laid out for caching. The delegate calculates the offset and then calls the ScrollController to scroll. But because the ListView of Flutter uses a lazy loading mechanism (check it outThe Flutter enhances list-ListView performance problem analysisUnderstand the),It is possible that the index specified is not in the current screen range, so that there is no size information. So we need to keep scrolling until we find the index that we want.

This is a good idea, but the scrolling logic in it is a little complicated, and I also have a bug when I run example, for the index is off the screen, sometimes it doesn’t jump directly to the item we need.(if section 6 and section 5 are specified, multiple jumps will succeed)

But I felt that this was a little bit softer, so I finally took this idea and completely rerealized this ability.

  • Auto-exposure capability (Widget to get screen visibility)

Auto exposure is essentially a callback to the user about which widgets are visible on our current screen. Once we get the Size information of each item, the problem is solved.

Let’s arrange the ITMes and think of the ListView as a window. Changes the position of the window based on offset when sliding to display different items. According to the offset and the height of the window, we can get the starting point and the end point of the visual range. Then, based on the cache information of the item’s height, we can calculate the item on the current screen. To reduce the frequency of this method, we can add a sampling range so that the list slides past a certain threshold. A map is used to record the items that have been exposed, ensuring that each item is only exposed once.

  • Callback notification for garbage collection

    This is relatively simple, because while garbage collection is called from performLayout() in the RenderSliverList, it still ends up in the Element’s void removeChild(RenderBox Child). So the resource can be freed by notifting the child that is being destroyed every time an Element is called. However, this is related to the Element reuse we mentioned in performance optimization, and should also be considered in the design.


Iv. Overall structure design of components

First, let’s look at the relationships between the main classes in the current ListView

We usually use the ListView directly, but to implement the functions mentioned above, we need to customize the ListView deeply. Mentioned above, for example, to give each item a nested a proxy Widget notifications measuring the size of the information, then we can choose to rewrite SliverChildBuilderDelegate build method, in which the corresponding insert we need nested Widget. Given that the sender of the message necessarily needs to insert the receiver into this structure, I refer to the implementation of PageView here, and choose to nested in ListView to collect size information, pass this information to the custom ScrollController, who implements the specified index scrolling.

Above is the final class diagram. To distinguish the components of the system, I have added BK as the keyword for all the classes involved in the modification (I love our company a lot). The biggest change in this structure is the introduction of a new member, BKNotifier, which provides information about itemCount for other classes and is used to improve efficiency when we add or delete items from the list.

Regardless of their referential relationship, from a functional point of view, they have this relationship (different features with different color dotted lines).

If you’d like to know more, feel free to let us know in the comments section.


conclusion

Finally, put a map of the features that have been implemented so far ~, all features are being verified, performance is still being developed ~

For incremental update of performance data, the debug time is from 320ms->100ms, about 60%+ (time is not important, release will not take this time, pay attention to the improved efficiency)

At present, the component development is in the final stage, and I will try to meet you in two weeks.


Finally, thanks to Eddie Peng and Daniel Wu for your likes and comments!!

In this installment, I will share my thoughts from the perspective of functional design. In the past, when designing functional modules, I tended to get stuck in local details first, so that the more I did, the more problems I would find later, which greatly increased the difficulty of overall implementation. This time, I turned over the university’s software engineering materials, tried to solve the problem from top to bottom, followed the software development process, and considered the connection between each module. Many problems were exposed at the beginning, and the whole development process was much smoother.

The next installment will cover performance optimization and cover some of the principles. I recommend reading my previous articles on the principles to help you understand the Flutter Framework better.

  • How do Widgets, Elements, and Render form tree structures?

  • The Flutter enhances list-ListView performance problem analysis

PS: Thanks to Eddie Peng and Daniel Wu for your likes and comments!!

Giant’s latest words really shook me!!!!