preface

Since its inception, the Flutter has been known for its ease of building beautiful, high-performance components with the goal of providing performance that approximates “native performance” at 60 frames per second (FPS), or 120fps on a device that can reach 120Hz. The frame rate is the number of frames per second that can be transmitted. It is an important indicator of screen lag in performance optimization. The Timeline tool is used to measure the frame rate of an application.

Note: There are several aspects to describe Performance indicators. This article focuses on Timeline

Flutter performance analysis performance

Flutter supports three modes of app compilation, at different stages of development, using different modes of app

Debug mode

Entering flutter run on the command line will activate debug mode by default. In this mode, the app uses the JIT compilation mode and the execution program is parsed at runtime, which means the application has a slower performance experience. A long black screen during cold startup or the first initialization of the Flutter Engine, frame drop, and lag are all normal.

One notable feature of this mode is that it can be hot-loaded, so it’s more like developing a front-end application

The Release pattern

The command line type flutter run –release will compile the application in release mode, which provides the most optimized and performance experience. The AOT compilation technology is used to compile the code from the DART native virtual machine to machine code for platforms such as Android and IOS. Equivalent to native development, time to compile, loss of hot overloading, but good performance. Generally, it is used to send packets to the application market, and the debugging functions in debug mode are lost

Profile mode

The command line type flutter run — Profile will be compiled using profile mode, a special debug application performance mode. This mode is not recommended to run on a virtual machine or emulator, but preserves some of the debugging capability. Because it can’t truly represent real machine performance.

Enter this command, after running, the console will print the debugging address

Click to jump to the debug page

Select Timeline and click the Flutter Developer button to enter the debug page of Timeline. Operate on the mobile phone for a few seconds, and click the Refresh button in the upper right corner to load the image. The page code should also be temporarily borrowed from SystemTrace, and the operation is similar

This is actually the previous version of debugging, although it can be used now, but the actual test found that it is not accurate, it is not convenient to use, now basically do not use this way, the new version of the Performance page is beautiful and convenient to use, You can use the pages in the Flutter Performance plugin in Android Studio to roughly determine whether the timeline is lagging or not, or you can turn on the Open DevTools button in the lower right corner of the Flutter Performance to analyze it on the web page.

Flutter Inspector

By the way, there are two other Flutter plug-ins in Android Studio. One is the Flutter Outline, which shows an Outline of the page layout. You can quickly view the tree structure of the page layout, and the menu bar provides a quick way to package, delete, move components up and down. Another plugin is the Flutter Inspector. After installing the Flutter plugin, this TAB appears in the right sidebar of the AS. It is used for layout debugging during development. This is relatively poor compared to the experience of native development XML to quickly locate layout files, and I believe there will be good optimization in the later stage.

Click the TAB and the page is as follows

The page is divided into the upper function button, the left View tree, the right layout preview.

What each button does:

Select Widget Mode

Select component mode and click on a Widget in the View Tree below to automatically locate the code. This allows you to quickly locate the code during development

Refresh Tree

Refresh the View Tree. After jumping to other pages in the App, the View Tree does not automatically update, so there is this button

Slow Animations

Slow down the animation

Debug Paint

Display layout measurements to quickly determine component boundaries, as shown below

Show Paint Baselines

Displays Baseline of the Text component for easy Text alignment

Show Repaint Rainbow

Displays color changes when redrawn

Invert Oversized Images

Easily view pictures with a higher resolution than display

Flutter Performance

Click on the right column Flutter Performance to display the following page:

The first button in the top left corner shows the Performance Overlay on the phone. The effect is shown below. The other buttons are the same as in the Inspector

On the top side, you can roughly determine whether the App drops frames, the white is normal, the red is stuck, the middle memory is occupied, and the bottom is the redraw status of each component. Click on the lower right corner of the Open DevTools to use more functions

Track Widget Rebuilds can be checked to see the redraw status of the components on the page. If you don’t want to redraw the components, adjust the code hierarchy or pull out the components to avoid performance losses. Here are some of the lessons I’ve learned during development:

  • Write large pages with StatefulWidget as little as possible, and avoid using setState in StatefulWidget as much as possible
  • You do not need to redraw the component to add the const keyword
  • The Provider refresh mechanism uses Consumer to sink the refresh range
  • The widget needs to be refreshed extracted as a StatefulWidget, narrowing the refresh scope

Timeline

The timeline event diagram shows all the event traces in the application. The Flutter framework emits timeline events when building frames, drawing scenes, and tracking other activities such as HTTP traffic. These events are shown on the timeline. You can also send your own Timeline events through dart: Developer Timeline and TimelineTask apis

Timeline event track format and viewer is used by many other projects, including Chromium & Android (via Systrace).

Track records are stored in JSON file format. Click the Export button in the upper right corner to Export the file.

After you’ve opened DevTools, run through the App for a while and click the Refresh button in the upper left corner to load the timeline shown below.

In the figure, the blue bar is the normal frame, and the red bar is the lag frame. Moving the mouse over the red bar can view the time of the current lag frame. In the upper right corner, there are the corresponding relationships of different color bars, including UI, Raster and Jank.

UI

The UI thread executes the Dart code in the Dart VM. This includes your application as well as the code in the Flutter framework. When your application creates and displays the scene, the UI thread creates a layer tree (a lightweight object containing device-independent drawing commands) and sends it to the raster thread to render on the device. Do not block the thread.

Raster

