👏 Visit my GitHub for more content. Click to go to GitHub

During the development and work of Flutter, I began to optimize the performance of my code in addition to the specification requirements due to the increasingly demanding work content and an excellent colleague. The home page of the App we developed was the key point, and I was in charge of it. Therefore, after the simple UI and logic construction was completed, certain performance optimization was required, so I began to understand and learn related processing.






0. Knowledge of rendering

The 0.0 Flutter runs in four modes: Debug, Release, Profile, and Test, which are completely independent at build time.

Debug Debug mode can run on both the real machine and emulator: all assertions are turned on, including debugging messages, debugger AIDS (such as Observatory), and service extensions. Optimized for fast Develop /run loops, but not for execution speed, binary size, and deployment. The command Flutter Run runs in this mode and is built with Sky/Tools/GN -- Android or Sky/Tools /gn --ios. Sometimes called "checked mode" or "Slow mode." Release The Release mode runs only on real machines, not emulators: turns off all assertions and debugging information, and closes all debugger tools. Optimized for quick start, fast execution, and reduced package size. Disable all debugging AIDS and service extensions. This pattern is intended to be deployed to the end user. The command flutter run --release runs in this mode, Sky /tools/gn -- Android -- runtimemode =release or Sky/Tools /gn --ios -- runtimemode =release Profile Profile mode only runs on real machines, not emulators: Basically the same as Release mode except for service extensions and tracing, and some things that run to minimally support tracing (such as the ability to connect observatory to a process). The command flutter run --profile runs in this mode, Sky /tools/gn -- Android -- runtimemode =profile or sky/tools/gn --ios -- runtimemode =profile You cannot run on an emulator because the emulator does not represent a real scenario.testheadlesstestMode only runs on the desktop: Basically the same as Debug mode, except it's headless and you can run it on the desktop. Command fluttertestIt runs in this mode and builds through Sky/Tools /gn.Copy the code

The architecture of 0.1 Flutter is divided into three layers :Framework, Engine, and Embedder.

1. Dart implementation of the Framework, including Material Design-style Widgets,Cupertino style Widgets for iOS, basic text/image/button Widgets, rendering, animations, gestures, etc. The core code of this part is: The Flutter Package under the Flutter repository, and the PACKAGE of IO,async, UI under the Sky_engine repository (DART: THE UI library provides the interface between the Flutter framework and the engine). 2.Engine is implemented in C++, including Skia,Dart, and Text. Skia is an open source two-dimensional graphics library that provides a common API for a variety of hardware and software platforms. 3.Embedder is an Embedder layer where the Flutter is embedded on various platforms. The main work here is to render Surface setup, thread setup, plugins, etc. As can be seen here, the platform correlation layer of Flutter is very low, the platform (such as iOS) just provides a canvas, the rest of the rendering related logic is inside the Flutter, which gives it a good cross-end consistency.Copy the code

0.2 The relationship between Widget, Element and RenderObject is as follows:

The Widget tree is actually a configuration tree, while the real UI rendering tree is made up of elements. However, since the elements are generated by widgets, there is a correspondence between them, so in most scenarios, we can broadly think of the Widget tree as either a UI control tree or a UI rendering tree. A Widget object can correspond to multiple Element objects. It makes sense that multiple instances (Elements) can be created based on the same configuration (Widget). The general process from creation to rendering is to generate the Element according to the Widget, then create the corresponding RenderObject and associate it with the Element. RenderObject property, and finally complete the layout and drawing through the RenderObject.Copy the code






1. Can performance debugging be performed under the simulator?

Answer: Yes, but debugging is very inaccurate. Therefore, using emulators for performance debugging is not recommended. Almost all of the Flutter application performance debugging should be done in analysis mode on a real Android or iOS device. In general, the performance metrics of an application running in debug mode or on an emulator are not the same as those in release mode. Consider checking performance on the slowest device used by the user.Copy the code

  • Why it should run on a real machine:

    • Emulators use different hardware, so performance varies – some operations on the emulator are faster than on the real machine, while others are slower than on the real machine.

    • Debug mode adds additional checks (such as assertions) that can be quite resource-intensive compared to analysis mode or release compilation.

  • Code is also executed differently in debug mode and release mode. Debug compilation runs the application in “just in time” (JIT) mode, while analysis and release is precompiled to local instructions (” ahead of time “, or AOT) and then loaded into the device. The COMPILATION of the JIT itself can cause the application to pause, resulting in a lag.







