The introduction

Flutter, Google’s new generation of open source cross-platform, high-performance UI framework, aims to help developers effectively build cross-platform, UI and interactive experience of beautiful applications, has been popular with developers since its launch.

When it comes time to develop an entirely new app, it’s easy to start from scratch and develop entirely with Flutter. But if it’s for an existing application that needs to introduce Flutter technology, it’s obviously not practical to use Flutter to rewrite the whole thing. Fortunately, Flutter does a good job of supporting integration into existing applications as separate pages, or even UI fragments, in what is known as the hybrid development model. This paper mainly discusses the hybrid development and construction of Flutter on Android platform from an Android development perspective.

Hello Flutter

There are very few mobile developers who don’t know about Flutter right now, but I won’t go into that here. The vast majority of people who have used this technology will say yes; Give it a try if you haven’t used it, run a Demo and experience it. It may be the last new skill you’ll ever need to learn and master. Then again, what is it about Flutter that makes it stand out from the crowd? To sum up, there are mainly the following points:

  • Cross-platform: It can achieve a set of code perfectly suitable for Android and iOS platforms, and will cover more platforms in the future, which greatly saves development manpower and maintenance costs, and has excellent cross-end UI performance consistency.
  • Efficient development: The SDK provides rich UI components out of the box; Declarative UI construction method, greatly reduce the error rate; Debug mode provides a hot-reload capability that allows you to preview code changes in real time without recompiling and installing.
  • High performance: Self-built rendering engine, independent of the system and can be optimized separately; Different from RN and WEEX, there is no extra overhead of mid-layer conversion. In Release mode, the code is compiled into AOT instructions and runs efficiently.

With these core advantages, Flutter has won a lot of fans among mobile developers since its launch, and has been studied as a foundation technology by major Internet companies. In the early days of Flutter, the application scenario was to build a brand new App from 0, which was not friendly to mixed development support. However, as a cross-platform technical framework, it still needs to rely on a lot of system capabilities provided by the native platform. In addition, there are many existing native APPs ready to try. Therefore, under this demand background, the support and improvement of mixed development has been better and better so far. Let’s start our hybrid development and build tour of Flutter on Android with a simple example.

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 (need to make sure the Flutter plugin has been successfully installed and enabled) by using File > New > New Module… Menu, we can create a new Flutter module or import an external Flutter module.

Here, taking the simplest Android App project as an example, import the Flutter module. After the successful import of the Flutter module, there will be some changes in the original project file and structure, mainly as follows:

  • The settings.gradle file adds the following. Flutter /include_flutter. Groovy. This step introduces an android Library Module named Flutter. It also introduces 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')
  • Project structure changes, as shown in the figure below:

Before the introduction of the Flutter Module, there was only one app Module in the project; After the plugin is introduced, you can see that the Flutter Gradle plugin automatically introduces several submodules in addition to the original app Module:

  • Flutter_module: This refers to the target Flutter Module that does not apply any Android-related plug-ins. It mainly contains Flutter related source code, resources, dependencies, etc.
  • Flutter: Android Library Module introduced for Flutter Gradle plugin; Mainly responsible for compiling flutter_module and its dependent third-party packages, Dart code of Plugin, and packaging Flutter resources, etc.
  • Device_info: The Flutter Android Plugin Library Module is automatically introduced for Flutter Gradle. This is because initially I added a dependency to the device_info plugin in the flutter_module pubspec.yaml file. The Flutter Gradle tool will introduce the Android platform side code and resources of all the plug-ins that the flutter_module depends on into the project as a Library Module to participate in the construction. To see which plugins flutter_module introduced, look at the.flutter-plugins and.flutter-plugins-dependencies files in their respective directories. These two files are generated when the Flutter Pub Get is performed and record the local file directory of the plug-in, dependency information, and so on.

Note: A project cannot contain more than one Flutter Module. You can import at most one. This is determined by Flutter’s Gradle plugin.

The use of Flutter

With the introduction of the Flutter module complete, let’s look at how to use Flutter.

Add the dependent

First of all, we need to add a dependency to the Flutter project in the build.gradle script file of the App module. Only in this way can the Flutter module participate in the construction of the entire application, and we can also call the Java layer API provided by Flutter from the App module. This is as follows:

dependencies { 
  implementation project(':flutter') 
}

Run the Flutter page

We can choose to use an Activity, Fragment, or View to host Flutter’s UI. The first two methods are covered here, and we assume that a widget has already been rendered via the runApp method in the flutter_module.

  • Run the Flutter Activity. Use IO. Flutter. Embedding. Android. FlutterActivity class can easily start a flutter Activity, of course, we can inherit and expand its logic. The sample code is as follows:
FlutterActivity 
  .withNewEngine() 
  .build(context) 
  .also { startActivity(it) }
  • Run Flutter Fragment. To add a fragment of the Flutter UI, use FlutterFragmentActivity or FlutterFragment: a. Use the 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. For both Plugin development and business logic, it is essential that the platform layer communicate with the Flutter layer, for which MethodChannel is required. When the platform layer calls the Flutter layer API through MethodChannel request, the data is packaged and encoded and transmitted to the Flutter layer through JNI and DartVM for decoding and use. After the calculation of the result is completed, the coding will be repackaged and transmitted to the Native layer through DartVM and JNI. Similarly, when the Flutter layer requests to call the API of the platform layer, the data processing is consistent, but the flow direction is opposite. In this way, the platform layer establishes a two-way, asynchronous communication channel with the Flutter layer. In the sample code below, Native layer using dev. Flutter. Create a MethodChannel example/counter, and set the Handler receives the Dart’s remote method invocation 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 creates MethodChannel with the same name and sets Handler to handle the result of the callback, then calls the incrementCounter method to request 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, which is fine for simple communication scenarios, but not so good for more complex communication interface APIs.

One is tedious, because we need to handwrite a lot of packaging, unpacking code; Second, it is easy to make mistakes. This was when Pigeon came into play. Pigeon is an official code generation tool that produces a type-safe two-way communication API. See the official Example for details. I won’t go into details here.

Pigeon :https://flutter.dev/docs/development/platform-integration/platform-channels#pigeon

Flutter APK parsing

Now that we’ve seen how to introduce and use Flutter in an existing Android project, let’s take a look at the structure of Flutter APK to see what Flutter Tools packages into the APK package. The following two figures show the Flutter APK package structure built in Debub mode and Release mode, respectively, ignoring non-Flutter related items.

It can be seen that the structure of APK in both modes is roughly the same, as follows:

  • Lib /{arch}/ libflutt.so: The Flutter Engine shared library for the corresponding architecture, responsible for Flutter rendering, JNI communication, DARTVM. If no version of the corresponding schema is required, abiFilters can Exclude it.
  • Lib /{arch}/libapp.so: Only exists in Release mode. The shared library contains binary instructions and data generated by Dart AOT. At run time, the 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 the font library.
  • FontManifest.json: The font library manifest file referenced, the JSON format, all the fonts used, and the path to the font file under flutter_assets.
  • AssetManifest.json: A list file of other resources, in JSON format, that maps all resource names to resource paths. When Flutter loads a resource, it will use this configuration list to find the resources in the corresponding path for reading and loading.
  • Kernel_blob. bin, isolate_snapshot_data, vm_snapshot_data: only exist in Debug mode, which are DartVM bytecode and data, respectively. Their function is similar to libapp.so, but the existence form and packaging method are different. In Debug mode, Flutter Tools packages instructions and data separately, primarily for hotload services, and in Release mode as shared libraries.

The pit of tread

Here, also summed up a few of the problems we encountered in the application, for your reference to avoid pit.

  • Routing management is complex: This includes page routing management within the Flutter layer as well as Flutter and native hybrid stack management. The former is well developed 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 project has not covered the complex business scenario of the latter, so there is little research on this area. Those who are interested in this area can learn about open source solutions such as Flutter_Boost.
  • Lifecycle Mismatches: Android components typically have their own lifecycles, and Flutter’s Widget State has its own lifecycles, but the two are not necessarily one-to-one. For example, while the native Activity page is finished and destroyed, the Flutter layer page is not necessarily disposed with it, especially when using the Cache Flutter Engine. Flutter pages can exist without the original page. They can be dynamically attached and Detach. When Attach triggers a rerender, all UI-related operations Pending during Detach will remain Pending until they are attached again. So in mixed development, the business logic should not rely too heavily on some of the lifecycle methods of Widget State, because they can be delayed and lead to strange bugs.

conclusion

Flutter Mixed Development, which allows developers to develop and migrate Flutter increments, is a crucial part of Flutter’s parasitism on the native platform.

This article provides an introduction to Flutter’s hybrid development from an Android development perspective. As Flutter’s open source project continues to iterate and evolve, the experience of hybrid development is getting better and better in terms of performance. However, there are still a few scenarios and problems that have not been solved well, such as the Flutter multi-instance problem (we will also share our problems and solutions with you in another article this month, so stay tuned).

In spite of the flaws, Flutter’s technology as a whole is very good. It is still in the stage of rapid development, and with the joint efforts of the Flutter team and the open source community, it is worth looking forward to the future ecology.

Author’s brief introduction

Li Chengda, senior mobile terminal development engineer of netease Yunxin, is keen on studying cross-platform development technology and engineering efficiency. Currently, he is mainly responsible for the related research and development work of video conferencing componentization SDK.

About the technical practice of netease Yunxin, please also pay attention to the official website of netease Yunxin.

More technical dry goods, welcome to pay attention to “netease intelligent enterprise technology +” WeChat public number.