Raster threads (formerly called GPU threads) execute the graphics code in the Flutter Engine. This thread gets the layer tree and displays it by talking to the GPU. You can’t access the raster thread or its data directly, but if the thread is slow, it’s because of what you did in the Dart code. The graphics library Skia runs on this thread.

Sometimes scenes produce layer trees that are easy to construct, but trees rendered on raster threads are expensive. In this case, you need to figure out what the code is doing, which can cause the rendering code to slow down. Certain kinds of workloads are more difficult for Gpus. They can involve unnecessary calls to saveLayer (), opacity of intersecting multiple objects, and clips or shadows in specific cases

Jank

Frame rendering shows junk frames with red overlay. A frame is considered outdated if it takes more than approximately 16 milliseconds to complete (for 60 FPS devices). To achieve a frame rendering rate of 60 FPS (frames per second), each frame must be rendered in about 16 ms or less. When you miss this target, you may experience UI clutter or frame drops

Render Frames

When a Flutter application or Flutter Engine starts, it starts (or selects from the pool) three other threads. These threads sometimes overlap, but in general, they are referred to as UI threads, GPU threads, and IO threads. The workflow between UI and GPU is as follows:

To generate a frame, the Flutter Engine is first equipped with the Vsync latch. A Vsync event will indicate the Engine to start some work and eventually draw a new frame for display on the screen. The vsync event is generated at a rate determined by the refresh rate of the hardware platform.

Vsync will first wake up the UI thread. The UI thread’s job is to convert the Widget tree you wrote in your code into the RenderTree you want to render. There are three concepts in the Flutter: WidgetTree, ElementTree, RenderTree, The Widget tree in the Dart file is not ultimately involved in drawing, but is simply a configuration that makes it easier for developers to write pages. For example, if we specify that there is a vertical list, Column, and there are three parallel texts in the list, the Flutter will generate elements in the corresponding positions according to the corresponding semantics. This is the real component of the Flutter UI, which is the Element displayed on the screen.

The component tree is translated to the screen by a layer of renderObjects. Renderobjects are the actual render objects that are responsible for the layout measurement and drawing operations. The purpose of this is to better deal with frequent changes in the upper UI, to compare and update as much as possible, and to modify the configuration rather than directly create the lower tree. Because the RenderObject tree is expensive to create, widgets are recreated, ElementTree and RenderTree are not completely recreated, but some nodes are reused to improve performance. The process by which the UI thread works to generate the RenderTree is called the RenderTree

Once the render tree is created, the GPU thread is awakened, and its job is to convert the render tree information to the GPU’s command buffer, and then submit the data to the GPU for execution in the same thread

The sample

Simulate a component time-consuming operation


class PageOne extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.tealAccent,
      child: Center(
        child: ListView(
          children: [
            for(var i=0; i<100000; i++) _buildItemWidget(i), ], ), ), ); } Widget _buildItemWidget(int i) {
    var line = lines[i % lines.length];
    return Padding(
        padding: EdgeInsets.symmetric(vertical: 12,horizontal: 18),
      child: Row(
        children: [
          Container(
            color: Colors.black,
            child: SizedBox(
              width: 30,
              height: 30,
              child: Center(
                child: Text(
                  line.substring(0.1),
                  style: TextStyle(color: Colors.white),
                ),
              ),
            ),
          ),
          SizedBox(width: 10,),
          Expanded(child: Text(
            line,
            softWrap: false[) [) [) [ }}Copy the code

As you can see, when the ListView children are populated, the layout is not reused. Instead, we simulate a situation where the page repeatedly creates 100,000 child entries. When the page loads for the first time, we see a significant lag.

Click the location where Jank occurs, you can see that the event corresponding to the Timeline Events is selected, and the execution time of each method is displayed below. You can see that the _buildItemWidget method takes 26.81ms and frames are dropped

It takes time to simulate a method


class PageTwo extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.greenAccent,
      child: Center(
        child: Text(
          'Page 2' + _fibonacci(30).toString(),
          style: TextStyle(color: Colors.black, fontSize: 20.0),),),); }static int _fibonacci(int i) {
    if(i <= 1) return i;
    return _fibonacci(i - 1) + _fibonacci(i - 2); }}Copy the code

When building initialization, execute a recursive call to Fibonacci function, the time complexity is O(2^n), pass parameter 30, that is, the function run 2^30 times, initialization page can see obvious delay, location time method is the same as above.

The sample code has been uploaded to Github

expand

Here to recommend a small tool fPS_monitor, shell classmate open source detection page fluency of the small tool, can be more intuitive and quantitative evaluation of page fluency, the page probably looks like this

The maximum and average time can directly observe the comparison effect before and after page optimization.

Page flow is divided into four levels: smooth (blue), good (yellow), Slow (pink), slow (red). FPS is translated into one frame, different colors are used for each level, and count the number of times each level occurs

Specific can jump link: juejin.cn/post/694791…

conclusion

Performance tuning is a constant topic on any platform and in any language, and understanding how performance tuning works and how to sharpen your observations is crucial for a developer. The plug-ins and performance analysis tools provided by Flutter can help us quickly locate code problems and improve development efficiency. The Flutter Inspector can improve the quality of the page coding during the code writing phase, and the Timeline can detect which pages have serious frame drop during the run phase and focus on analysis.

Refer to the link

Medium.com/flutter/pro…

Cloud.tencent.com/developer/a…

Juejin. Cn/post / 694013…