2. How to test App performance?

Answer: 1. Run > Flutter Run main.dart in Android Studio and IntelliJinProfile Mode option 1.1 Select View > Tool Windows > Flutter Inspector. 1.2 On the toolbar, select the shelf icon. 2. In VS Code, open the launch.json file and set the flutterMode attribute to profile (change back to release or Debug when the analysis is complete). To open thecommandThe palette. 2.2 Enter Performance in the text box and select Toggle Performance Overlay in the pop-up list. If the command is not available, make sure the application is running. 3.From thecommandLine, use the --profile flag: 3.1 flutter run --profile 3.2 Trigger performance layer 4 with the --profile parameter. You can set the showPerformanceOverlay property in the MaterialApp or WidgetsApp constructor totrueClass MyApp extends StatelessWidget {@Override Widget build(BuildContext Context) {return MaterialApp(
      showPerformanceOverlay: true,
      title: 'My Awesome App',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'My Awesome App')); }}Copy the code






3. How do I view the analysis performance layer?

Answer: The performance layer shows the time consuming information of the application in two charts. If the UI is getting stuck (frame skipping), these diagrams can help analyze why. The diagram is shown at the top of the current application, but is not drawn in the normal widget style - the Flutter engine itself draws this layer to minimize the impact on performance. Each chart represents the last 300 frames of the current thread. Left: GPU thread performance is shown at the top, UI thread is shown at the bottom, and the vertical green bars represent the current frame. Right: Total time used per frame during renderingCopy the code
Flutter uses a few extra threads to do this. The developer's Dart code runs on the UI thread. Although it does not have direct access to other threads, the actions of the UI thread have an impact on the performance of the other threads. Platform thread The main thread of the platform. The plug-in code runs here. See the UIKit documentation for iOS or the MainThread documentation for Android for more information. The performance layer does not show the thread. UI Thread The UI thread executes Dart code on the Dart VM. This thread includes code written by the developer and code generated by the Flutter framework based on application behavior. When an application creates and presents a scene, the UI thread first creates a Layer tree, a lightweight object containing device-independent render commands, and sends the layer tree to the GPU thread to render to the device. Do not block this thread! The thread is shown in the lowest column of the performance layer. GPU thread The GPU thread retrieves the layer tree and notifies the GPU to render. Although you can't communicate directly with a GPU thread or its data, if that thread is slow, it must be somewhere in the developer's Dart code. The graphics library Skia runs on this thread, sometimes called the Rasterizer thread. The thread is shown in the top bar of the performance layer. I/O threads may block time-consuming tasks (I/O in most cases) from UI or GPU threads. This thread is not shown in the performance layer.Copy the code

The red bar indicates that the current frame is taking time to render and draw. When both charts are red, it is time to start diagnosing the UI thread (Dart VM).

Each frame should be created and displayed within 1/60 of a second (about 16ms). If one frame is timed out (any image) and cannot be displayed, resulting in a lag, one of the charts will show a red bar. If a red bar appears on a UI diagram, the Dart code is consuming a lot of resources. If the red bar appears on a GPU chart, it means the scene is too complex to render quickly.Copy the code
1. Lowering the frame render time below 16ms May not make much difference visually, but it can extend battery life and avoid heat problems. 2. It may work fine on your current test device, but consider using the lowest end of the device your application supports. 3. As 120FPS devices become popular, each frame needs to be rendered within 8ms to ensure a smooth and smooth experience.Copy the code






4. How to perform performance analysis and start processing?

4.1 Locate problems in UI charts

If the performance layer's UI chart shows red, start by analyzing the Dart VM, even if the GPU chart also shows red. The Dart DevTool provides functions such as performance analysis, heap testing, and code coverage. The Timeline interface for DevTool allows developers to analyze the UI performance of their application frame by frame.Copy the code

(Observatory was replaced by Dart DevTools. This browser-based tool is still under development, but is only for previewing. Refer to the DevTools’ Docs page for installation and use instructions.)

