ListView fluency doubled!! Flutter caton analysis and general optimization scheme

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.

Principle of the article:

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

2. ListView construction process and performance analysis

Practical article:

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

2. ListView performance problems and optimization scheme

3. Open source !!!!

Ps: The component has been basically completed on the basic function and performance optimization, will open source, focus like don’t miss the latest information!

Last time we looked at the ListView component’s functional design, this time we’ll focus on the root cause and optimization of ListView lag in terms of performance.


Is ListView performance problematic?

In daily business development, we use the ListView component in a variety of scenarios. Use it to quickly complete a list page, or to fit into a small screen. Is there a performance problem with using the native ListView component? The answer is yes, and in a real business scenario, I’ve come across just such a page (the UI doesn’t look like this)

This is a vertical list with three Textfields for each row of items. The Performance OverLay shows that in general, this page has a serious slowdown on my Vivo X23(Snapdragon 660) in Profile mode.


Two, why the emergence of caton?

The basic principle of

As we know, the process of sliding the list is actually made up of individual frames, the term is frames. For most people, at 60 frames per second, or 60FPS, the process is smooth. When it’s less than 60FPS, it feels like it’s stuck.

At 60 frames per second, that means an average of 16.7ms between frames. If it is longer than 16.7ms, there will be a lag in perception. As you can see from the DevToools tool provided by the system, the above example takes up to 130ms per frame when a card occurs.

What phases does the system go through in order to draw a frame?

Why does a frame take more than 16.7ms? To understand this we need to know, what does the Flutter do in order to draw a frame?

To get the answer, just search for drawFrame() in any Flutter project.

The English annotation on this method is very detailed, I recommend you to have a look. There are 10 steps, of which, the following steps are close to the developer.

Animation -> Build -> Layout -> Draw -> Composition

The cause of the lag

With the DevTools diagrams, we can see that. The bulk of the build time in the previous 130ms was concentrated in the build method called in the Layout

According to the frame drawing process mentioned above, we can see that the layout is built after the build, why is the build in the layout?

As I mentioned in my analysis of the performance problem with the Flutter enhanced list-ListView, the ListView in the Flutter uses lazy loading. For each item in the ListView, not all of them will be built during the build phase. Instead, in the layout phase, each item is dynamically constructed according to the current screen size and the scope of the cache area. The process is shown in the figure (from upYang).

Therefore, in the above analysis diagram, layout is mainly carried out by ListView, and build is each child node. In fact, if you take a closer look at each build, the structure below each build is basically KeyedSubTree, AutomaticKeepAlive, KeepAlive, etc. This is what the ListView wraps for each item when it is built. The detailed principles are mentioned in the Flutter enhancement List-ListView performance problem analysis.

So the obvious reason for the stalling is that the ListView builds multiple complex items within a given frame. For example, in the analysis diagram, multiple items were built at the same time in the Layout stage. The construction time of one item was close to 10ms, and the construction naturally exceeded 16ms.


3. Which scenarios are prone to lag?

Now that we know what causes ListView stalling, when does the stalling mainly occur? In combination with my own testing findings, it is mainly in the following three stages.

  • 1. First entry, when the list is built

When we open a ListView build page, because there are no items in the ListView at this time, we will build several times, such as the 130ms of the above example.

  • 2. Fast slide to build multiple items in one frame

When we are in the process of fast sliding, because the sliding range is relatively large, it is also possible to cause the construction of multiple items.

  • 3, setState to load more

The third scenario is on some paging list, where we tend to update the list with setState() after the data request is completed, which ends up in performRebuild() of the corresponding Element in the ListView

The _childElements are the cached item nodes (that is, all items currently on screen and in the cache), where each item is updated. At the same time, because there are more child nodes (the number of items increases), new items will be built, which is also easy to cause lag.


3. How to optimize ListView lag?

Optimization idea

1. Frame on screen

The essence of Caton is that the module takes too long to run within a frame. This is not just a ListView problem, it is the same for all pages with complex elements. So is there a general solution to this problem? The answer is simple. There are two ways to think about it: The first type of module time optimization (such as layout optimization on Android) needs to be analyzed on a specific basis, because the reasons for module lag are various, such as complex widgets, no appropriate local refresh, or a large number of calculations performed by UI ISOLATE, etc. The second idea is to segment the time without optimizing the module to improve the fluency, which is commonly known as frame running. A picture is used to understand the principle:

Suppose we can display four items on the screen and each item takes 10ms to build. In the existing ListView layout process, the four items are constructed simultaneously in the first frame, for a total of 40ms.

