一、环境搭建
首先,需要开发者按照原生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文件中,
文章图片
com.example.flutter_library
flutter_release
1.0 aar
io.flutter.plugins.sharedpreferences
shared_preferences_release
1.0
compile
io.flutter
flutter_embedding_release
1.0.0-626244a72c5d53cc6d00c840987f9059faed511a
compile
在拷贝的时候,注意我们本地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实现一个简单的跳转。
然后在任何页面添加一个跳转代码,比如。
myButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
startActivity(
FlutterActivity.createDefaultIntent(this)
);
}
});
【原生Android工程接入Flutter aar】不过当我运行项目,执行跳转的时候还是报错了,错误的信息如下。
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.(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 : FlutterEngineoverride 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:用于传递数据。
import 'package:flutter/services.dart';
class PluginManager {
static const MethodChannel _channel = MethodChannel('plugin_demo');
static Future pushFirstActivity(Map params) async {
String resultStr = await _channel.invokeMethod('jumpToMain', params);
return resultStr;
}}
然后,当我们点击Flutter入口页面的返回按钮时,添加一个返回的方法,主要是调用PluginManager发送消息,如下。
Future 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("key")
Toast.makeText(this,"返回原生页面",Toast.LENGTH_SHORT).show()
finish()
result.success(params)
} else {
result.notImplemented()
}
}
}}
重新运行原生项目时,点击Flutter左上角的返回按钮就可以返回到原生页面,其他的混合跳转也可以使用这种方式进行解决。
文章图片
关于混合开发中混合路由和FlutterEngine多实例的问题,可以参考FlutterBoost。
推荐阅读
- 【Android进阶】Android使用 flutter
- Android 开发 3 年,被要求转 Flutter ,Flutter 真的很香吗
- Flutter主题切换 flutter redux
- Flutter AppBar入门使用
- flutter-样式
- 跨平台新潮,写给程序员的Flutter完整开发实战详解,带你玩转Flutter
- android studio flutter代理设置问题
- 适用于 Flutter 的 Google 移动广告 SDK 正式版现已发布
- 写给程序员的Flutter完整开发实战详解,轻轻松松解决跨平台烦恼
- flutter添加到android原生