4.2 Locating faults in a GPU chart

In some cases, the layer tree of the interface is easy to construct but time-consuming to render on the GPU thread. When this happens, the UI chart does not show red, but the GPU chart shows red. This is where you need to find out what is causing the slow rendering in your code. Certain types of loads can be more complex for gpus. This can include unnecessary calls to a saveLayer, complex operations between many objects, and clipping or shading in specific situations.Copy the code

If the reason for inference is a lag in the animation, you can use the timeDilation property to dramatically slow down the animation. You can also use the Flutter Inspector to slow down the animation. In the Inspector, select Enable Slow Animations under the Gear menu. If you want to do more with the animation speed, set the timeDilation property in your code. Does catton happen in the first frame or throughout the animation? If it's the whole animation, is it clipping? There may be an alternative to clipping for drawing scenes. For example, rectangles on opaque layers are cropped with sharp corners instead of rounded corners. Try using RepaintBoundary for fading in, rotating, or other operations on a static scene.

4.2.1 Checking off-screen Views

saveLayer

The saveLayer method is one of the heaviest operations in the Flutter framework. This method is useful when updating the screen, but it can slow down the application and should be avoided if not necessary. Even if the saveLayer is not explicitly called, it may be called indirectly in some other operation. Can use PerformanceOverlayLayer. CheckerboardOffscreenLayers switch to check whether or not to use the saveLayer scene. After turning on the switch, run the application and check to see if the profile of the image flickers. The container flashes if a new frame is rendered. For example, there may be a set of objects whose transparency is to be rendered using a saveLayer. In this case, it may be better to apply transparency to each widget individually than through higher-level parent widget operations in the Widget tree. The same goes for other potentially resource-intensive operations, such as cropping or shading.

Opacity, clipping, and shadows are not a bad idea in and of themselves. However, operations on widgets at the top level of the Widget tree can result in additional saveLayer calls and unwanted processing.

4.2.2 Check for uncached images

RepaintBoundary It is a good idea to use RepaintBoundary to cache images when needed. One of the heaviest operations from a resource perspective is to render a texture with an image file. First, the compressed image needs to be extracted from persistent storage, decompressed into host storage (GPU storage), and transferred to device storage (RAM). That said, I/O operations on images are heavyweight. The cache provides a snapshot of the complexity level so that it can be easily rendered into subsequent frames. Because the raster cache entry takes a lot of resources to build and increases the load on the GPU storage, images are only cached when necessary. Open PerformanceOverlayLayer. CheckerboardRasterCacheImages switch which can check the image cache. Run the application to view images rendered using a random color grid, identifying cached images. When interacting with the scene, the images in the grid should be static – the flicker view representing the re-cached images should not appear. For the most part, developers want to see static images in the grid, not non-static images. If the static image is not cached, you can cache it in the RepaintBoundary Widget. Although the engine may also ignore Repaint Boundary if it doesn’t think the image is complex enough.

4.2.3 Viewing widget rebuild performance

The design of the Flutter framework makes it difficult to build applications that do not achieve 60fps fluency. Often, when things get stuck, it’s a simple bug that the UI is being rebuilt more per frame than it needs to be. Widget Rebuild Profiler can help debug and fix bugs caused by these issues. You can view the number of widget rebuilds under the current screen and frame in the Widget Inspector. For details, refer to the display performance data in developing the Flutter app in Android Studio or intelliJ-like.






5.UI application performance optimization summary

5.1 the UI rendering

5.2 UI Debugging Procedure

1. Set in mian

  • DebugDumpLayerTree ○ View the Layer tree
  • DebugPaintLayerBordersEnabled view layer boundaries
  • DebugRepaintRainbowEnabled be redrawn RenderObject
  • DebugProfilePaintsEnabled in the observatory shows draw a tree

2. The real machine is running in the profile

3. Select Open TimeLine View. You are advised to Open it in Chrome

4. View analysis

5.3 Summary of UI Performance Improvement

1. Avoid repetitive and time-consuming work in the build() method because the child Wdiget’s build() method will be called frequently when the parent Widget is rebuilt.