After framing, in the first frame of the page we start by building a simple placeholder item, which can be a simple Container. Building four Containers in the first frame does not cause a lag, since it is rarely time consuming. After that, the actual four items are deferred to the next four frames for rendering. This way, for each 16.7ms, no render timeout occurs and the entire process does not lag.

Does this affect the user experience when the frames are on the screen? Look at the big factory:

In terms of experience, the list control structure is known to have an invisible Cache area, so most of the screen on the frame is done in this invisible area, which is not perceived by the user in the high-end or normal scrolling situation. On the low end of the fast sliding machine can clearly see the card blank situation, but the overall feeling is better than serious stop.

Combined with my tests in this period of time, this scheme does have almost no impact on high-end machines (test machine: Oneplus 7Pro) (related to the implementation scheme), and is significantly optimized in the middle and low end of the machine, with almost no lag in the use process.

2. LoadMore Incremental update

As mentioned above, item construction is driven by the ListView layout, so in the case of incremental update, we simply change itemCount and mark the ListView for layout. In the article, He mentioned that the Widget cache needed to be updated before the layout, but in fact, after 1.22, the Widget cache was officially removed because it had almost no optimization effect, so the process became easier.

3. Element reuse?

One more thing that Xianyu mentioned in the article is the reuse of Element. After communicating with Lwlizhe about this optimization point, I personally think that the effect may not be so obvious. If we take a ViewHolder as an example from a Native perspective, the essence of its reuse is to reduce the time to create a view and parse XML for items of the same type. There is a key method: onBindViewHolder binds data to the view.

But with Flutter, there is no way to bind widgets to items with different data, even though the items are of the same type. So the only thing you can do is to create a cache pool to hold elements, and get them from the cache first when creating them. The problem is that there is a cacheExtent cache designed to cache the elements in a cacheExtent. Personally, I don’t think there is much need to do an extra cache. In the simplest case, set the cacheExtent to a larger size.

Implementation scheme

With the basic idea how to achieve this function? I’m going to talk to you about the implementation of the split-screen.

The split-screen is simply a placeholder and a replacement for the actual Widget, but the key point is how to split-screen? .

Here with the help of conditional frame task queue implementation, its principle is shown in the figure.

First, tasks are added to the queue of the current ISOLATE to avoid affecting the rendering process of the system. In this way, tasks are scheduled after the system completes transactions such as rendering. However, the task is not executed immediately, but needs to meet certain conditions. Referring to the system practice, there is an enumeration of weight values. We define a weight value for each task, and the task can be executed only when the corresponding conditions are met.

For example, if our task weight is priority.idle, such a task will only be executed at full idle (depending on the defined scheduling policy). If there is an uninterrupted animation on the screen, the entire task queue will be blocked.

The task is as simple as replacing the placeholder Widget with the actual child, but it’s pretty playable.

The easiest way to do this is to simply replace it. To highlight the loading process, I changed the placeholder and the actual item to a strong color contrast. In practice, you can set the placeholder Widget close to the item style for better results.

This looks a little stiff, we can give it a transparency change ~

Or slide in from left to right

OHHHHHHHHHHH

In fact, you can see the whole process. When you slide slowly, you can’t see the Widget switch because of the preload. While there was an occasional frame timeout during the fast slide, the peak was nearly half as low as before, and the overall FPS was much more stable, with no noticeable downtime during the slide.

However, due to the addition of a lot of animation, part of the UI may be stalled ~ HAHAHAHHA


Four, non-list card solution

For delays caused by building classes, we can also decompose a complex frame into multiple frames to optimize the delays.

A complex page must be made up of complex elements, and here we have multiple rows under a column, with multiple complex widgets within each row. In this example, we can nest our frame Widget within each row module and render each row as a frame. The actual optimization is shown below:

This is a horizontal optimization for a page, and the same can be done for a vertical optimization for a complex Widget, which will not be illustrated here


V. Performance optimization data in current Profile mode:

The first time you enter the list, the build performance is improved by 90%

Quick swipes rarely appear to stall

Loading more improves performance by 80%

Memory comparison

The ListView swipes quicklyBKListView quick slide


Thank you Daniel Wu and Eddie Peng for your likes

In order to ensure the stability of the component, the program review and boundary test are still under way. I will meet you after there is no problem. Welcome to follow me to get the latest developments.

Of course, as mentioned earlier, there are two main ways to optimize fluency: (1) to reduce the time spent on modules, and (2) to split the time. Framing is one way to optimize the Flutter, and more attempts will be made in the future. Now I am planning a series of optimization ideas for Flutter, from monitoring to performance tools to optimization, and I will share with you the technical points of practical application and project with the framework knowledge. Welcome to pay attention to my nuggets and public micro signal, to bring you the driest knowledge.

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 ~

Didn’t expect kaido, I’m back !!!!!