2

1. Environment construction

First of all, developers need to build a good development environment in accordance with the native Android and iOS build process. Then, go to Flutter official website download the latest SDK, after downloading, unzip it to a custom directory. If there is a download problem, you can use the temporary mirror that Flutter officially built for Chinese developers.

export PUB_HOSTED_URL=https://pub.flutter-io.cn
export FLUTTER_STORAGE_BASE_URL=https://storage.flutter-io.cn

In order to facilitate the use of the command line, additional environment variables need to be configured. First, use the vim command to open the terminal.

vim ~/.bash_profile  

Then, add the following code to the .bash_profile file, and use the source ~/.bash_profile command to make the file changes take effect.

export PATH=/Users/mac/Flutter/flutter/bin:$PATH
//刷新.bash_profile
source ~/.bash_profile

After completing the above operations, use the flutter doctor command to check whether the environment is correct. If it succeeds, the following message will be output.
在这里插入图片描述

Second, create a Flutter aar package

There are two main ways to integrate Flutter with native Android. One is to create a flutter module and then rely on it as a native module; the other is to package the flutter module into aar, and then rely on the aar package in the native project. The official recommendation is to use the aar method. Access.

There are two ways to create flutter aar, one is to use Android Studio to generate it, and the other is to use the command line directly. Use the command line to create a flutter module as follows:

flutter create -t module flutter_module

Then, enter the flutter_module and execute the flutter build aar command to generate the aar package. If there is no error, the /flutter_module/.android/Flutter/build/outputs directory, as shown in the figure below.

在这里插入图片描述

build/host/outputs/repo
└── com
    └── example
        └── my_flutter
            ├── flutter_release
            │   ├── 1.0
            │   │   ├── flutter_release-1.0.aar
            │   │   ├── flutter_release-1.0.aar.md5
            │   │   ├── flutter_release-1.0.aar.sha1
            │   │   ├── flutter_release-1.0.pom
            │   │   ├── flutter_release-1.0.pom.md5
            │   │   └── flutter_release-1.0.pom.sha1
            │   ├── maven-metadata.xml
            │   ├── maven-metadata.xml.md5
            │   └── maven-metadata.xml.sha1
            ├── flutter_profile
            │   ├── ...
            └── flutter_debug
                └── ...

Of course, we can also use Android Studio to generate aar packages. Select File -> New -> New Flutter Project -> Flutter Module to generate a Flutter module project.
在这里插入图片描述

Then we select build ->Flutter ->Build AAR to generate the aar package.

在这里插入图片描述
The next step is to integrate aar in the native Android project.

Three, add Flutter dependency

3.1 Add aar dependency

Official recommendation method

The way to integrate the aar package is the same as the way to integrate the ordinary aar package. First, create a new libs folder in the app directory and add the following configuration in build.gradle.

android {
    ...

buildTypes {
        profile {
          initWith debug
        }
      } 

    String storageUrl = System.env.FLUTTER_STORAGE_BASE_URL ?:
      "https://storage.googleapis.com"
      repositories {
        maven {
            url '/Users/mac/Flutter/module_flutter/build/host/outputs/repo'
        }
        maven {
            url "$storageUrl/download.flutter.io"
        }
      }
    
}

dependencies {
      debugImplementation 'com.xzh.module_flutter:flutter_debug:1.0'
      profileImplementation 'com.xzh.module_flutter:flutter_profile:1.0'
      releaseImplementation 'com.xzh.module_flutter:flutter_release:1.0'
    }

Local Libs way

Of course, we can also copy the generated aar package to the local libs, and then open app/build.grade to add local dependencies, as shown below.

repositories {
    flatDir {
        dirs 'libs'
    }
}