2. When setState() is called on State, all descendant widgets are rebuilt. Therefore, move the call to setState() to the part of the Widget subtree where the UI actually needs to change. Avoid calling setState() higher in the Widget tree if the changed portion is contained in only a small part of the Widget tree. Improve build efficiency – reduce the starting point of traversal




3. The traversal stops when the same sub-Widget instance is encountered in the previous frame. This technique is used extensively within frames to optimize animations that do not affect subtrees. See the TransitionBuilder mode and SlideTransition using this principle to avoid recreating their offspring widgets during animation. Improve build efficiency – stop tree traversal




4. Add RepaintBoundary to a separate layer where you need to update to reduce the number of layer update nodes






6. Summary of GPU application performance optimization

6.1 GPU graphics rendering

Because the Dart code calls SKia's C and C++ code directly, the Dart code can match the Java code to achieve the performance of the native App.

Skia (open source graphics engine) is a C++ open source 2D vector graphics library (Cairo is a vector library), including fonts, coordinate transformations, bitmaps, etc., equivalent to lightweight Cairo, currently used on Google's Android and Chrome platforms. Skia works with OpenGL/ES and specific hardware features to enhance the display. In addition, Skia is one of the many graphical platforms that WebKit supports and is implemented in WebKit's GraphicsContext.h/.c.

6.2 GPU Debugging Procedure

The Skia has two very different backends. The Flutter uses a pure CPU back end in the iOS emulator, while real devices typically use GPU hardware to accelerate the back end, so the performance features are quite different

1. Run under project path: Flutter run --profile -- Trace-Skia 2. Click the link after the completion of the operation to open the TimeLine View, but at this time, you need to select All and select 3 for All functions. Then operate the App and hit Refresh to generate a rendering chart. 4. Flutter a frame is recorded as SkPicture (SKP) and sent to Skia for rendering. Screenshot --type=skia --observatory-port=<port> capture SKP and with [debugger.skia.org]() we can upload SKP and then step through each drawing command.Copy the code

6.3 Summary of GPU Performance Improvement

1. Avoid using the Opacity Widget, especially in animations. Set AnimatedOpacity or FadeInImage instead. For more information, see Performance Considerations for Opacity Animation

See Transparent Image for an example of applying transparency directly to an image, which is faster than using the Opacity widget. For example: Container(color: color.fromrgbo (255, 0, 0, 0.5)) 👍 Opacity(Opacity: 0.5, child: Container(color: 0.5) Colors. Red)). 🙅Copy the code




2. The Clip will not call saveLayer () (unless explicitly use Clip. AntiAliasWithSaveLayer), so the operation is not so time-consuming Opacity, but still very time consuming, so please use caution.




3. If most Children Widgets are not visible on the screen, avoid constructors that return a specific list (such as Column() or ListView()) to avoid build costs. Use lazy methods with callbacks (such as listView.Builder).




4. Avoid calling saveLayer().

Why does saveLayer cost so much? Calling saveLayer() opens up an off-screen buffer. Drawing content to an off-screen buffer can trigger render target switches, which are particularly slow on older Gpus. 1 ShaderMask 2 ColorFilter 3 Chip -- might cause call to saveLayer()ifdisabledColorAlpha ! = 0xff 4 Text -- might cause call to saveLayer()ifThere's an overflowShader to avoid calling saveLayer() : 1: To fade in and out of an image, consider using the FadeInImage widget, which applies the gradient opacity using the GPU's fragment shader. For more details, see the Opacity document. 2: To create rectangles with rounded corners, instead of applying clipping rectangles, consider using the borderRadius property provided by many widgets.Copy the code




5. Use Visibility to control Visibility when widgets are blocked and do not need to be rendered.




6. When using AnimatedBuilder, avoid building widget trees in widget builders that do not rely on animations. Each change in the animation recreates the Widget tree. Instead, build that part of the subtree and pass it to the AnimatedBuilder as a child.




7. Avoid cropping in animations. If possible, pre-cut the image before the animation begins.




8. Optimize your page. Performance costs when lots of images load, such as reducing image quality






Reference:

  1. Application performance optimization best practices with Flutter
  2. Performance Testing and Theory of Flutter (Analyze your Flutter app)
  3. Principles of high performance rendering with Flutter

👇 Recommended 👇 :

Daily learning about the accumulation of Flutter development