1. Background

In Flutter development, in addition to hot updates, Flutter is most criticized for the poor experience of hybrid development, and the most important thing about hybrid development is the management of routing and component lifecycle.

At present, Flutter's cross-platform program consistency and excellent experience have been unanimously praised by most developers. But for projects that already have mature business code, it is almost unrealistic to use pure Flutter for development directly, so more projects use Flutter as a submodule without changing the original App business. Access and development, the structure is as follows.
在这里插入图片描述

In hybrid development, the stack will inevitably involve the jump between the Flutter page and the native page. Prior to Flutter 2.0, the official routing scheme had such defects as communication isolation, resource non-sharing, and possible huge memory loss (reflected in abnormal memory growth when opening multiple Flutter pages) under multiple engines. For this kind of problem, hybrid routing solutions such as flutter_boost, mix_stack, and flutter_thrio have emerged in the industry. The principle is to use a single-engine multiplexing solution, but there are still many pain points, mainly reflected in the following two points:

  • When mixed stack routing is in use, memory exceptions may still occur;
  • The modification of the underlying code of Flutter causes the upper-level framework to be continuously adapted.
  • 2. Multi-engine solution

    Before the Flutter 2.0 version, Flutter's control rendering was directly separated from the native platform, that is, regardless of the page stack and rendering tree, they run independently of the platform. This brings a better cross-platform experience to Flutter, but it also causes the Some problems in development when native platforms are mixed.

Children's shoes who have studied the Flutter architecture should know that the technical link of Flutter is based on the Engine written in C++ and the Framework layer written in Dart, as shown in the figure below.
在这里插入图片描述
Through the above architecture diagram, we can get the following information:

  • Flutter's Engine layer manages the four threads used by Flutter. For an introduction to the four threads, please refer to: Flutter's thread management model .
  • The role of Flutter's isolate is to manage the memory of the Dart layer and the running entity of single-threaded control, and the memory and logic between each isolate are isolated, and corresponding to the Engine, resources are not shared.
  • Engine relies on native view components to provide rendering capabilities, corresponding to Activity/ViewController, so opening a new page means creating a new Engine. If enough pages are opened, memory leakage may occur.

And, in terms of hybrid routing management, although the Dart layer itself provides routing management methods such as navigator, when we integrate Flutter in the native project, there will definitely be a hybrid routing jump such Native -> Flutter -> Native -> Flutter…

How to save the state of the Flutter page and restore the content of the Flutter page when the page is rolled back or jumped is a tricky problem for the multi-engine solution. The following figure demonstrates the memory loss caused by the repeated creation of Engine under the Flutter multi-engine solution.

在这里插入图片描述
In which scenarios does the multi-engine solution have advantages? Mainly reflected in the following two aspects:

  • Applications that integrate the Flutter interface are not located on the leaf nodes of the routing stack, and it may be a hybrid routing stack, that is, Native -> Flutter -> Native -> Flutter.
  • Multiple Flutter Views are integrated on the same page at the same time and displayed at the same time.

Three, FlutterEngineGroup solution

3.1 FlutterEngineGroup

FlutterEngineGroup is a brand new solution proposed by Flutter 2.0. The mixed mode of multiple Engines used in the FlutterEngineGroup solution greatly reduces the memory usage of additional Flutter engines. According to official data, FlutterEngineGroup dropped from about 19MB on Android and 13MB on iOS to about 180kB, reducing fixed memory overhead by about 99%.

Moreover, from the official examples provided by Flutter, the API of FlutterEngineGroup is very simple. Multiple Engine instances maintain their own internal navigation stack independently, so each Engine can correspond to an independent module.

Thanks to the features of FlutterEngine generated by FlutterEngineGroup that can share GPU context and shared rendering threads, the initialization of the engine can be realized faster and the memory usage can be lower. The following is the use of running the official example to open 10 pages, on the real Android machine, the memory and CPU usage.
在这里插入图片描述

3.2 Run the official example

The official provides an example of using FlutterEngineGroup, you can FlutterEngineGroup , and then follow the steps below to run the example:

cd ../multiple_flutters_module
flutter pub get
cd -
open -a "Android Studio" multiple_flutters_android/

Then, open and run the Android hybrid project in Android Studio.
在这里插入图片描述

3.3 Basic use of FlutterEngineGroup

Starting from Flutter 2.0, FlutterEngine will be generated by FlutterEngineGroup, and the generated FlutterEngine can be independently applied to FlutterActivity/FlutterViewController, or even FlutterFragment. Therefore, you need to create a FlutterEngineGroup object before using FlutterEngineGroup, preferably in the onCreate() method of Application.

private void initFlutterEngine() {
        engineGroup = new FlutterEngineGroup(this);
        DartExecutor.DartEntrypoint dartEntrypoint = new DartExecutor.DartEntrypoint(
                FlutterInjector.instance().flutterLoader().findAppBundlePath(), "topMain"
        );
        FlutterEngine topEngine = engineGroup.createAndRunEngine(this, dartEntrypoint);
        FlutterEngineCache.getInstance().put("topMain", topEngine);
    }

The topMain above is the method that needs to be executed to jump to Flutter. Then, use the Intent method to jump to the Flutter main page on the native page, and pay attention to using withCachedEngine when jumping, which can solve the white screen problem of the jump.

Intent intent =  FlutterActivity.withCachedEngine("topMain").build(getActivity());
           startActivity(intent);

Then, create a DartEntrypoint object in main.dart of the Flutter module. Note that the method name needs to correspond to the FlutterEngine created by FlutterEngineGroup, as shown below.

@pragma('vm:entry-point')
void topMain() => runApp(MyApp(color: Colors.green));

Through the dartEntrypoint and context created above, the corresponding FlutterEngine can be created using FlutterEngineGroup. In fact, it is internally through FlutterJNI.nativeSpawn to interact with the original engine to get the new address id. Finally, use the generated binaryMessenger of FlutterEngine to get a MethodChannel for communication between native and dart.

In addition, the Engine obtained through the above process can be directly used for rendering and running the new Flutter UI. Since the FlutterEngineGroup manages the Engine, we don't need to worry about memory issues.

At this point, you may have noticed that because each Flutter page is an independent Engine, due to the design concept of dart isolate, the Flutter page memory of each independent Engine cannot be shared. If you need to share data, you can only hold the data in the native layer, and then inject or pass it to each Flutter page. As the official said, is more like an independent Flutter module .

Next, we will introduce how Flutter 2.x performs basic operations such as data transfer in hybrid development.


xiangzhihong
5.9k 声望15.3k 粉丝

著有《React Native移动开发实战》1,2,3、《Kotlin入门与实战》《Weex跨平台开发实战》、《Flutter跨平台开发与实战》1,2和《Android应用开发实战》