dependencies {
    ...
    //添加本地依赖
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation(name: 'flutter_debug-1.0', ext: 'aar')
    implementation 'io.flutter:flutter_embedding_debug:1.0.0-f0826da7ef2d301eb8f4ead91aaf026aa2b52881'
    implementation 'io.flutter:armeabi_v7a_debug:1.0.0-f0826da7ef2d301eb8f4ead91aaf026aa2b52881'
    implementation 'io.flutter:arm64_v8a_debug:1.0.0-f0826da7ef2d301eb8f4ead91aaf026aa2b52881'
    implementation 'io.flutter:x86_64_debug:1.0.0-f0826da7ef2d301eb8f4ead91aaf026aa2b52881'
}

io.flutter: Where does flutter_embedding_debug come from? It is actually in the flutter_release-1.0.pom file when build/host/outputs/repo is generated.
在这里插入图片描述

  <groupId>com.example.flutter_library</groupId>
  <artifactId>flutter_release</artifactId>
  <version>1.0</version>
  <packaging>aar</packaging>
  <dependencies>
  <dependency>
  <groupId>io.flutter.plugins.sharedpreferences</groupId>
  <artifactId>shared_preferences_release</artifactId>
  <version>1.0</version>
  <scope>compile</scope>
  </dependency>
  <dependency>
  <groupId>io.flutter</groupId>
  <artifactId>flutter_embedding_release</artifactId>
  <version>1.0.0-626244a72c5d53cc6d00c840987f9059faed511a</version>
  <scope>compile</scope>
  </dependency>

When copying, pay attention to the environment of our local aar package, they are one-to-one correspondence. Next, in order to be able to rely on it correctly, you also need to add the following dependencies in the outer build.gradle.

buildscript {
repositories {
    google()
    jcenter()
    maven {
        url "http://download.flutter.io"        //flutter依赖
    }
  }
dependencies {
    classpath 'com.android.tools.build:gradle:4.0.0'
  }
}

If the native Android project uses componentized development ideas, it is usually added under a certain module/lib, such as module_flutter.

 在module_flutter build.gradle下配置
  repositories {
      flatDir {
        dirs 'libs'   // aar目录
      }
    }

在主App 下配置
repositories {
//  详细路径
flatDir {
    dirs 'libs', '../module_flutter/libs'
  }
}

3.2 Source code dependency

In addition to using the aar method, we can also use the flutter module source code to rely on. First, we create a module in the native Android project, as shown below.
在这里插入图片描述
After the addition is successful, the system will generate the following code in the settings.gradle file by default.

 
include ':app'                                  
setBinding(new Binding([gradle: this]))                              
evaluate(new File(                                                   
  settingsDir.parentFile,                                           
  'my_flutter/.android/include_flutter.groovy'                    
))                                                                   

Then, add source code dependencies in the app/build.gradle file.

dependencies {
  implementation project(':flutter')
}

3.3 Use fat-aar to compile aar

If some third-party libraries are introduced into flutter, then multiple projects need to use fat-aar when using flutter. First, add fat-aar dependency in .android/build.gradle.

 dependencies {
        ...
        com.github.kezong:fat-aar:1.3.6
    }

Then, add the following plugin and dependencies in .android/Flutter/build.gradle.

