Flutter 混合接入是指 Flutter 以依赖库的形式接入现有的 Android 或 iOS 项目。不同于集中式的 Flutter 项目( Flutter 做为主项目,把 Android 和 iOS 项目包括在内),Flutter 混合接入更适合当前已有 Android 和 iOS 项目,以及可能需要与 Flutter 页面产生交互的场景。
混合接入的思路是基于现有的 Android 或 iOS 项目,添加对 Flutter 模块的依赖。以 Android 为例,Android 实现界面依靠 Activity 或 Fragment 组件,组件布局通过 FlutterView 做为 Android 视图容器把 Flutter 实现的界面渲染到屏幕上。Android 和 Flutter 模块通过 Platform Channel 传递消息。Flutter 模块依赖的第三方库通过实现 Android 插件与 Android 相互调用。Flutter 模块添加到现有项目的同时,插件也会注入到现有项目实现互通。
官方对 Flutter 接入现有项目做了说明,详细请参考 Add Flutter to existing app。本文仅演示 AAR 依赖的实现思路,目的是帮助已有 Android 项目快速接入 Flutter,或者期望 Android 和 Flutter 模块可交互的新 Android 项目。
接下来通过步骤演示来说明混合接入的方法。如果还没有 Android 或 iOS 项目,需要先创建一个 Android 或 iOS 项目。
创建 Android 项目
官方对创建 Android 项目做了说明,详细请参考 Create an Android project。但对于即将接入 Flutter 的 Android 项目,有些步骤需要注意。
使用 Android Studio 4.0 创建新项目的过程中,不要勾选 Use legacy android.support libraries。因为 Support Library 不再维护,替换为 AndroidX 。在 Flutter v1.17 之后,Flutter 模块仅支持使用 AndroidX 的 Android 项目。
创建 Flutter 模块
在 Android Studio 中使用 File -> New -> New Module 打开向导。设置项目名,Flutter SDK 路径,项目位置,项目描述后点击下一步。
设置 Flutter 模块的包名,现有的 Android 项目支持 AndroidX,勾选 Use androidx.* artifacts,然后完成。
重启 Android Studio,左侧边栏下拉菜单选择 Project Files 视图。如果创建的 Flutter 项目和 Android 项目在同一个目录,就会看到如下图所示的 Android 和 Flutter 项目文件结构。
生成 Flutter 模块包
推荐 Android 项目使用依赖 AAR 的方式接入 Flutter。这样的方式接入有明显的优势,Flutter 对 Android 代码侵入最小,Android 只需要依赖 Flutter 编译生成的 AAR 即可。而且 Android 编译不需要依赖 Flutter 环境,更便于多端团队并行开发。
接下来就介绍 Flutter AAR 生成和 Android 依赖 AAR 的方法。点选左侧边栏中的 Flutter 模块路径名称,然后在顶部菜单点击 Build -> Flutter -> Build AAR,Flutter 编译 AAR 的命令就会自动执行,并生成 debug,profile,release 三个版本的 AAR。
打开 Android 项目目录下 /app/build.gradle 文件,添加两个代码仓库,一个是刚生成的 AAR 的路径,推荐使用相对路径;另一个是公有的代码仓库,便于下载公有的第三方库,墙内推荐使用国内的镜像仓库。以下代码片作为示例参考,合并到 build.gradle 文件中。
android {
buildTypes {
// 允许 profile 类型从 debug 类型中复制配置
profile {
initWith debug
}
}
}
repositories {
// 指定 Flutter 仓库
maven {
url '../../flutter_module/build/host/outputs/repo'
// 如果使用相对路径,那么该路径是相对于本 build.gradle 文件路径
// flutter_module 是创建 Flutter Module 时设置的项目名称
}
maven {
// 国内也可以选用 storage.flutter-io.cn 域名作为镜像仓库
url 'https://storage.googleapis.com/download.flutter.io'
}
}
dependencies {
// 添加 Flutter AAR 依赖
// com.demo.exandroid.fluttermodule 是创建 Flutter Module 时设置的包名
debugImplementation 'com.demo.exandroid.fluttermodule:flutter_debug:1.0'
profileImplementation 'com.demo.exandroid.fluttermodule:flutter_profile:1.0'
releaseImplementation 'com.demo.exandroid.fluttermodule:flutter_release:1.0'
}
如果 dependencies 代码块中包含了自动添加的源码依赖方式的代码,需要注释掉,避免重复依赖。
dependencies {
// implementation project(path: ':flutter')
}
编辑完成后点击右上方出现的 Sync Now 同步 Android Gradle 配置。如果出现 No Version of NDK matched … ,就点击 Install NDK … ,Android Studio 会自动下载安装 NDK。安装完成后,Android Gradle 会继续执行编译完成。
在 Android 项目中使用 Flutter 引擎
需要修改部分 Android 代码实现 Flutter 界面的显示。打开 Android 项目下的 app 目录,参考示例代码对相应的文件进行创建和修改。
src/main/java/包名/MainActivity.kt
class MainActivity : AppCompatActivity() {
companion object {
private const val TAG_FLUTTER_FRAGMENT = "flutter_fragment"
}
private var flutterFragment: FlutterFragment? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val fragmentManager: FragmentManager = supportFragmentManager
// Attempt to find an existing FlutterFragment, in case this is not the
// first time that onCreate() was run.
flutterFragment = fragmentManager
.findFragmentByTag(TAG_FLUTTER_FRAGMENT) as FlutterFragment?
// Create and attach a FlutterFragment if one does not exist.
if (flutterFragment == null) {
val newFlutterFragment = FlutterFragment
.withCachedEngine("my_engine_id")
.build<FlutterFragment>()
flutterFragment = newFlutterFragment
fragmentManager
.beginTransaction()
.add(
R.id.fragment_container,
newFlutterFragment,
TAG_FLUTTER_FRAGMENT
)
.commit()
}
}
}
src/main/res/layout/activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/fragment_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
</FrameLayout>
src/main/java/包名/App.kt
class App : Application() {
private lateinit var flutterEngine: FlutterEngine
override fun onCreate() {
super.onCreate()
// Instantiate a FlutterEngine.
flutterEngine = FlutterEngine(this)
// Start executing Dart code to pre-warm the FlutterEngine.
flutterEngine.dartExecutor.executeDartEntrypoint(
DartEntrypoint(
FlutterMain.findAppBundlePath(),
"main"
)
)
// Cache the FlutterEngine to be used by FlutterActivity.
FlutterEngineCache
.getInstance()
.put("my_engine_id", flutterEngine)
}
}
src/main/AndroidManifest.xml
<application
android:name=".App"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
src/main/res/values/styles.xml
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="@android:style/Theme.Black.NoTitleBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
</style>
<style name="LaunchTheme" parent="Theme.AppCompat">
<item name="android:windowBackground">@drawable/ic_splash</item>
</style>
</resources>
App.kt 是自定义的 Application 子类,没有就新建一个,用于实现 Flutter 引擎预初始化。随后在 Activity 启动时获取 FlutterEngine,加快应用的启动速度。
styles.xml 文件的修改主要涉及去除 Android 项目默认开启的 ActionBar,以避免在 Flutter 接入后,同时看到 Flutter 和 Android 的标题栏。
使用 Fragment 做为 Flutter 界面显示的组件,可以方便以部分屏幕显示 Flutter 界面。
连接真机或虚拟机,点击工具栏的运行按钮,启动应用。
当这个页面出现时,说明 Flutter 已经成功接入到 Android 的现有项目了。