The background,

This article is based on Flutter 1.22.4 • Channel stable • github.com/flutter/flu… • Tools • Dart 2.10.4

Because the company internally needs to transform the original APP with FLUTTER and intends to carry out modular reconstruction by business, it practices how to embed flutter as a module into the original Android project. Official documents as the first-hand information for learning, timeliness and accuracy can be guaranteed, so first refer to flutter.cn/….. On the module integration tutorial for some sample demo preparation.

Second, module integration

1. Automatic integration

  1. Create an empty application project using Android Studio.
  2. By * * File – > New – > New Module.. Create a flutter Module. Import a flutter Module if you already have one.

The FLUTTER module is then automatically integrated.

2. Manual integration

Usually we need to separate the business modules, and each team member is responsible for the development of one module without interfering with each other. When the need for integrated debugging, in the original shell project by relying on AAR or source code for debugging. Source code dependence is suitable for scenarios where flutter code interacts with native code. If business A does not need to modify the code of BUSINESS B, it can directly rely on the AAR package of business B. The Git submodule provides such collaboration. The Git subModule allows you to treat one Git repository as a subdirectory of another. Git submodule can be used to manage service modules and basic modules. The specific steps are as follows:

  • Create a standalone module using the following command line:
flutter create -t module login_module
Copy the code
  • Import Android Studio and run it to generate. Android directory, structure as follows

  • By managing the project with Git, modules can be run independently and business logic can be developed on them.
  • In the native Android project, execute git command line to add sub-module, update the module when there is an update
Git subModule update git submodule update git submodule updateCopy the code
  • Setting. gradle for the native project adds the following configuration:
setBinding(new Binding([gradle: this]))                                // new
evaluate(new File(                                                     // new// new
        'login_module/.android/include_flutter.groovy'         // new
))
Copy the code
  • Add a dependency on the Flutter module to the build.gradle file of the project app module:
dependencies {
  implementation project(':flutter')}Copy the code

3. Route redirection

We use the automatically integrated Flutter Module to describe how to jump to a single flutter page by adding the following code to the Activity:

   findViewById<View>(R.id.fab).setOnClickListener {
            startActivity(
                FlutterActivity.createDefaultIntent(this))}Copy the code

Running the APP, the actual effect is as follows: there is an obvious black screen, mainly because flutterEngine initialization causes lag. To minimize the delay time, you can initialize a flutterEngine in advance in Application. Then load the FlutterActivity using the preheated FlutterEngine:

Third, source code analysis

FlutterActivity

View the structure of FlutterActivity in the toolbar on the right side of Android Studio, and click the method name to quickly locate the method we need to analyze

The inheritance structure of FlutterActivity is as follows:

The Host interface abstracts the common methods, FlutterActivity and FlutterFragment are responsible for implementation, including Lifecycle, context, getInitialRoute, getAppBundlePath and other methods. We can inherit FlutterActivity to rewrite part of the methods and achieve Customize flutterEngine, AppBundlePath, etc. We’ll start with OnCreate:

@Override
  protected void onCreate(@Nullable Bundle savedInstanceState) {
    switchLaunchThemeForNormalTheme();
    super.onCreate(savedInstanceState);
    lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_CREATE);
    // Create delegate class
    delegate = new FlutterActivityAndFragmentDelegate(this);
    delegate.onAttach(this);
    delegate.onActivityCreated(savedInstanceState);
    configureWindowForTransparency();
     CreateFlutterView will be delegated to the delegate onCreateView method
    setContentView(createFlutterView());
    configureStatusBarForFullscreenFlutterExperience();
  }
Copy the code

FlutterActivityAndFragmentDelegate

The delegate class implements all the same Flutter logic in FlutterActivity and FlutterFragment. This includes setting up the engine, creating views, distributing life cycle events to flutter, and executing dart code.

  1. onCreateViewMethod is responsible forContentViewThe interior is created depending on the render modeFlutterTextureVieworFlutterSurfaceViewAnd thenflutterEngineBind to the newly createdFlutterView
>FlutterActivityAndFragmentDelegate.java
@NonNull
  View onCreateView(
      LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {...if (host.getRenderMode() == RenderMode.surface) {
      flutterView = new FlutterView(host.getActivity(), flutterSurfaceView);
    } else {
      FlutterTextureView flutterTextureView = newFlutterTextureView(host.getActivity()); . flutterView =new FlutterView(host.getActivity(), flutterTextureView);
    }
    flutterSplashView = newFlutterSplashView(host.getContext()); . flutterSplashView.displayFlutterViewWithSplash(flutterView, host.provideSplashScreen()); flutterView.attachToFlutterEngine(flutterEngine);return flutterSplashView;
  }

Copy the code
  1. callflutterView#attachToFlutterEngineMethods afterflutterEngineBegin to renderFlutterViewAt this point you can forward from

FlutterView events are bound to the flutterEngine, such as user touch events, barrier-free service events, keyboard events, etc.

3. FlutterActivity calls the corresponding method of the proxy class at OnStart and starts executing the dart code internally:

>FlutterActivityAndFragmentDelegate.java 
void onStart(a) {
    Log.v(TAG, "onStart()");
    ensureAlive();
    doInitialFlutterViewRun();
  }
Copy the code

4. Internal doInitialFlutterViewRun method will determine whether the dart is carried out, it would no longer if it is carried out, and then obtain the resource path and loading

>FlutterActivityAndFragmentDelegate.java 
private void doInitialFlutterViewRun(a) {
    if(host.getCachedEngineId() ! =null) {
      return;
    }
    if (flutterEngine.getDartExecutor().isExecutingDart()) {
      return;
    }

  	//STEP1 sends the event setting the initial route to dart via the flutter/navigation method channel
    if(host.getInitialRoute() ! =null) {
      flutterEngine.getNavigationChannel().setInitialRoute(host.getInitialRoute());
    }
	//STEP2 obtain the bundle resource path
    String appBundlePathOverride = host.getAppBundlePath();
    if (appBundlePathOverride == null || appBundlePathOverride.isEmpty()) {
      appBundlePathOverride = FlutterInjector.instance().flutterLoader().findAppBundlePath();
    }

    Dart code entry can be set to the IO. Flutter.Entrypoint property in manifest.xnl
    DartExecutor.DartEntrypoint entrypoint =
        new DartExecutor.DartEntrypoint(
            appBundlePathOverride, host.getDartEntrypointFunctionName());
    //STEP4 call the C++ layer code to invoke the main method, which is the work of the dart layer.
    flutterEngine.getDartExecutor().executeDartEntrypoint(entrypoint);
  }

Copy the code

5. Look at the above code labeling STEP1, global search flutter/navigation keywords, find SystemChannels. The navigation channel is concrete implementation in the dart, ALT + click on the navigation The call may have been made in a WidgetsBinding, but IinitialRoute is not handled here, and the initial route is described under app.dart.

>binding.dart
mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, RendererBinding, SemanticsBinding {
  @override
  void initInstances(a) {
    super.initInstances(); . SystemChannels.navigation.setMethodCallHandler(_handleNavigationInvocation); }}Copy the code

Dart indicates that the value of the initialRoute is in window.dart DefaultRouteName, which can be set in Android with NewEngineIntentBuilder#initialRoute.

This is the basic flow of flutterActivity loading a page, but there are a lot of details to build a usable hybrid routing scheme, such as splash screen, Engine reuse, route stack switching, and so on.