Flutter 应用集成浅析
简单分析下 Flutter 如何集成到现有 Android 应用中。
前言
截止目前 Flutter (1.12.13+hotfix.5),集成到 Android 应用已经非常简单了。
Flutter can be embedded into your existing Android application piecemeal, as a source code Gradle subproject or as AARs.
无非两种集成方式:源码集成或 AAR 产物集成。,这里不再赘述,仅做简单总结。
- Android Studio 3.6 + Flutter IntelliJ plugin (version 42及以上) 可以方便快速地自动集成 Flutter 模块
- Flutter 的 Android 引擎使用 Java 8 特性,所以要记得开启 1.8 兼容,否则会提示 “default interface methods” 问题
- 源码集成 时注意 Android 工程和 Flutter 工程在同级目录下
- 注意编译模式及CPU架构,不匹配的话会出现找不到
libflutter.so
的问题 - Flutter 的 AOT(ahead of time) 编译产物只支持
armeabi-v7a
和arm64-v8a
,x86 下可进行 debug (Just-In-Time, JIT 模式),但不能安装 release 包
产物集成
产物集成相比源码集成更简单。主要步骤如下,具体过程可参考官方文档。
第一步,生成 AAR。
1 | cd flutter_project |
缺省编译所有模式下的产物,包括 debug, profile 和 release。不想编译 profile 模式产物的话,加上 --no-profile
即可。
另外注意编译 AAR 有限制,仅能为 plugin 或 module 工程编译 AAR 产物,否则提示如下错误。
第二步,添加产物仓库及依赖。
1 | repositories { |
1 | dependencies { |
源码集成
第一步,创建 Flutter 项目。在 host app 的同级目录下创建 my_flutter
项目:
1 | $ cd some/path/ |
第二步,引入 Flutter 项目并作为模块。在 host app 的 settings.gradle
文件中添加如下配置:
1 | ... |
这段配置的作用不妨视作黑魔法,其作用如下:
- 将
my_flutter
Flutter Project 作为名为:flutter
的 Android Library Module,引入到当前 Android Project - 查找并保存
my_flutter
依赖的 Flutter 插件
第三步,添加对 :flutter
module 的依赖。在 host app 的 build.gradle
中加上以下配置:
1 | dependencies { |
以上是手工操作步骤。在 Android 3.6 中可以自动操作,同样也会产生跟上面相同的配置。
Android Studio 3.6 中打开 host app,并新建一个 Flutter Module:
创建完成后生成的配置如下:
可见,无论手工操作还是自动操作,源码集成的关键在于这几个脚本:
my_flutter/.android/include_flutter.groovy- 黑魔法,用于在 Android 工程引入 Flutter 工程,我们略过my_flutter/.android/Flutter/build.gradle
- 这个脚本决定 Flutter 工程如何构建,它引入 Flutter SDK 中的flutter.gradle
脚本<Flutter SDK>/packages/flutter_tools/gradle/flutter.gradle
- 集成 Flutter 工程的核心
接下来我将分析 build.gradle
和 flutter.gradle
两个脚本是如何将 Flutter 集成到 Android 应用的。
build.gradle
先来分析 my_flutter/.android/Flutter/build.gradle
。
第一步,加载并解析 .android
目录下的 local.properties
文件。
1 | // 加载 .android 目录下的 local.properties 文件 |
第二步,引入 flutter.gradle
脚本并通过 flutter
插件指定 Flutter Project 源码位置。flutter
插件来自 flutter.gradle
脚本中的 FlutterPlugin
。
1 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" |
flutter.gradle
再来看 <Flutter SDK>/packages/flutter_tools/gradle/flutter.gradle
。flutter
插件的具体实现在 apply()
方法。
1 | // 应用 FlutterPlugin |
apply()
主要流程概括如下:
- 创建 FlutterExtension。这个插件很简单,只包括
source
(源码路径)target
(Dart入口,通常是lib/main.dart
)
- 添加 Flutter Task
- 配置 APK
- 针对 Target Platform 生成 multiple APK 或 fat APK
- 配置 build type,例如是否压缩资源
- 获取 Flutter 相关信息
- 本地 Flutter SDK 路径
flutter
命令- 引擎版本
- 引擎路径 (来自
gradle.properties
文件的local-engine-out
属性 )
- 添加 Flutter Dependency
接下来我们挑重点说,
- 添加 Flutter Task
- 添加 Flutter Dependency
简单来说,添加 Flutter Task 是添加一些 Task 用于处理三类 Flutter 相关的资源,库、资源、插件:
- 库 - 库是编译过程中生成jar文件和so文件。库文件应正确地打包到 AAR 或 APK
- 资源 - 资源是
pubspec.yaml
文件添加指定的各类资源,如图片、字体等。资源文件应正确地打包到 AAR 或 APK - 插件 - 插件是
pubspec.yaml
文件添加的各种 Dart 库。插件的处理比较麻烦,一是某些插件包含原生Java或OC代码,二是插件之间有依赖关系
而添加 Flutter Dependency 则是将 Flutter 框架(包括引擎)添加为 Android 工程的依赖,具体包括:
flutter_embedding.jar
- Flutter Framework,即io.flutter.embedding.android.FlutterActivity
所在的 Java 库libflutter.so
- Flutter 引擎
跟以上流程相关的几个辅助方法:
useLocalEngine()
- 判断是否使用本地 Flutter 引擎,来自gradle.properties
文件的local-engine-repo
属性getTargetPlatforms
- 获取 Target Platform,来自gradle.properties
文件的target-platform
属性shouldSplitPerAbi()
- 判断是否生成 multiple APK (即针对每种架构生成一个 APK,与之对应的是 fat APK),来自gradle.properties
文件的split-per-abi
属性,缺省为false
getPluginList()
- 解析.flutter-plugins
文件获取插件列表getPluginDependencies()
- 解析.flutter-plugins-dependencies
文件获取插件依赖
添加 Flutter Task
addFlutterTasks()
是最复杂的方法。精简后的代码如下:
1 | private void addFlutterTasks(Project project) { |
- 第3步和第4步处理库和资源
- 第5步处理插件
addFlutterDeps
首先看第3步 addFlutterDeps
的创建。
1 | // 1. 创建 compileTask。FlutterTask 实际是对 flutter build 命令的包装 |
FlutterTask
继承自 BaseFlutterTask
。BaseFlutterTask
实际是对 flutter build
命令的包装,具体包装过程可以参数 BaseFlutterTask.buildBundle()。
configurePlugins
再来看 configurePlugins()
如何配置插件依赖。仍然分两种情况处理:源码集成和产物集成。代码如下:
1 | /** |
另外,注释中提到了几个很重要的信息:
插件在 pubspec.yaml 中添加。当运行
flutter pub get
命令时,工具会生成.flutter-plugins
和.flutter-plugins-dependencies
文件。.flutter-plugins
包含每个插件的位置,.flutter-plugins-dependencies
包含每个插件的依赖项Android 项目的
settings.gradle
文件会加载每个插件为子工程
以我们的项目为例。.flutter-plugins
文件内容如下:
1 | # This is a generated file; do not edit or check into version control. |
该项目在 Android Studio 中看到的工程结构如下:
添加 Flutter Dependency
apply()
的另一个要点是添加 Flutter 依赖,由 addFlutterDependencies()
实现。相比添加 Flutter Task,添加 Flutter 依赖则简单得多。
1 | class FlutterPlugin implements Plugin<Project> { |
实践
通过看 buid.gradle
和 flutter.gradle
源码,我们对 Flutter 如何集成到 Android 项目中有一定的了解了。现在结合两个实例来加深理解,这里以一个编译失败问题和 so 加载失败问题为例。
编译失败 Cause: assert appProject != null
Flutter 1.12.13+hotfix.5 有一个编译失败的 Issue #42214,错误日志如下:
问题来源:这个问题实际上来自 flutter.gradle
脚本中的一处 bug, 见Pull #41333
问题分析:addFlutterTasks()
方法中第4步存在硬编码问题,默认所有的 app module 名为 app
。实际项目中 app module 名很可能不是 app
,所以断言失败,导致集成 Flutter 后编译出错
解决办法:要么将 app module 改名为 app
,要么给本地的 flutter.gradle
打上如下补丁。注意要将 IGame
替换成实际项目名。
找不到 libflutter.so
或 libapp.so
一些年代比较久远的 Android 项目中,so 往往放在 lib/armeabi
目录。
而 Flutter 的 AOT 产物只支持 x86_64
、 armeabi-v7a
和 arm64-v8a
三种架构。另外,Flutter 的构建流程默认将会将 so 文件打包到对应的目录中,
所以会出现找不到 libflutter.so
的问题。一种简单而粗暴的解决方案见 Flutter原理与实践 - 美团技术团队。
Flutter 构建流程中 packFlutterAppAotTask
会将生成的 app.so
移动并重命名为 lib/<abi>/libapp.so
。
1 | Task packFlutterAppAotTask = project.tasks.create( |
修改一:注意这里的 <abi>
只支持上述提到的三种架构,并不包括 armeabi
。我们可以修改 packFlutterAppAotTask
,修改后将 app.so
移动并重命名为 lib/armeabi/libapp.so
的目的。
修改二:修改原始的 embedding jar 包(libflutter.so
从原始的 lib/armeabi-v7a
移到 lib/armeabi
目录),并在 gradle.properties
中提供 local-engine-repo
,将其指向修改后的 embedding jar 包。具体见 addFlutterDependencies()
1 | /** |
参考
Integrate a Flutter module into your Android project - Flutter