Thanks to Flutter's excellent cross-platform performance, hybrid development can be seen everywhere in today's apps. For example, the official version of Skyline, a new rendering engine for small programs announced by WeChat recently , also uses Flutter for underlying rendering, claiming that the rendering speed has increased by 50%.
It is not a simple matter to introduce Flutter into the existing native App for development. It is necessary to solve various problems caused by the mixed mode, such as routing stack management, package volume and memory burst, etc.; there is also a special case, An app originally developed by Flutter may also be mixed in later and developed with native View.
My team is currently in this situation. Flutter is not yet perfect in terms of performance, the overall page is not smooth enough, and there will be serious heating behavior in some complex page scenarios. Although the Flutter team has released a new The rendering engine impeller , which performs well on iOS, has a qualitative improvement in fluency, but still cannot completely solve some performance problems and the impeller under Android has not yet been developed.
In order to deal with the current dilemma and the unknown problems that may arise in the future, we expect to expand more possibilities through the mixed mode.
routing management
The most difficult thing to deal with under mixed development is the routing problem. We know that native and Flutter have their own routing management systems. How to manage and interact with each other in the case of interspersed native pages and Flutter pages is a major difficulty. Currently popular , the representative framework is flutter_boost单引擎方案
produced by Xianyu team ; the multi-engine solution FlutterEngineGroup officially represented by flutter .
Single engine solution flutter_boost
flutter_boost achieves the purpose of minimum memory by multiplexing the Engine, the following picture is its design architecture diagram ( picture from the official )
In terms of engine processing, flutter_boost defines a general CacheId: "flutter_boost_default_engine". When the native needs to jump to the Flutter page, by FlutterEngineCache.getInstance().get(ENGINE_ID);
obtaining the same Engine, no matter how many Flutter pages A, B, and C in the figure are opened , there will be no additional Engine
memory consumption.
public class FlutterBoost {
public static final String ENGINE_ID = "flutter_boost_default_engine";
...
}
In addition, both ends are registered with the navigation interface, which is Channel
notified through to request routing changes, page return, and page life cycle processing. In this mode, Channel
the interface processing at this layer is the focus.
Multi-engine solution FlutterEngineGroup
In order to deal with the problem of memory explosion, the official optimized the multi-engine scenario, which came into FlutterEngineGroup
being. FlutterEngineGroup
The following Engines share some common resources, such as GPU context, thread snapshot, etc. When generating additional Engines, it is claimed that the memory usage is reduced to 180k. This level can basically be regarded as normal loss.
Take the B and C pages in the above figure as an example. Both are Flutter pages. Under the processing of FlutterEngineGroup, because the Engines they are in are not the same, this will produce complete isolation behavior, that is, the B and C pages use different The stacks are in different Isolates, and the two cannot interact directly.
The advantage of multi-engine is that it can erase the internal routes such as F, E, C, D, and A shown in the above figure. Every time a new Flutter page is added, all of them are called back to the native, so that the native generates a new Engine to host the page , so that the routing management is all handled by the native, and one Engine only corresponds to one Flutter page.
But it will also bring some additional processing. As mentioned above, there is no direct interaction between Flutter pages under different Engines. If it involves a scene that requires notification and interaction, it has to be forwarded through native.
For FlutterEngineGroup
more information, you can refer to the official instructions .
performance comparison
It is officially claimed that the new Engine created by FlutterEngineGroup will only occupy 180k of memory, so is it really as it says? Let's do a memory usage test for the above two solutions
flutter_boost
Test model: OPPO CPH2269
Test code: github.com/alibaba/flu…
Memory dump command: adb shell dumpsys meminfo com.idlefish.flutterboost.example
condition | PSS | RSS | biggest change |
---|---|---|---|
1 Native | 88667 | 165971 | |
+26105 | +28313 | +27M | |
1 Native + 1 Flutter | 114772 | 194284 | |
-282 | +1721 | +1M | |
2 Native + 2 Flutter | 114490 | 196005 | |
+5774 | +5992 | +6M | |
5 Native + 5 Flutter | 120264 | 201997 | |
+13414 | +14119 | +13m | |
10 Native + 10 Flutter | 133678 | 216116 |
When loading the Flutter page for the first time, increase the memory by about 27M, and then open an additional page, and the memory increase will show a steeper and steeper trend from 1M -> 2M -> 2.6 M (the value is only for reference, because there are Native pages, only Look at the trend change)
FlutterEngineGroup
Test model: OPPO CPH2269
Test code: github.com/flutter/sam…
Memory dump command: adb shell dumpsys meminfo dev.flutter.multipleflutters
condition | PSS | RSS | biggest change |
---|---|---|---|
1 Native | 45962 | 140817 | |
+29822 | +31675 | +31M | |
1 Native + 1 Flutter | 75784 | 172492 | |
-610 | +2063 | +2M | |
2 Native + 2 Flutter | 75174 | 174555 | |
+7451 | +7027 | +3.7M | |
5 Native + 5 Flutter | 82625 | 181582 | |
+8558 | +7442 | +8M | |
10 Native + 10 Flutter | 91183 | 189024 |
When loading the Flutter page for the first time, increase the memory by about 31M, and then open an additional page to increase the memory, showing a steeper trend from 1M -> 1.2M -> 1.6 M (the value is only for reference, because there are Native pages, Just look at the trend change)
in conclusion
The two tests use different demo codes, which cannot be judged by numerical values. However, through the numerical performance, we can basically confirm that neither of the two solutions will cause abnormal memory surges, which are completely within the acceptable range.
PlatformView
PlatformView can also implement hybrid UI, and WebView in Flutter is introduced through PlatformView.
PlatformView allows us to insert native View into the Flutter interface, wrap a layer of PlatformView on the outermost layer of a page, and manage routing by Flutter. In this way, no additional Engine is generated, and it is the simplest mixing method.
But it also has disadvantages. It is not suitable for the scene where the main Native is mixed with Flutter, and now most of the scenes are mainly for the main Native mixed with Flutter. In addition, due to its underlying implementation, PlatformView will have compatibility issues. In some models, keyboard problems, flickering or other performance overhead may occur. For details, see this introduction
data sharing
Native and Flutter use different development languages for development, so the data structure objects and memory objects defined on one side cannot be perceived by the other party, and other means must be used for data synchronization and processing.
MethodChannel
Flutter developers must be familiar with MethodChannel. It is inevitable to interact with the native during development. MethodChannel is a two-way design, which allows us to call native methods in Flutter and also allows us to call Flutter methods in native. If you don’t know much about Channel, you can read the official document . As mentioned in the document, the data needs to be encoded and decoded during the transmission of this channel. The corresponding relationship kotlin
is taken as an example (see the document for the complete mapping):
Dart | Kotlin |
| -------------------------- | ----------- |
| null | null |
| bool | Boolean |
| int | Int |
| int, if 32 bits not enough | Long |
| double | Double |
| String | String |
| Uint8List | ByteArray |
| Int32List | IntArray |
| Int64List | LongArray |
| Float32List | FloatArray |
| Float64List | DoubleArray |
| List | List |
| Map | HashMap |
local storage
This method is easier to understand. Local storage is regarded as a transfer station. Data operations are stored locally in Flutter, and the local database can be queried at a certain time (such as onResume) when returning to the original page, and vice versa.
question
Whether it is MethodChannel
or is 本地存储
, there will be a problem: the data structure of the object is independent, and both sides need to be defined repeatedly. For example, I have a Student object in Flutter, and I also need to define a Student with the same structure on the Android side, so as to facilitate operation. Now I will transfer it to, Student student
decode Unit8List
it into an operable Android
Channel , and then translate it into an object.Kotlin
ByteArray
ByteArray
Android
Student
class Student {
String name;
int age;
Student(this.name, this.age);
}
The best solution to this problem is to use DSL
a class of frameworks, such as Google's ProtoBuf , to compile the same object configuration file into different language environments, which can save this part of double-ended repeated definition behavior.
image cache
In terms of memory, if the same image is loaded on both sides, both native and Flutter will generate a cache. Under Flutter, it will be cached by default ImageCache
. Different frameworks under the native environment are responsible for different objects. In order to remove duplicate image caches, it is necessary to unify the image loading management.
The same is true for Ali's solution , which shares the local text cache and memory cache of pictures by connecting to the native picture library. Its implementation idea is to connect to an external library through customization ImageProvider
and to obtain image data for analysis, and the processing of the connection is to extend the Flutter Engine.Codec
If you do not want to modify the Flutter Engine, you can also use external textures to handle it. By PlatformChannel
requesting the original, the external texture data of the picture is displayed, and TextTure
the picture is displayed through the component.
// 自定义 ImageProvider 中,通过 Channel 去请求 textureId
var id = await _channel.invokeMethod('newTexture', {
"imageUrl": imageUrl,
"width": width ?? 0,
"height": height ?? 0,
"minWidth": constraints.minWidth,
"minHeight": constraints.minHeight,
"maxWidth": constraints.maxWidth,
"maxHeight": constraints.maxHeight,
"cacheKey": cacheKey,
"fit": fit.index,
"cacheOriginFile": cacheOriginFile,
});
// ImageWidget 中展示时通过 textureId 去显示图片
SizedBox(
width: width,
heigt: height,
child: Texture(
filterQuality: FilterQuality.high,
textureId: _imageProvider.textureId.value,
),
)
Summarize
Different businesses have different requirements for the degree and requirements of mixing, and there is no universal solution. For example, in the case of my team 主Flutter混原生
, 路由管理
I chose PlatformView
this processing mode on the website, which is easier to develop and maintain. If there are compatibility problems later, I can also transition to flutter_boost
the FlutterEngineGroup
website.
Link: https://juejin.cn/post/7262616799219482681