dependencies {
    testImplementation 'junit:junit:4.12'
  
    // 添加 flutter_embedding.jar debug
    embed "io.flutter:flutter_embedding_debug:1.0.0-eed171ff3538aa44f061f3768eec3a5908e8e852"
    // 添加 flutter_embedding.jar release
    embed "io.flutter:flutter_embedding_release:1.0.0-e1e6ced81d029258d449bdec2ba3cddca9c2ca0c"
    // 添加各个 cpu 版本 flutter.so
    embed "io.flutter:arm64_v8a_debug:1.0.0-eed171ff3538aa44f061f3768eec3a5908e8e852"
    embed "io.flutter:armeabi_v7a_debug:1.0.0-eed171ff3538aa44f061f3768eec3a5908e8e852"
    embed "io.flutter:x86_64_debug:1.0.0-eed171ff3538aa44f061f3768eec3a5908e8e852"
    embed "io.flutter:x86_debug:1.0.0-eed171ff3538aa44f061f3768eec3a5908e8e852"

At this point, if we run the project, an error of Cannot fit requested classes in a single dex file This is a very old subcontracting problem, which means that one dex with more than 65k methods can no longer fit multiple dex. The solution is to add multidex to app/build.gradle.

android {
    defaultConfig {
            ···
        multiDexEnabled true
    }
}

dependencies {
    //androidx支持库的multidex库
    implementation 'androidx.multidex:multidex:2.0.1'
}

Five, jump to Flutter

5.1 Start FlutterActivity

After integrating Flutter, we then register FlutterActivity in AndroidManifest.xml to implement a simple jump.

<activity
  android:name="io.flutter.embedding.android.FlutterActivity"
  android:theme="@style/LaunchTheme"
  android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
  android:hardwareAccelerated="true"
  android:windowSoftInputMode="adjustResize"
  android:exported="true"  />

Then add a jump code to any page, for example.

myButton.setOnClickListener(new OnClickListener() {
  @Override
  public void onClick(View v) {
    startActivity(
      FlutterActivity.createDefaultIntent(this)
    );
  }
});

But when I run the project and execute the jump, I still get an error. The error message is as follows.

   java.lang.RuntimeException: Unable to start activity ComponentInfo{com.snbc.honey_app/io.flutter.embedding.android.FlutterActivity}: java.lang.IllegalStateException: ensureInitializationComplete must be called after startInitialization
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2946)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3081)
        at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:78)
        at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:108)
        at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:68)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1831)
        at android.os.Handler.dispatchMessage(Handler.java:106)
        at android.os.Looper.loop(Looper.java:201)
        at android.app.ActivityThread.main(ActivityThread.java:6806)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:547)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:873)

Reading the error should be a problem of initialization, but the official documentation does not mention any code related to the initialization step. Check the official issue of Flutter, which means that you need to add a line of initialization code:

public class MyApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        FlutterMain.startInitialization(this);
    }
}

Then, I ran it again and found that the following error was reported.

java.lang.NoClassDefFoundError: Failed resolution of: Landroid/arch/lifecycle/DefaultLifecycleObserver;
        at io.flutter.embedding.engine.FlutterEngine.<init>(FlutterEngine.java:152)
        at io.flutter.embedding.android.FlutterActivityAndFragmentDelegate.setupFlutterEngine(FlutterActivityAndFragmentDelegate.java:221)
        at io.flutter.embedding.android.FlutterActivityAndFragmentDelegate.onAttach(FlutterActivityAndFragmentDelegate.java:145)
        at io.flutter.embedding.android.FlutterActivity.onCreate(FlutterActivity.java:399)
        at android.app.Activity.performCreate(Activity.java:7224)
        at android.app.Activity.performCreate(Activity.java:7213)
        at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1272)
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2926)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3081)
        at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:78)
        at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:108)
        at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:68)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1831)
        at android.os.Handler.dispatchMessage(Handler.java:106)
        at android.os.Looper.loop(Looper.java:201)
        at android.app.ActivityThread.main(ActivityThread.java:6806)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:547)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:873)
     Caused by: java.lang.ClassNotFoundException: Didn't find class "android.arch.lifecycle.DefaultLifecycleObserver" on path: DexPathList[[zip file "/data/app/com.example.myapplication-kZH0dnJ-qI1ow1NqGOB2ug==/base.apk"],nativeLibraryDirectories=[/data/app/com.example.myapplication-kZH0dnJ-qI1ow1NqGOB2ug==/lib/arm64, /data/app/com.example.myapplication-kZH0dnJ-qI1ow1NqGOB2ug==/base.apk!/lib/arm64-v8a, /system/lib64, /vendor/lib64]]

The last log gives a hint that the lifecycle is missing, so add the lifecycle dependency, as follows.

   implementation 'android.arch.lifecycle:common-java8:1.1.0'

Then run it again and there is no problem.
在这里插入图片描述

5.2 Start with FlutterEngine

By default, each FlutterActivity will create a FlutterEngine when it is created, and each FlutterEngine has an initialization operation. This means that there will be a certain delay when starting a standard FlutterActivity. In order to reduce this delay, we can create a FlutterEngine in advance before starting FlutterActivity, and then use FlutterEngine when jumping to FlutterActivity. The most common way is to initialize the FlutterEngine first in the Application, for example.

