Superfluous preface
When Flutter 2.0 was released, one of the things that drew the most attention was the add-to-app updates, because aside from the hot updates, one of the biggest criticisms of Flutter was that it wasn’t a good hybrid development experience.
Why not? Because Flutter controls render directly off the native platform, that is, both the page stack and the render tree run independently of the platform. This gives Flutter a better cross-platform experience, but it also causes the problem of high cost when mixing with native platforms.
Not to mention integrating Flutter into existing native projects, the PlatformView and Hybrid Composition experience of integrating native controls into Flutter at this stage still needs to be improved. Of course, “supported” and “usable” is a good step forward.
So Flutter 2.0 was released to the worldFlutterEngineGroup
Used to support officialAdd Flutter to existing app
Solution.
Prior to this scheme, there were similar third-party supports such as Flutter_boost, mix_stack, Flutter_thrio, etc. Whether they were useful or not is not discussed here, but the problem with all of them is:
Unofficial support is bound to have the problem of adaptation for each version. According to the current speed of Issue Closed and PR merge of Flutter, it is likely that there will be major changes in the quarterly version. Therefore, if the developer does not maintain Flutter or does not maintain Flutter in a timely manner, Intrusive frameworks can easily become bottlenecks.
Is there any defect in the officially provided FlutterEngineGroup scheme? Of course there is, it looks more like a state of being spawned at the moment, and there are still issues with various aspects, such as not being able to destroy in some places. (Of course this problem is also merged in the Master branch)
However, the official solution provided by Flutter means that this design is guaranteed by Flutter officials to have the advantage of compatibility in future versions.
The FlutterEngineGroup scheme uses a mixed mode of multiple engines, officially claiming that each subsequent Engine object, except for one Engine object, will consume only 180kB on Android and iOS.
In previous scenarios, each additional Engine might add up to 19MB for Android and 13MB for iOS.
According to the official Flutter example, the API of the FlutterEngineGroup is very simple. Multiple Engine instances maintain their own internal navigation stack independently, so that each Engine corresponds to an independent module.
Therefore, after using FlutterEngineGroup, FlutterEngine will be generated by FlutterEngineGroup. The generated FlutterEngine can be applied to FlutterActivity/FlutterViewController independently, even FlutterFragment:
So, as shown in the example above, you can display two separate FlutterViews on one Activity.
This actually benefits from the fact that FlutterEngine generated by FlutterEngineGroup can share GPU context, Font Metrics and ISOLATE Group Snapshot, thus achieving faster initial speed and lower memory footprint.
Here is the memory usage after opening 16 pages using the official example, and each page returned successfully without a black screen.
Simple usage introduction
To use FlutterEngineGroup, you first need to create a FlutterEngineGroup singleton, and then whenever you need to create an Engine, The corresponding FlutterEngine is created with its createAndRunEngine(Activity, dartEntrypoint).
val app = activity.applicationContext as App
// This has to be lazy to avoid creation before the FlutterEngineGroup.
val dartEntrypoint =
DartExecutor.DartEntrypoint(
FlutterInjector.instance().flutterLoader().findAppBundlePath(), entrypoint
)
engine = app.engines.createAndRunEngine(activity, dartEntrypoint)
this.delegate = delegate
channel = MethodChannel(engine.dartExecutor.binaryMessenger, "multiple-flutters")
Copy the code
Take this code from the official Demo as an example:
1. Create DartEntrypoint objects using findAppBundlePath and EntryPoint, where findAppBundlePath is the default Flutter_assets directory. Entrypoint is the name of the startup method in the Dart code; That is, the method that binds runApp in DART.
///kotlin
app.engines.createAndRunEngine(pathToBundle, "topMain")
///dart
@pragma('vm:entry-point')
void topMain() => runApp(MyApp());
Copy the code
2. FlutterEngine can be created using the dartEntrypoint and Context created above. NativeSpawn internally interacts with the original engine to get the new Long address ID.
In c + + layer is similar to the original RunBundleAndSnapshotFromLibrary method, but it can’t change the package path or asset, so can only load the same AOT documents, The pointer here is a new AndroidShellHolder.
Finally, use the generated binaryMessenger of FlutterEngine to get a MethodChannel for communication between native and DART.
The Engine derived from the above process can naturally be used to render and run the new Flutter UI, for example by inheriting FlutterActivity directly and then returning the resulting Engine by overriding the provideFlutterEngine method.
class SingleFlutterActivity : FlutterActivity()......override fun provideFlutterEngine(context: Context): FlutterEngine? {
return engine
}
}
Copy the code
Isn’t that easy? After such a simple access:
- It can pass at the DART level
MethodChannel
Open the original page; - The native layer can be created by creating
FlutterEngine
Open a new Flutter page - You can even open one in the native layer
FlutterView
The Dialog;
Of course, you may have noticed by now that since each Flutter page is an independent Engine, due to the design concept of dart Isolate, the memory of the Flutter pages of each independent Engine cannot be shared.
That is, when you need to share data, you can only hold it in the native layer and then inject or transfer it to each Flutter page. As officially stated, each Flutter page is more like an independent Flutter module.
Of course, this can cause some unnecessary problems. For example, the same image may be loaded multiple times in different Flutter engines on the native layer. This problem may require you to use external textures for the load of the Flutter image to achieve uniform memory management on the native layer.
In addition, I have found another problem: ARM TBI on Android 11. However, through this attempt, I believe that the progress of FlutterEngineGroup will become clearer and clearer, and it will be applied to the production environment earlier.