To Be An Artist Engineer.

0%

Android Native与Flutter混合开发探究

摘要

Flutter作为Google推出的新一代跨平台开发框架,具有热启动调试、速度快等优势,可以把原来需要Android和iOS两端人员的工作量压缩到一端,从而大大提高开发效率。
但是超级账号作为一款已经上线的成熟应用,引入Flutter面临混合开发的技术难题。目前混合开发有两种方案:

Google官方方案:

wiki地址:https://github.com/flutter/flutter/wiki/Add-Flutter-to-existing-apps

主要过程如下:

  • 在项目的父目录调用flutter create -t module “#modulename”创建flutter module项目

  • 在flutter module中进行flutter模块的开发

  • 在主项目中依赖flutter module,最终打包上线

该方案要求native端强制依赖flutter subproject, 侵入性较强,但是整合比较简单。

闲鱼方案:

如图:

这种方案是把flutter模块抽成一个独立的library并发布到远程maven, native端开发人员可以直接依赖,从而做到解耦。
闲鱼的技术人员没有采用官方推荐的打包方案而是自己创建了一套打包流程:

Flutter

  • dart的release编译方式为aot,会生成手机可运行的ARM代码,对应的文件为icudtl.dat、isolate_snapshot_data、isolate_snapshot_instr、vm_snapshot_data、vm_snapshot_instr。
  • flutter_assets即flutter项目中引用的字体和图片等资源
  • flutter_jar,包含lib_flutter.so和dart vm等

Flutter plugin

flutter的包依赖,如image_picker、fluttertoast等,这些依赖默认不会打到flutter library中,必须手动编译生成aar

TL.DR

闲鱼是采用脚本自定义打包内容,但是把apk反编译后发现gradle默认生成的aar也可以满足需要(多了一些无用的资源文件)。在此基础上为了方便flutter和native开发的自由切换,这里加入了组件化的一些特性:

  • 添加一键切换library和application的开关,在开发flutter阶段采用application的形式,从而充分利用flutter热重启的优点。而在集成阶段则切换成library, 把flutter和flutter plugins的依赖导入到项目中即可。

在根目录的gradle.properties文件中加入开关:

1
isModule = false

在app/build.gradle中进行适配:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
if (isModule.toBoolean()) {
apply plugin: 'com.android.library'
} else {
apply plugin: 'com.android.application'
}

android{
...
sourceSets {
main {
if (isModule.toBoolean()) {
manifest.srcFile 'src/main/module/AndroidManifest.xml'
} else {
manifest.srcFile 'src/main/AndroidManifest.xml'
}
}
}
}

打包使用自定义task,包含编译项目和aar包的收集:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
task buildDebug(type: Copy, dependsOn: "assembleDebug") {
def flutterProjectRoot = rootProject.projectDir.parentFile.toPath()

def plugins = new Properties()
def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins')
if (pluginsFile.exists()) {
pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) }
}

from("../../build/app/outputs/aar") {
include "app-debug.aar"
rename "app-debug.aar", "${rootProject.projectDir.parentFile.name}-debug.aar"
}
plugins.each { name, path ->
from("../../build/$name/outputs/aar") {
include "$name-debug.aar"
}
}
if(outputDir.isEmpty()){
into './libs'
}else{
into outputDir
}
}
  • Fat aar模式
    闲鱼模式对Flutter和Flutter plugin是分别依赖的,这样可能会有很多aar包,后续可以考虑aar的合并。目前这块没有官方支持,而三方库有一些缺陷(gradle版本限制,api过时等),所以这块暂不考虑

注意事项

  • flutter在打包时会丢弃icudtl.dat文件,这是flutter用于处理text的一个底层库,所以运行时会发生”ICUContext null”错误,解决办法就是在flutter的android目录下手动把apk里的flutter_shared/打包icudtl.da文件拷贝到src/main/assets/目录下,这样打包就不会出错了。

  • 在应用启动时需要手动初始化Flutter运行时环境, 否则在跳到flutter页面时会报“NOT INITIALIZED”异常

  • Flutter release模式下只支持armV7架构,只有在debug模式下才支持x86和x86_64(暂时), 但是主工程默认支持所有abi模式(除了mips和mips64),所以如果在诸如x86架构上的手机上打开flutter页面可能导致崩溃。但是考虑到x86目前占有市场份额不足1%的现状,这个问题影响不是很大。

  • 除了依赖Flutter工程还需要把flutter依赖的三方库一并导入主项目,否则三方库的运行可能出现问题。

参考

https://zhuanlan.zhihu.com/p/40528502(闲鱼flutter混合工程持续集成的最佳实践)
https://flutter.io
https://github.com/flutter/flutter/wiki/Add-Flutter-to-existing-apps(flutter wiki)