乐趣区

关于android:原生Android工程接入Flutter-aar

一、环境搭建

首先,须要开发者依照原生 Android、iOS 的搭建流程搭建好开发环境。而后,去 Flutter 官网下载最新的 SDK,下载结束后解压到自定义目录即可。如果呈现下载问题,能够应用 Flutter 官网为中国开发者搭建的长期镜像。

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

为了方便使用命令行,还须要额定配置下环境变量。首先,应用 vim 命令关上终端。

vim ~/.bash_profile  

而后,将如下代码增加到.bash_profile 文件中,并应用 source ~/.bash_profile 命令使文件更改失效。

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

实现上述操作之后,接下来应用 flutter doctor 命令查看环境是否正确,胜利会输入如下信息。

二、创立 Flutter aar 包

原生 Android 集成 Flutter 次要有两种形式,一种是创立 flutter module,而后以原生 module 那样依赖;另一种形式是将 flutter module 打包成 aar,而后在原生工程中依赖 aar 包,官网举荐 aar 的形式接入。

创立 flutter aar 有两种形式,一种是应用 Android Studio 进行生成,另一种是间接应用命令行。应用命令行创立 flutter module 如下:

flutter create -t module flutter_module

而后,进入到 flutter_module,执行 flutter build aar 命令生成 aar 包,如果没有任何出错,会在 /flutter_module/.android/Flutter/build/outputs 目录下生成对应的 aar 包,如下图。

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
                └── ...

当然,咱们也能够应用 Android Studio 来生成 aar 包。顺次抉择 File -> New -> New Flutter Project -> Flutter Module 生成 Flutter module 工程。

而后咱们顺次抉择 build ->Flutter ->Build AAR 即可生成 aar 包。


接下来,就是在原生 Android 工程中集成 aar 即可。

三、增加 Flutter 依赖

3.1 增加 aar 依赖

官网举荐形式

集成 aar 包的形式和集成一般的 aar 包的形式是一样大的。首先,在 app 的目录下新建 libs 文件夹 并在 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'
    }

本地 Libs 形式

当然,咱们也能够把生成的 aar 包拷贝到本地 libs 中,而后关上 app/build.grade 增加本地依赖,如下所示。

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:flutter_embedding_debug 来自哪里呢,其实是 build/host/outputs/repo 生成的时候 flutter_release-1.0.pom 文件中,

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

在拷贝的时候,留神咱们本地 aar 包的环境,它们是一一对应的。接下来,为了可能正确依赖,还须要在外层的 build.gradle 中增加如下依赖。

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

如果,原生 Android 工程应用的是组件化开发思路,通常是在某个 module/lib 下依赖,比方 module_flutter 进行增加。

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

在主 App 下配置
repositories {
//  具体门路
flatDir {dirs 'libs', '../module_flutter/libs'}
}

3.2 源码依赖

除了应用 aar 形式外,咱们还能够应用 flutter 模块源码的形式进行依赖。首先,咱们在原生 Android 工程中创立一个 module,如下图。

增加胜利后,零碎会默认在 settings.gradle 文件中生成如下代码。

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

而后,在 app/build.gradle 文件中增加源码依赖。

dependencies {implementation project(':flutter')
}

3.3 应用 fat-aar 编译 aar

如果 flutter 中引入了第三方的一些库,那么多个我的项目在应用 flutter 的时候就须要应用 fat-aar。首先,在 .android/build.gradle 中增加 fat-aar 依赖。

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

而后, 在 .android/Flutter/build.gradle 中增加如下 plugin 和依赖。

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"

此时,如果咱们运行我的项目,可能会报一个 Cannot fit requested classes in a single dex file 的谬误。这是一个很古老的分包问题,意思是 dex 超过 65k 办法一个 dex 曾经装不下了须要个多个 dex。解决的办法是,只须要在 app/build.gradle 增加 multidex 即可。

android {
    defaultConfig {
            ···
        multiDexEnabled true
    }
}

dependencies {
    //androidx 反对库的 multidex 库
    implementation 'androidx.multidex:multidex:2.0.1'
}

五、跳转 Flutter

5.1 启动 FlutterActivity

集成 Flutter 之后,接下来咱们在 AndroidManifest.xml 中注册 FlutterActivity 实现一个简略的跳转。

<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"  />

而后在任何页面增加一个跳转代码,比方。

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

不过当我运行我的项目,执行跳转的时候还是报错了,谬误的信息如下。

   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)

看报错应该是初始化的问题,然而官网文档没有提到任何初始化步骤相干的代码,查查 Flutter 官网的 issue,示意要加一行初始化代码:

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

而后,我再次运行,发现报了如下谬误。

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]]

最初的日志给出的提醒是 lifecycle 缺失,所以增加 lifecycle 的依赖即可,如下。

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

而后再次运行就没啥问题了。

5.2 应用 FlutterEngine 启动

默认状况下,每个 FlutterActivity 被创立时都会创立一个 FlutterEngine,每个 FlutterEngine 都有一个初始化操作。这意味着在启动一个规范的 FlutterActivity 时会有肯定的提早。为了缩小此提早,咱们能够在启动 FlutterActivity 之前事后创立一个 FlutterEngine,而后在跳转 FlutterActivity 时应用 FlutterEngine 即可。最常见的做法是在 Application 中先初始化 FlutterEngine,比方。

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

而后,咱们在跳转 FlutterActivity 时应用这个缓冲的 FlutterEngine 即可,因为 FlutterEngine 初始化的时候曾经增加了 engine_id,所以启动的时候须要应用这个 engine_id 进行启动。

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

当然,在启动的时候,咱们也能够跳转一个默认的路由,只须要在启动的时候调用 setInitialRoute 办法即可。

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

六、与 Flutter 通信

通过下面的操作,咱们曾经可能实现原生 Android 跳转 Flutter,那如何实现 Flutter 跳转原生 Activity 或者 Flutter 如何销毁本人返回原生页面呢?此时就用到了 Flutter 和原生 Android 的通迅机制,即 Channel,别离是 MethodChannel、EventChannel 和 BasicMessageChannel。

  • MethodChannel:用于传递办法调用,是比拟罕用的 PlatformChannel。
  • EventChannel: 用于传递事件。
  • BasicMessageChannel:用于传递数据。

对于这种简略的跳转操作,间接应用 MethodChannel 即可实现。首先,咱们在 flutter_module 中新建一个 PluginManager 的类,而后增加如下代码。

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

}

而后,当咱们点击 Flutter 入口页面的返回按钮时,增加一个返回的办法,次要是调用 PluginManager 发送音讯,如下。

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

接下来,从新应用 flutter build aar 从新编译 aar 包,并在原生 Android 的 Flutter 入口页面的 configureFlutterEngine 办法中增加如下代码。

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

}

从新运行原生我的项目时,点击 Flutter 左上角的返回按钮就能够返回到原生页面,其余的混合跳转也能够应用这种形式进行解决。

对于混合开发中混合路由和 FlutterEngine 多实例的问题,能够参考 FlutterBoost。

退出移动版