introduction
Flutter, as a new generation of cross-platform, high-performance UI framework open sourced by Google, aims to help developers efficiently build cross-platform, exquisite applications with consistent UI and interactive experience. After its launch, it has been favored by developers.
When we need to develop a brand new application, we can easily start from scratch and develop it entirely with Flutter. But if it is for an existing application and needs to introduce Flutter technology, it is obviously unrealistic to use Flutter to rewrite it all. Fortunately, Flutter well supports the integration of independent pages or even UI fragments into existing applications, the so-called hybrid development model. This article mainly talks about the hybrid development and construction of Flutter under the Android platform from the perspective of Android development.
Hello Flutter
I believe that there should be very few mobile developers who don't know Flutter now, so I won't introduce too much here. For this technology, most of the used ones will say good; if you haven't used it before, it is recommended to try it and run a demo to experience the experience, it may be the last new technology you need to learn and master. Looking back, what is the unique charm of Flutter that makes it stand out from the crowd? To sum up, there are mainly the following points:
- Cross-platform: A set of codes can be perfectly adapted to the Android and iOS platforms, and more platforms will be covered in the future, which greatly saves development manpower and maintenance costs, and has excellent cross-end UI performance consistency.
- Efficient development: SDK provides a wealth of UI components, out of the box; declarative UI construction method, greatly reducing the error rate; Debug mode provides hot reload capability, can preview code changes in real time, no need to recompile and install.
- High performance: It adopts self-built rendering engine, which is independent of the system and can be optimized separately; different from RN and WEEX, there is no additional overhead of intermediate layer conversion; the code is compiled into AOT instructions in Release mode, and it runs efficiently.
Benefiting from the above core advantages, Flutter has attracted a lot of fans of mobile developers after its launch, and major Internet companies have also used it as a basic technology for research. In the early days of Flutter, its application scenario was mainly to build a brand new App from 0, which was very unfriendly to support hybrid development. However, as a cross-platform technical framework, it still needs to rely on the many system capabilities provided by the native platform. In addition, there are many existing native apps eager to try. Therefore, under the background of this demand, the support and improvement of hybrid development have been developed more and more. The better, let's use a simple example to start the journey of Flutter hybrid development and construction on the Android side.
Introduce the Flutter module
To use Flutter in an existing Android Project, you need to introduce a Flutter Module. Open an existing Android project in Android Studio (you need to ensure that the Flutter plugin has been successfully installed and enabled), and by using the File> New> New Module... menu, we can create a new Flutter module or import an external Flutter module.
Here takes the simplest Android App project as an example to import the Flutter module. After the Flutter module is successfully imported, the original project file and structure will undergo some changes, mainly:
- The following content has been added to the settings.gradle file. In fact, it is to execute the .android/include_flutter.groovy script file under the corresponding Flutter module. This step will introduce an Android Library Module called Flutter and all the plug-ins that the Flutter module depends on.
setBinding(new Binding([gradle: this]))
evaluate(new File(
settingsDir.parentFile,
'flutter_module/.android/include_flutter.groovy'
))
include ':flutter_module'
project(':flutter_module').projectDir = new File('../flutter_module')
- The project structure changes, as shown in the following figure:
Before the introduction of the Flutter module, there was only one app module in the project; after the introduction, you can see that in addition to the original app Module, the Flutter Gradle plugin automatically introduced several additional sub-modules:
- flutter_module: Refers to the target Flutter Module to be introduced, and does not apply any Android-related plugins, mainly including Flutter-related source code, resources, dependencies, etc.
- flutter: Android Library Module introduced for the Flutter Gradle plugin; mainly responsible for compiling flutter_module and its dependent third-party Package, Plugin Dart code, and packaging Flutter resources.
- device_info: Flutter Android Plugin Library Module automatically introduced for the Flutter Gradle plugin. This is because at the beginning I added a dependency on the device_info plugin in the pubspec.yaml file of flutter_module. The Flutter Gradle tool will introduce the code and resources on the Android platform side of all plug-ins that flutter_module depends on as a Library Module into the project and participate in the construction together. If you want to check which plugins are introduced by flutter_module, you can check the .flutter-plugins and .flutter-plugins-dependencies files in their corresponding directories. These two files are generated when you execute flutter pub get and record the local file directory, Rely on information, etc.
Note: A project cannot contain multiple Flutter Modules, only one can be imported at most, which is determined by the Flutter Gradle plugin.
Use Flutter
After completing the introduction of the Flutter module, let's take a look at how to use Flutter.
Add dependency
First, you need to add a dependency on the Flutter project in the build.gradle script file of the App module. Only then can the Flutter module participate in the construction of the entire application, and then we can call the Java layer API provided by Flutter in the App module. . As follows:
dependencies {
implementation project(':flutter')
}
Run Flutter page
We can choose to use Activity, Fragment or View to host Flutter's UI. Here we mainly introduce the first two methods, and assume that a widget has been rendered in flutter_module through the runApp method.
- Run Flutter Activity. Using the io.flutter.embedding.android.FlutterActivity class can easily start a Flutter Activity, of course, we can also inherit it and extend our own logic. The sample code is as follows:
FlutterActivity
.withNewEngine()
.build(context)
.also { startActivity(it) }
- Run Flutter Fragment. You can use FlutterFragmentActivity or FlutterFragment to add Flutter UI fragments: a. Use FlutterFragmentActivity to automatically create and add a FlutterFragment; b. Manually create a FlutterFragment and add it to the target Activity. The sample code is as follows:
val flutterFragment = FlutterFragment.withNewEngine()
.dartEntrypoint(getDartEntrypointFunctionName())
.initialRoute(getInitialRoute())
.appBundlePath(getAppBundlePath())
.flutterShellArgs(FlutterShellArgs.fromIntent(intent))
.handleDeeplinking(shouldHandleDeeplinking())
.renderMode(renderMode)
.transparencyMode(transparencyMode)
.shouldAttachEngineToActivity(shouldAttachEngineToActivity())
.build<FlutterFragment>()
fragmentManager
.beginTransaction()
.add(
FRAGMENT_CONTAINER_ID,
flutterFragment,
TAG_FLUTTER_FRAGMENT
)
.commit()
- The platform layer communicates with the Flutter layer. Whether it is developing Plugin or business logic, communication between the platform layer and the Flutter layer is indispensable. For this, MethodChannel is needed. When the platform layer requests to call the Flutter layer API through MethodChannel, the data is packaged and encoded, and then transmitted to the Flutter layer through JNI and DartVM for decoding; after the result calculation is completed, it will be repackaged and encoded, and then transmitted back to Native through DartVM and JNI In the same way, when the Flutter layer requests to call the platform layer API, the data processing is the same, but the flow direction is opposite. In this way, a two-way, asynchronous communication channel is established between the platform layer and the Flutter layer. In the sample code below, the Native layer uses dev.flutter.example/counter to create a MethodChannel, and sets up the Handler to receive Dart's remote method call incrementCounter, and calls reportCounter to return the result.
channel = MethodChannel(flutterEngine.dartExecutor, "dev.flutter.example/counter")
channel.setMethodCallHandler { call, _ ->
when (call.method) {
"incrementCounter" -> {
count++
channel.invokeMethod("reportCounter", count)
}
}
}
The Dart layer uses the same name to create a MethodChannel, and sets the Handler to process the callback result, and then calls the incrementCounter method to request the counter. The sample code is as follows:
final _channel = MethodChannel('dev.flutter.example/counter');
_channel.setMethodCallHandler(_handleMessage);
_channel.invokeMethod('incrementCounter');
Future<dynamic> _handleMessage(MethodCall call) async {
if (call.method == 'reportCounter') {
_count = call.arguments as int;
notifyListeners();
}
}
Here we communicate by manually creating MethodChannel. This is fine for simple communication scenarios, but it is not very applicable when the communication interface API is more complicated.
One is cumbersome, because we need to write a lot of packaging and unpacking codes by hand; the other is error-prone. At this time, it was Pigeon's turn to show off. Pigeon is an official code generation tool that can generate type-safe two-way communication API interfaces. For details, please refer to the official Example, which will not be repeated here.
Pigeon :https://flutter.dev/docs/development/platform-integration/platform-channels#pigeon
Flutter APK analysis
At this point, we have learned how to introduce and use Flutter in an existing Android project. Next, let's explore the structure of the Flutter APK and see what Flutter Tools packs in this APK package. The following two figures show the Flutter APK package structure constructed in Debub mode and Release mode, respectively, ignoring non-Flutter related items.
It can be seen that the APK structure in the two modes is roughly the same, the description is as follows:
- lib/{arch}/libflutter.so: Flutter Engine shared library of the corresponding architecture, responsible for Flutter rendering, JNI communication, and DartVM. If you don't need the corresponding architecture version, you can Exclude it through abiFilters.
- lib/{arch}/libapp.so: Only exists in Release mode. The shared library contains binary instructions and data generated by Dart AOT. At runtime, Flutter Engine reads the corresponding executable machine instructions and data from the shared library through Dynamic Load.
assets/flutter_assets: related assets referenced by Flutter
- fonts: Contains font libraries.
- FontManifest.json: The referenced font library manifest file, json format, all fonts used, and the path of the font file under flutter_assets.
- AssetManifest.json: Other resource manifest files, in json format, are the mapping of all resource names to resource paths. When Flutter loads a certain resource, it will find the resource of the corresponding path through this configuration list and load it after reading it.
- kernel_blob.bin, isolate_snapshot_data, vm_snapshot_data: only exist in Debug mode. They are DartVM bytecode and data respectively. Their function is similar to libapp.so, except that they exist in different forms and packaging methods. In Debug mode, Flutter Tools packages instructions and data separately, mainly for hot reload (HotReload) services, while in Release mode, they are packaged into a shared library.
Stepped pit
Here, I also summarized a few problems we encountered in the application, for your reference to avoid pits.
- Complex routing management: This includes page routing management within the Flutter layer and Flutter and native hybrid stack management. The former has been well improved and supported in the Navigator 2.0 API, but the latter still faces many limitations and deficiencies and needs to be improved. At present, the latter is not involved in the very complex business scenario in the project, so there is relatively little research on this area, and interested students can learn about open source solutions such as flutter_boost.
- Life cycle does not correspond: Android components generally have their own life cycle, and Flutter's Widget State also has its own life cycle, but the two are actually not one-to-one correspondence. For example, although the native Activity page has been dropped by Finish and Destroy, the Flutter layer page may not necessarily be Disposeed, especially when using the Cache Flutter Engine. Flutter pages can exist without the native page. They can be dynamically attached and Detach. Re-rendering will be triggered when Attach, and all UI-related operations during Detach will be Pending until they are attached again. Therefore, in hybrid development, business logic should not overly rely on some life cycle methods of Widget State, because they may be delayed in execution and cause some strange bugs.
to sum up
Flutter hybrid development allows developers to gradually develop and migrate Flutter, which is a vital part of Flutter parasitic on the native platform.
This article mainly introduces the introductory knowledge of Flutter hybrid development from the perspective of Android development. With the continuous iteration and evolution of the Flutter open source project, the experience of hybrid development is getting better and better, and the performance is getting higher and higher. But the fly in the ointment is that there are still some application scenarios and problems that have not been well improved and solved, such as the Flutter multi-instance problem (we will also share with you in another article this month and introduce our experience in practicing Flutter multi-instance Please pay attention to the problems and solutions.)
The flaws are not concealed, Flutter is a very good technology on the whole. It is still in a stage of rapid development. I believe that with the joint efforts of the Flutter team and the open source community, the future ecology is worth looking forward to.
About the Author
Li Chengda, a senior mobile terminal development engineer at NetEase Yunxin, is keen on researching cross-platform development technology and engineering efficiency improvement. Currently, he is mainly responsible for the related research and development of video conferencing component SDK.
Regarding the technical practice of , please pay attention to 160af5891613a3 Netease Yunxin official website .
For more technical dry goods, please pay attention to [NetEase Smart Enterprise Technology+] WeChat public account.
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。