By Feige, from Xianyu Technology Team
Flutter carries the main services of Xianyu, including search and details. Flutter is a cross-terminal technical solution with high performance, but sometimes it cannot meet the application performance requirements. With the rapid iteration of services and the increasing service complexity, problems with Flutter occur, including slow startup, slow page loading, and page sliding lags. These problems affect the user experience.
To this end, we have upgraded the user experience of the Xianyu app. The Xianyu app has been systematically optimized, starting from the key nodes. The APK thinning, startup speed-up, and optimization for the page loading duration and fluency are included. With these optimizations, the user experience is improved.
This article describes the performance and experience optimizations of Xianyu on the Flutter side in three parts:
- Fluency Optimization
- Optimization for search page loading
- Optimization for detail page loading
The solution is shown in the following figure:
The most difficult issue is Flutter fluency. In terms of the online data and offline user experience, the fluency of the search page and product details page are low. There is also a lot of feedback about page lags from the public.
Now, let’s introduce fluency optimization in three aspects:
- The Construction of Flutter Tools for Lag Locating
- LoadMore Optimization For Long Lists
- Thumbnails Scrolling Loading
The Construction of Flutter Tools for Lag Locating
Flutter builds UI in a data-driven manner. There are some declarations in the official documentation, Best Practices of Flutter Performance:
- Avoid repeated and time-consuming work in the
build()method because the
build()method of the child widget will be called frequently when the parent widget is rebuilt.
- Avoid returning an excessively large widget in a super-long
build()method. Split the widget into different small widgets and encapsulate them.
However, with the rapid iteration of the business, developers are often careless and write low-performance code. Ideally, the performance analysis tool should automatically locate the incorrect code and modify it in time.
Flutter officially provides a performance analysis tool called DevTools. Its Timeline interface analyzes the UI performance of applications frame by frame, and the CPU Profiler interface captures the time-consuming operations when lags occur.
During the R&D phase, we can use this tool to analyze local replicable lags. However, there are limitations with DevTools:
- It only works after the lags occur, and it is unable to monitor the lags.
- It can only be used for offline troubleshooting but not for online lag feedback because there are not enough scenarios and capabilities.
For this reason, we need to build a Flutter lag tool to monitor time-consuming functions online and offline that lead to lags.
- Implementation Principle: The Dart code in Release mode is compiled based on AOT, and the business code and SDK are compiled into platform-related machine code. Therefore, we can capture the native stack through the signaling mechanism and then obtain the Flutter stack through symbolic restoration.
We can identify two low fluency issues using the lag tool: time-consuming function and over-rendering.
Locating Time-Consuming Functions
For lags caused by time-consuming functions, the time-consuming functions can be located by viewing the stack.
The call stack shows how data serialization and deserialization during channel calling are time-consuming.
The code of FxImage shows that the native implementation corresponding to resumeImage has been empty. It means channel calling can be omitted on the Flutter side.
A large amount of data reported in the automation stage is stacks in the rendering stage. The business code cannot be located because Flutter’s refreshing mechanism is a centralized and asynchronous rendering mechanism.
The service layer needs to refresh the interface elements. The framework layer first marks the elements to be updated as dirty elements and then waits for the next rendering callback on the engine side. During this rendering callback, the collected dirty elements are updated uniformly.
This mechanism results in a large number of
Element.rebuild and update methods in the stack during the capturing and rendering stage, which are function call stacks of the Flutter framework. With service code missed, these stacks are not enough to locate lags.
Here is our solution: By increasing the dirty element information during rendering, the complexity of dirty elements can help identify an oversized refreshing range caused by incorrect code. The complexity is calculated based on the depth, length, and sub-node number of elements.
With this solution, the over-rendering problem is located in the quick question component on the details page.
The dirty element information shows that the complexity of dirty elements is high.
- Optimization Scheme:
- Improve the building efficiency
setStaterefreshing data into sub-nodes
- Separate each label into
- Perform partial refreshing only
LoadMore Optimization for Long Lists
By using the lag tool, we find that during the scrolling loading of search results pages, the entire list container will be marked dirty and rebuilt, causing serious fluency problems. After analysis, we found that after more data was returned, the
setState was called after the data was added to the list, and the container widget was marked dirty.
In addition, due to the preloading, more logic is triggered when approaching the last frame at the bottom. Thus, the frequency of widgets being reconstructed and marked dirty becomes higher.
In the beginning, we started from the business layer. More data is pre-loaded and saved to memory first instead of calling the
setState directly. Then, the
setState is called when the page gets to the bottom to rebuild the list container. Later, we conducted optimization at the underlying layer of the list container. During LoadMore calling, the entire list container will not be marked dirty and reconstructed. The container loading and page sliding processes are greatly optimized with an experience improvement.
As shown in the following figure, the entire container is refreshed. The red area indicates the dirty widgets.
After optimization, only new cards are refreshed.
Thumbnails Scrolling Loading
The feed flow card contains a large number of images. During fast scrolling, loading a large number of images is a big challenge for the memory and I/O, which affects fluency, especially on low-end devices.
- Optimization Scheme: In the process of fast scrolling, only small fuzzy thumbnails are loaded. The original images are displayed gradually after scrolling stops, and the original images are not loaded beyond the screen area. This method optimizes the user experience.
The effect is shown in the figure. The small picture below is scaled five times to demonstrate the effect.
After optimizations, the FPS of the Xianyu details page and search page improved by 3. The number of severe lags in low-end devices reduced by half, and the FPS of middle- and high-end devices improved to 57 or above. The number of severe lags is nearly 0.
The online high-FPS data is shown below:
This is the FPS curve of online, low-end devices. The horizontal axis represents the frame rate range, and the green curve represents the optimized version. The closer the curve is to the right, the higher the fluency.
This is the FPS curve of online high-end devices. The green curve stands for the optimized version.
The high FPS data of the online search page is shown below:
This is the FPS curve of online, low-end devices. The green curve stands for the optimized version.
This is the FPS curve of online high-end devices. The green curve stands for the optimized version.
Optimization for Search Results Page Loading
After optimizing the fluency, let’s take a look at what optimization needs to be done for page loading.
Before optimization, there is a long loading process from keyword searching to the result displaying. We must focus on the business process to optimize the page loading speed. The opening process of the search results page is on the chart below:
The search result page is implemented by Flutter, but it is opened by clicking the Native page. It may take some time between route interception and container opening under the hybrid stack.
In this case, information can be prefetched from a URL. Asynchronous and parallel data prefetching can be performed while jump navigation is performed on Native, which can reduce the time required to open the page.
At the same time, the request RT of the search page is relatively long. Generally, the page has already prepared, while the network request is not returned yet. So, if the template is pre-loaded after the network request is returned, there will be a high hit rate. The optimized process is on the chart below:
Data prefetching and the template preloading solution is adopted using some parallel means. The loading time of the search results page is reduced by 300 ms on Android low-end devices.
At the same time, the skeleton screen animation (implemented by Lottie) is displayed during data requesting instead of the Xiaohuangyu loading animation, bringing a better user experience.
Optimization for Detail Page Loading
We mainly use the following three methods to optimize the detail page loading:
- FlutterBoost Optimization
- Data Pass-Through
- Transition Animation
Xianyu is still using the Native + Flutter hybrid development mode. FlutterBoost processes the mapping and page jump of the hybrid stack pages.
In the open processing of FlutterBoost, a new container is opened by the
startActivity(). In the jump scenarios of the detail pages, most detail pages are jumped from the Flutter pages. You can reuse previously opened containers.
For such scenarios, we have added a new feature to FlutterBoost. When a new Flutter page is opened on the existing Flutter page, two Flutter pages can share the same Flutter container (Activity, viewController). By doing so, the page opening can be accelerated. Moreover, the memory consumption can be reduced, and the transition animation of Flutter pages is also supported.
In some scenarios, search results pages and hot sales pages will jump to detail pages. In these scenarios, some detailed data can already be obtained through the previous interface. So, we can pass through this part of the data to the detail page. When requesting detailed data, simple content, such as the main picture, title, and price, is displayed first. Then, the detail page is updated after the detailed data is returned.
Based on the previous data pass-through, we also use transition animation for immersive page transitions, further improving the user experience.
- Implementation Process: During the routing and navigation process, the page building process is taken over by inheriting the
ModalRoute. The simplified detail page is animated, and the animation status is monitored. When the animation is completed, the
DetailPagecan be mounted to the widget tree.
Summary and Outlook
To sum up, in terms of technical details optimization, the main page loading duration and fluency are optimized. In addition to the optimization for business processes, some general optimization functions are developed, such as data prefetching, widget framing, long list loading, long list partial refreshing, and optimization for the long list scrolling difference algorithm. In terms of visual optimization, a large number of micro-animation elements are used in the design. Flutter- and Lottie-based animations are used on many pages, such as the publish page, the search result loading page, and loading and refreshing animations. After optimization, the Xianyu app works smoother.
Flutter engine startup will be explored further in the future. It is necessary to optimize Flutter startup, especially for the homepage after implementation. Due to the fast iteration of the business, the early optimization work will deteriorate easily. How can you guarantee efficiency and performance during rapid business changes? This is the next problem that needs to be solved. For example, through performance verification, unqualified code can be returned and provided with optimization suggestions before code integration. The performance optimization measures can be placed into the container framework.