class MyApplication : Application() {
    
    lateinit var flutterEngine : FlutterEngine

    override fun onCreate() {
        super.onCreate()
        flutterEngine = FlutterEngine(this)
        flutterEngine.dartExecutor.executeDartEntrypoint(
            DartExecutor.DartEntrypoint.createDefault()
        )
        FlutterEngineCache
            .getInstance()
            .put("my_engine_id", flutterEngine)
    }
}

Then, we can use this buffered FlutterEngine when jumping to FlutterActivity. Since the engine_id has been added when the FlutterEngine is initialized, we need to use this engine_id to start it.

myButton.setOnClickListener {
  startActivity(
    FlutterActivity
      .withCachedEngine("my_engine_id")
      .build(this)
  )
}

Of course, at startup, we can also jump to a default route, just call the setInitialRoute method at startup.

class MyApplication : Application() {
  lateinit var flutterEngine : FlutterEngine
  override fun onCreate() {
    super.onCreate()
    // Instantiate a FlutterEngine.
    flutterEngine = FlutterEngine(this)
    // Configure an initial route.
    flutterEngine.navigationChannel.setInitialRoute("your/route/here");
    // Start executing Dart code to pre-warm the FlutterEngine.
    flutterEngine.dartExecutor.executeDartEntrypoint(
      DartExecutor.DartEntrypoint.createDefault()
    )
    // Cache the FlutterEngine to be used by FlutterActivity or FlutterFragment.
    FlutterEngineCache
      .getInstance()
      .put("my_engine_id", flutterEngine)
  }
}

Six, communicate with Flutter

After the above operations, we have been able to complete the native Android jump to Flutter. How can Flutter jump to the native Activity or how does Flutter destroy itself and return to the native page? At this time, the communication mechanism of Flutter and native Android, namely Channel, is used, which are MethodChannel, EventChannel, and BasicMessageChannel, respectively.

  • MethodChannel: Used to transfer method calls. It is a more commonly used PlatformChannel.
  • EventChannel: Used to deliver events.
  • BasicMessageChannel: used to transfer data.

For this simple jump operation, you can directly use MethodChannel to complete. First, we create a new PluginManager class in flutter_module, and then add the following code.

import 'package:flutter/services.dart';

class PluginManager {
  static const MethodChannel _channel = MethodChannel('plugin_demo');

  static Future<String> pushFirstActivity(Map params) async {
    String resultStr = await _channel.invokeMethod('jumpToMain', params);
    return resultStr;
  }

}

Then, when we click the return button on the Flutter entry page, we add a return method, which is mainly to call PluginManager to send a message, as follows.

Future<void> backToNative() async {
    String result;
    try {
      result = await PluginManager.pushFirstActivity({'key': 'value'});
    } on PlatformException {
      result = '失败';
    }
    print('backToNative: '+result);
  }

Next, re-use flutter build aar to recompile the aar package, and add the following code to the configureFlutterEngine method of the native Android Flutter entry page.

class FlutterContainerActivity : FlutterActivity() {

    private val CHANNEL = "plugin_demo"

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

    }


    override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
        GeneratedPluginRegistrant.registerWith(flutterEngine)
        MethodChannel(flutterEngine.dartExecutor, CHANNEL).setMethodCallHandler { call, result ->
            if (call.method == "jumpToMain") {
                val params = call.argument<String>("key")
                Toast.makeText(this,"返回原生页面",Toast.LENGTH_SHORT).show()
                finish()
                result.success(params)
            } else {
                result.notImplemented()
            }
        }
    }

}

When re-running the native project, click the back button in the upper left corner of Flutter to return to the native page, and other mixed jumps can also be solved in this way.
在这里插入图片描述

For the issue of hybrid routing and FlutterEngine multi-instance in hybrid development, please refer to FlutterBoost .


xiangzhihong
5.9k 声望15.3k 粉丝

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