Flutter Development App Optimization Journey

Recently, I have been doing flutter development for several years. Following the project iteration of the flutter version from 1.* to the current 3.*, I have gradually started to experience the performance optimization of the project. Some basic principles of flutter garbage collection mechanism (GC) are here I will not explain too much, presumably the relevant developers have heard of it a long time ago, if you are not very clear, you can also go to the Internet to check some principled articles of the great god. I won't go into too much detail here.

I developed a separate article about flutter optimization detection tools https://blog.csdn.net/BUG_delete/article/delete/129295442 , you can refer to it if necessary.

This article will continue to be updated and supplemented with new content as my understanding deepens and new technologies are learned. Siege lions who like flutter can like and bookmark this article, so that they can check for new content updates in a timely manner.

Below, I will briefly summarize some optimization methods and methods encountered in developing flutter projects by myself. For the encouragement of those who are interested.

1. Memory optimization

  1. Timely release beyond the scope of action.
    1. Pay attention to the closing and removal operations of a series of objects such as listener and provider.
  2. Reduce the creation and use of singleton models
    1. It is not necessarily that the less the better, the main principle is: it can be destroyed in time after exiting the minimum range that it can use.
    2. The value form of the provider is also a singleton.
  3. Avoid using context asynchronously
    1. ​​​​​​​​​​​​​​
  4. Image loading optimization

    1. When we only use pictures, there will be parameters of width and height in flutter's related picture loading constructor, and try to add parameter settings where pictures are used. Flutter internally uses width and height as the size parameters when caching the image, and repaints the image as the cache image, which avoids large images (which are actually not very large in size) from causing damage to the device memory. Necessary stress issues.

  5. Clean up in time (the GC recovery mechanism is not as smooth as the RAC mechanism, and the recovery operation does not feel like it is being done in real time, so we need to remove it in time when it is not in use)
    1. Image cleaning: PaintingBinding.instance.imageCache.clear();
    2. Webview clears the cache: _webViewController?.clearCache(); Note that the cleaning here will clean up the local storage of all webviews in the project, not just the pages loaded by the current webview
    3. Lottie cleanup: sharedLottieCache.clear(); (need to import the path: import 'package:lottie/src/providers/lottie_provider.dart'; maybe the current version 1.4.3 is strange to introduce the total file path of lottie is invalid)

2. Startup speed optimization

  1. Avoid starting too many time-consuming tasks for initialization (block loading initialization)
    1. eg: Network initialization can be placed on the splash screen page, and modules and SDKs that are strongly associated with users can be operated after login.
  2. It takes time to start the page to avoid doing it in the initState method.
    1. The task is processed in the state management mechanism, and the page starts the principle of "friendly interaction" (loading animation, or displaying old data first and then refreshing after new data processing is completed)
  3. There are time-consuming operations in the page build
  4. We should try to avoid performing time-consuming operations in build(), because build() will be called frequently, especially when the Widget is rebuilt.

    In addition, we don't need to perform blocking operations in the code. We can convert file reading, database operations, network requests, etc. into asynchronous methods through Future.

    Finally, for CPU-intensive operations, such as image compression, you can use isolate to make full use of multi-core CPUs.

    As a multi-threaded implementation in Flutter, isolate is called isolate (isolation) because each isolate has a separate memory .

    Flutter runs an event loop that takes the oldest event from the event queue, processes it, and returns the next event to process, and so on, until the event queue is emptied. Whenever an action is interrupted, the thread waits for the next event .

    In essence, not just isolate, all high-level APIs can be applied to asynchronous programming, such as Futures, Streams, async and await, all of which are built on this simple event loop.

    However, async and await are actually just alternative syntaxes for futures and streams, which change the code writing form from asynchronous to synchronous, mainly to help you write clearer and more concise code.

    In addition, async and await can also use try on catch finally for exception handling, which can help you deal with some exceptions in data parsing.

  5. Flutter engine preloading ( quote Zhihu )
    1. ​​​​​​​​​​​​​​​​Using it can achieve the effect of opening the page in seconds. The specific implementation is:

      Define a preLoad method in the HIFlutterCacheManager class, use Looper.myQueue().addIdleHandler to add an idelHandler, and when the CPU is idle, the queueIdle method will be called back. In this method, you can initialize the FlutterEngine and cache it in the collection.

      After preloading is complete, you can get the cached engine from the collection through the getCachedFlutterEngine method of the HIFlutterCacheManager class.

  6. Dart VM warm up
    1. ​​​​​​​​​​​​​​​​For the mixed scene of Native + Flutter, if you don’t want to use the engine preloading method, you can also increase the startup speed of Flutter by preheating the Dart VM. This method will increase the loading speed of the Flutter engine to a certain extent, but the overall improvement in startup speed is not as much as that of the preloading engine.

      Whether it is engine preloading or Dart VM preheating, there is a certain memory cost. If the App memory pressure is not high, and it is predicted that the user will visit the Flutter business next, then using this optimization can bring good value; On the contrary, it may cause a waste of resources, which is of little significance.

  7.  

