1

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.

1, Hello Flutter

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 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 of : 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 : adopts self-built rendering engine, 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.

2. 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')

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.

在这里插入图片描述
described as follows:

  • 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.

3. Use Flutter

3.1 Add dependency

First of all, 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 we can also call the Java layer provided by Flutter in the App module. API.

dependencies { 
  implementation project(':flutter') 
}

3.2 Run Flutter page

3.2.1 Add Flutter page

We can choose to use Activity, Fragment or View to host the Flutter UI. Here we mainly introduce the first two methods, and assume that a widget has been rendered in flutter_module through the runApp method.

Flutter Activity

First, we introduce the way to use 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.

FlutterActivity 
  .withNewEngine() 
  .build(context) 
  .also { 
    startActivity(it) 
  }
Flutter Fragment

The other is the Flutter Fragment method. 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.

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()

3.2.2 Platform layer and Flutter layer communication

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 the Handler to receive Dart's remote method call incrementCounter, and calls reportCounter to return the result, as shown below.

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.

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(); 
    } 
  }

In the above example, we communicated by manually creating MethodChannel. This is no problem in 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 is the turn of the Pigeon 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, Pigeon official link .

4. Flutter APK analysis

We have already understood how to introduce and use Flutter in an existing Android project. Next, let's explore the structure of the Flutter APK and see what exactly is packaged in this APK package by Flutter Tools. The following two figures show the Flutter APK package structure constructed in Debub mode and Release mode, respectively, ignoring non-Flutter related items.

在这里插入图片描述
You can see that the APK structure in the two modes is roughly the same, the differences are as follows:

  • lib/{arch}/libflutter.so : Flutter Engine shared library of the corresponding architecture, responsible for Flutter rendering, JNI communication, 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 resources 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.

5. Stepped pits

Flutter hybrid development allows developers to gradually carry out Flutter development and migration. It is a vital part of Flutter's parasitic on the native platform. However, there are some problems in the process of connecting to Flutter:

  • 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.

xiangzhihong
5.9k 声望15.3k 粉丝

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