Flutter 踩坑记之集成 Flutter 到 Android 工程

不得不说 Flutter v1.7 仍然没有足够稳定,个人感觉玩玩 demo 还行,集成到已有工程项目仍然各种奇怪的坑。

前言

你可能会反驳我的观点,不是很产品都已经开始在发布产品中使用 Flutter 了吗,怎么到了你这里就不成熟稳定。

的确,已经不少产品使用 Flutter,我非常佩服这些将 Flutter 用于正式产品的团队,一是这得是踩过了多少坑,二是他们为后来人积累了不少宝贵经验。但我作为 Flutter 新手却仍然会轻易踩到一些坑,有些坑可能是 Flutter 代码 bug,有些坑仅仅只是 Flutter 文档更新不及时而已。这里举两个例子。

不存在的 flutter build aar 命令

Add Flutter to existing apps · flutter/flutter Wiki 是关于如何向已有 Android 工程集成 Flutter 的官方文档。

截图显示该文档6天前更新。今天是 2019-08-15,所以文档最后编辑日期是 2019-08-09,可以认为它已经足够新了。

-w949

文档中提到两种集成 Flutter 至现有 Android 工程的方式。方式一是将 Flutter 模块编译成 aar 文件,作为 Android 工程的依赖。文档中给出的编译命令如下:

1
2
$ cd some/path/my_flutter
$ flutter build aar

我的 Flutter 版本如下(已是目前最新)。

1
2
3
4
5
➜  flutter git:(stable) flutter --version
Flutter 1.7.8+hotfix.4 • channel stable • https://github.com/flutter/flutter.git
Framework • revision 20e59316b8 (4 weeks ago) • 2019-07-18 20:04:33 -0700
Engine • revision fee001c93f
Tools • Dart 2.4.0

但是我本地的 Flutter 运行 flutter build aar 后提示根本就不支持 aar 子命令!当前可支持的子命令如下:

-w885

推测这是一处文档错误。

可笑的 libflutter.so 加载失败问题

Flutter 另一个极坑的问题是由于某种原因加载不到 libflutter.so 导致应用崩溃。关于这个问题的 issue 超级多,随便就能搜出一大堆,比如 issue#36。官方给出的理由不少理由说这个问题不是问题,但我认为它大大减少了 Flutter 的易用性。

加载 libflutter.so 失败的原因有很多种。这里只分析原工程中存在 jniLibs/armeabi 目录导致 libflutter.so 压根没有打包到的 APK 的情形,简单总结如下:

早期的安卓普通是 armeabi 架构,而目前普遍是 armeabi-v7a 架构,可简单理解为 armeabi 架构已废弃。所以 APK 推荐使用 jniLibs/armeabi-v7a 目录来放 so 库,而不是放在 jniLibs/armeabi 目录中 。不过由于 armeabi-v7a 是兼容 armeabi 的,所以虽然很多老旧项目将 so 放在 jniLibs/armeabi 运行时其实也不会有问题。

但是由于 Flutter 官方只提供了四种 CPU 架构的 so 库:armeabi-v7a、arm64-v8a、x86 和 x86-64,这就会导致旧的 Android 项目中集成 Flutter 时出问题。问题就是,很可能压根就没将 libflutter.so 打包到 APK!

坑一:找不到 include_flutter.groovy 脚本

严格来说这个坑是我自己的问题,没有注意到项目目录结构。

Add Flutter to existing apps · flutter/flutter Wiki 使用如下命令在 some/path/my_flutter 中生成 Flutter 模块,

1
2
$ cd some/path/
$ flutter create -t module --org com.example my_flutter

在原有 Android 项目的 app 模块中添加如下配置用于集成 Flutter:

1
2
3
4
5
6
7
// MyApp/settings.gradle
include ':app' // assumed existing content
setBinding(new Binding([gradle: this])) // new
evaluate(new File( // new
settingsDir.parentFile, // new
'my_flutter/.android/include_flutter.groovy' // new
)) // new

一开始的我的 my_flutter 模块目录结构是这样的:

1
2
3
4
5
6
7
some/path/
|--- MyApp
|--- setting.gradle
|--- build.gradle
|--- my_flutter
|--- app
|--- build.gradle

提示找不到 include_flutter.groovy,发现是目录不正确。

建议将 my_flutter 跟 Android 根项目平级,而不是作为其子目录,如下:

1
2
3
4
5
6
7
some/path/
|--- my_flutter
|--- MyApp
|--- setting.gradle
|--- build.gradle
|--- app
|--- build.gradle

坑二: 加载 libflutter.so 失败

前面说到我在老项目中集成 Flutter 时遇到没将 libflutter.so 打包到 APK 的问题,网上找到不少解决办法:

我嫌以上方法都太麻烦,就用了一种简单粗暴且有效的办法:找到 libflutter.so 放在项目的 jniLibs/armeabi 目录。(当然,开发中确实有效,但实际上 Flutter 为 debug, release 以及 profile 三种模式提供不同版本的 libflutter.so,所以这种做法在发布时是不可取的)

另外我尝试将 armeabi 目录重命名为 armeabi-v7a 并将 abiFilters "armeabi" 修改 abiFilters "armeabi-v7a"也能保证将 libflutter.so 打包到 APK 中。

坑三 无法启动 Dart VM

解决上面的坑之后,debug 包不再报找不到 libflutter.so 了。但是遇到类似如下的错误:

1
2
3
4
5
2019-06-24 11:08:24.366 30834-30834/com.alarmnet.tc2 E/flutter: [ERROR:flutter/runtime/dart_vm_data.cc(19)] VM snapshot invalid and could not be inferred from settings.
2019-06-24 11:08:24.366 30834-30834/com.alarmnet.tc2 E/flutter: [ERROR:flutter/runtime/dart_vm.cc(241)] Could not setup VM data to bootstrap the VM from.
2019-06-24 11:08:24.366 30834-30834/com.alarmnet.tc2 E/flutter: [ERROR:flutter/runtime/dart_vm_lifecycle.cc(89)] Could not create Dart VM instance.
2019-06-24 11:08:24.366 30834-30834/com.alarmnet.tc2 A/flutter: [FATAL:flutter/shell/common/shell.cc(218)] Check failed: vm. Must be able to initialize the VM.
2019-06-24 11:08:24.368 30834-30834/com.alarmnet.tc2 A/libc: Fatal signal 6 (SIGABRT), code -6 in tid 30834 (om.alarmnet.tc2)

一头雾水。通过对比 Android 项目中集成 Flutter 模块生成的 debug APK,以及纯 Flutter 项目生成的 debug APK,发现前者中缺少 assets/flutter_assets

原来是因为打包时 assets/flutter_assets 丢失导致 dart 虚拟机无法启动。简单办法也简单,my_flutter 模块是可以直接编译的,它生成的中间文件中包含 flutter_assets,拷贝一份放在 MyApp/app 模块的 assets 中即可。

(注:assets/flutter_assets 丢失的根本原因是 flutter:copyFlutterAssetsDebug 任务没有正确运行,原因不详)

解决上面的坑之后,终于成功将 Flutter 集成到 debug APK 中并成功运行。

(注:虽然成功但很失望,因为 debug 模式下 Flutter 的性能明显太低。另外,手动拷贝 flutter_assets 目录在发布产品时是不可行的)


试着编个 release APK 包玩一下,又遇到另一个无法启动 Dart VM 的问题。

1
2
2019-08-16 18:22:46.093 32010-32010/? A/flutter: [FATAL:flutter/runtime/dart_vm.cc(380)] Error while initializing the Dart VM: Precompiled runtime requires a precompiled snapshot
2019-08-16 18:22:46.179 32092-32092/? A/DEBUG: Abort message: '[FATAL:flutter/runtime/dart_vm.cc(380)] Error while initializing the Dart VM: Precompiled runtime requires a precompiled snapshot

欲哭无泪。

(未完待续。问题还没解决,但怕自己拖延,所以还是先发出来)。