3. Layout loading optimization

  1. Avoid refreshing setState as a whole, (unless there are fewer elements on the Widget page, or many elements need to be updated)
    1. You can use dart's StreamBuilder, ValueListenableBuilder, GlobalKey and other operations to partially refresh.
    2. Components of the CustomPainter that draws the completed chart, avoiding redrawing problems during sliding. (RepaintBoundary) or flexibly handle the shouldRepaint method​​​​​​​​
      @override
        bool shouldRepaint(CustomPainter oldDelegate) {
          return oldDelegate != this;
        }
  2. element minimization principle
    1. Avoid all-in-one use of Container, and use SizeBox, DecoratedBox, and Padding appropriately to reduce the bloatedness of components.
    2. To use nil instead of unnecessary placeholder elements (SizeBox or Container), you need to understand that  nil is just a basic Widget element, and its construction cost is almost zero. Generally, we will use more in the ternary operator.
  3. use const wherever possible
    1. ​​​​​​​​​​​​​​​​If a certain instance has been defined with const, then when the const definition is used again in other places, it will be directly taken from the constant pool, which can save RAM. It should be used for some components that have not been modified for a long time.
    2. const is not only suitable for the constant definition of strings and numbers, but also for the construction of widgets
  4. Problems with nested swirls in the build() method (which is not a problem in itself):
    1. Poor code readability : When drawing an interface, a Widget needs to nest a Widget, but if the Widget nests too deeply, it will lead to poor code readability, which is not conducive to later maintenance and expansion.
    2. Difficult to reuse : Since all codes are in one build(), common UI codes cannot be reused to other pages or modules.
    3. Affect performance : When we call setState() on State, all Widgets in build() will be rebuilt, so the larger the Widget tree returned in build(), the more Widgets need to be rebuilt, and the The worse the performance.
    4. Therefore, you need to  control the time-consuming of the build method, disassemble the Widget into small pieces, and avoid returning a huge Widget directly, so that the Widget will enjoy finer-grained reconstruction and reuse.

    5. You can actually think of the Build function as a way to just render the UI, and it should.

  5. Reasonable use of StatelessWidget and StatefulWidget
    1. ​​​​​​​​​​​​​​​​​​StatelessWidget is more lightweight
  6. Avoid bulk processing with List.forEach
    1. ​​​​​​​​​​​​​​​​In fact, Future is a very good thing. For example, Future.forEach can also traverse the array, but in the process of execution, the impact on the main thread is indeed different. .
  7. Not required, the list form can be loaded using the builder method.
    1. eg:ListView.builder()、GridView.builder(),PageView.builder()
  8. Separation of variable layers from invariant layers
    1. Such as Gif, animation. At this time, we need RepaintBoundary, but the composition of independent layers also consumes, which needs to be measured and grasped. This will cause the page to rePaint on the same layer. At this time, you can use RepaintBoundary to wrap the changeable Gif component, so that it is in a separate layer, and finally combine it with a layer for the upper screen.
      image.png

4. Key indicators (page exception rate, page frame rate, page loading time, etc.)

  1. list optimization
    1. ​​​​​​​​​​​​​​For long lists, remember to use itemExtent in the ListView. itemExtent is very important, it will help Flutter to calculate the scrolling position of the ListView instead of calculating the height of each Widget, at the same time, it can make the scrolling animation have better performance .
    2. Reduce build time for collapsible ListView. For the collapsible ListView, when it is not expanded, set its itemCount to 0, so that the item will only be built when it is expanded, so as to reduce the first opening and building time of the page .
  2. Try not to set translucent effects for Widgets
    1. ​​​​​​​​​​​​​​​​Consider using a picture instead, so that part of the Widget area that is occluded does not need to be drawn.
  3. ​​​​​​​​Optimizing the raster thread
  4. Accelerate Flutter's performance-optimized raster thread with key
  5. Special adaptation for low-end devices
    1. Downgrade CustomScrollView, ListView and other pre-rendered areas to reasonable values

      By default, CustomScrollView will not only render the contents of the screen, but also render the content of the components in the upper and lower areas of 250. For example, the current screen can display 4 components, but there are still 4 components in the display state. If setState(), then There will be 8 component redraws. The actual user only sees 4, but it should only need to render 4, and sliding up and down will also trigger the creation and destruction of off-screen Widgets, causing scrolling freezes. High-performance mobile phones can pre-render, and the distance of this area is reduced to 0 or a smaller value on low-end mobile phones.
      image.png​​​​​​​​ 

5. Package size optimization

  1. Resource compression (pictures, json with pictures, gif, audio, video, etc.)
  2. Clean up unused and duplicate dependent libraries in time
  3. Enable code reduction and resource reduction ( quote Zhihu )

    Turn on minifyEnabled and shrinkResources, the size of the built release package will be reduced by about 10%, or even more.

  4. Packages for building single-ABI architectures

    At present, in the mobile phone market, x86 / x86_64/armeabi/mips / mips6 occupy a small amount. As the latest generation architecture, arm64-v8a is the current mainstream, while armeabi-v7a only exists in a small number of old mobile phones.

    Therefore, in order to further optimize the package size, you can build a single-architecture installation package. In Flutter, you can build a single-architecture installation package in the following ways :

    cd <flutter应用的android目录>
    flutter build apk --split-per-abi

    If you want to further compress the package size, you can dynamically deliver the so, and put the so on the remote end for dynamic loading, which can not only further reduce the package size, but also realize hot repair and dynamic loading of the code.

6. Other optimizations (good way of thinking, sdk introduction)

  1. An http plug-in library is recommended. The http plug-in library also supports the method of constructing multiple requests for one connection, so as to reduce the consumption and time waste of tcp handshake and disconnection from the network layer. It will be more useful when you need to request multiple interfaces at once.
  2. Identify images

  3. Flutter Inspector: Click "Invert Oversized Images", it will identify those images whose decoding size exceeds the display size, and the system will invert them, so you can find it more easily in the App page. ​​​​​​​​

    For these pictures, you can specify cacheWidth and cacheHeight as the display size, which allows the flutter engine to parse the pictures with the specified size and reduce memory consumption.

  4. Shader preheating: shader preheating helps to improve the smoothness of routing switching, please refer to shader preheating details for usage 
  5. There is an image in the ListView item to optimize memory

    Disable these two options by setting them to false, so that invisible child elements will be automatically processed and GC. ​​​​​​​​​​​​​​​​
    ListView.builder(
      ...
      addAutomaticKeepAlives: false (true by default)
      addRepaintBoundaries: false (true by default)
    );
    1. Operations such as redrawing child elements and managing state will take up more CPU and GPU resources, but it can solve your App's memory problem, and you will get a high-performance view list. ​​​​​​​​​​​​​​​​

Guess you like

Origin blog.csdn.net/BUG_delete/article/details/118572544