flutter项目作为组件集成到原Android项目中,官方提供的集成方式只是一个wiki,网上有其他的集成方式,其中
第一种是以.groovy方式在settings.gradle中添加依赖,如图:
这种依赖方式需要团队每个人都要安装flutter 环境,下载sdk等,否则无法编译项目,所以可以依赖jar/aar的方式来集成,也就是下面第二种方式集成。
第二种是把flutter项目打包成aar,然后拷贝到原Android项目中集成
由于本人采用第一种方式集成后,直接运行到手机正常,打包release失败,原因是Flutter官方只提供了四种CPU架构的SO库:armeabi-v7a、arm64-v8a、x86和x86-64,其中x86系列只支持Debug模式,但是公司原项目中大多SDK都只提供了armeabi架构的库(比如地图sdk和语音sdk等),这就导致了打包时缺少armeabi的libflutter.so库而无法运行flutrer模块,按照美团技术方案的so库兼容方案复制armeabi-v7a中的so到armeabi中,结果release包flutter模块报Check failed: vm. Must be able to initialize the VM.有时候还会其他莫名的错误。
今天尝试了第二种集成方式即打包aar并添加到Android项目中打包,最后成功运行flutter模块
看下我的flutter版本v1.7.8+hotfix.4
这是公司原项目的build.gradle,原项目中ndk只使用了armeabi下的ndk支持,没有采用armeabi-v7a的支持,而flutter的SDK中有偏偏没有armeabi的libflutter.so,所以需要自己复制armeabi-v7a中的libflutter.so如果项目采用了armeabi-v7a支持,那就不用复制libflutter.so这一步了,直接打aar集成就好。
1).打开flutter项目
在命令行输入打包命令 flutter build apk
会编译生成apk文件 位于 build/app/outputs/apk/release/app-release.apk
打开apk可以看到,里面lib目录中的armeabi-v7a中有libflutter.so和libapp.so,这里有armeabi文件是因为我操作了第2)步才会生成。
2) 解压apk包,复制lib目录下armeabi-v7a中的libflutter.so和libapp.so,在android/app/目录下创建libs/armeabi,然后将libflutter.so和libapp.so拷贝到armeabi的目录下,我这里还拷贝了libapp.so,刚开始没有拷贝这个libapp.so,导致我修改flutter代码后重新打aar,新修改的flutter代码没有生效,比较恶心的就是这一点,没次需要重新打aar都要拷贝最新的libapp.so
3)然后在gradle中配置
android{
sourceSets {
main {
jniLibs.srcDirs = ['libs']
}
}
}
4)在按第1)步重新打包apk就会看到第一步中的armeabi文件夹,里面有libflutter.so和libapp.so
5)也可以不按 第(2~4)部的从apk中复制这两个so,看着比较麻烦,还有一种是运行命令:
cd android
./gradlew assembleRelease
会在build/intermediates/library_and_local_jars_jni/release/armeabi-v7a/中生成libflutter.so和libapp.so,然后复制libflutter.so和libapp.so到build/intermediates/library_and_local_jars_jni/release/armeabi中
但是由于每次更新flutter代码重新编译后libapp.so都会不一样,此外flutter sdk版本升级也比较快,每个版本打出的so可能稍有不同,所以只要更新flutter代码或者升级sdk可能就需要拷贝so,比较麻烦,那么我通过定义一个Task监听打包aar的任务来进行自动拷贝,稍后我会贴出自定义拷贝so库的gradle代码。
上面通过编译命令得到了apk,如果要想打包aar,理论上只需要把
app/build.gradle中的apply plugin: 'com.android.application
改为apply plugin: 'com.android.library,
同时注释掉applicationId "com.ffl.my_flutter",
并且将清单文件修改为:
然后执行以下命令,就能得到app-release.aar
文件
flutter clean
cd android
./gradlew assembleRelease
解压app-release.aar文件就能看到jin/armeabi目录下有了libflutter.so和libapp.so
第一步最后说到,每次更新flutter代码或者升级flutterSDK都要拷贝libflutter.so和libapp.so到libs/armeabi中或者build/intermediates/library_and_local_jars_jni/release/armeabi中,作为一个追求完美的程序猿怎么能忍?所以解决方法是在build.gradle中写个Task,在打aar的时候让它自动拷贝so文件,废话不说,直接上代码:
在android/app下的build.gradle文件中配置以下代码
//以下任务为了拷贝so 因为Flutter默认只生成v7的so
Task copyFlutterSo = project.task('copyFlutterSo') {
doLast {
println("armeabi-v7a已生成,准备拷贝libflutter.so和libapp.so")
copy{
def buildDir = '/Users/用户名/Documents/wbg_flutter/my_flutter/build/app'
def dir = "${buildDir}/intermediates/library_and_local_jars_jni/release"
from "${dir}/armeabi-v7a/libflutter.so",
"${dir}/armeabi-v7a/libapp.so"
into "${dir}/armeabi/"
println("libflutter.so和libapp.so已复制到armeabi文件夹")
}
}
}
project.tasks.whenTaskAdded { Task task ->
if (task.name == 'transformNativeLibsWithSyncJniLibsForRelease') {
task.dependsOn(copyFlutterSo) // 生成JniLibs之后执行自定义task
}
}
运行命令:
cd android
./gradlew assembleRelease
不过遗憾的是,由于鄙人水平有限,写的task可能有缺陷,导致每次更新flutter代码或者执行clean操作后,要运行2次命令:./gradlew assembleRelease才会把so复制成功,生成的aar中才会有armeabi类型的so。
我猜原因可能是更新代码重新编译时,发现有改动的代码,导致拷贝好的armeabi目录被删除,重新生成library_and_local_jars_jni/release目录,因为拷贝操作是在transformNativeLibsWithSyncJniLibsForRelease任务后,assembleRelease的任务前,我想就算没有被删除,拷贝的libapp.so也不是最新的,因为编译完成重新生成了新的libapp.so.
build.gradle中加入测试代码:
gradle.taskGraph.afterTask { Task task, TaskState state ->
if (state.failure) {
println "FAILED"
}
else {
println "done"
}
}
执行生成aar命令:./gradlew assembleRelease,查看task执行顺序情况
可以看到transformNativeLibsWithSyncJniLibsForRelease执行完后后面还有其他命令,assembleRelease最后执行,我想就是在assembleRelease执行后又生成了新的library_and_local_jars_jni/release目录,这种只会在代码有变动或者clean后才会生成新的release目录。而第二次运行./gradlew assembleRelease检测到没有变动的代码,就不重新生成release目录,这时执行完拷贝任务,最后打出的aar中就包含了armeabi,这时我的理解,有更好办法的小伙伴麻烦告知一下,共同学习。
上述打包成功的aar就可以作为普通的aar集成到Android项目中了
1、拷贝aar到现有android项目中,拷贝到libs目录下
2、配置build.gradle
repositories {
flatDir { dirs 'libs' }
}
dependencies {
compile(name: 'app-release', ext: 'aar')
}
3、展示Flutter界面
其实就是模仿新建的flutter项目,在application中初始化,或者在FlutterMainActivity的onCreate中初始化
FlutterMain.startInitialization(this);
然后新建一个Activity继承FlutterActivity
public class FlutterMainActivity extends FlutterActivity {
//跳转该页面的时候可以传要跳转的页面,参数名固定为route
private static final String ROUTE_PAGE = "route";
public static Intent makeIntent(Context context, String routePage) {
if (routePage == null || routePage.equals("")) {
routePage = "/";
}
Intent intent = new Intent(context, FlutterMainActivity.class);
intent.setAction(Intent.ACTION_RUN);
intent.putExtra(ROUTE_PAGE, routePage);
return intent;
}
public void onCreate(@Nullable Bundle savedInstanceState) {
//初始化Flutter
FlutterMain.startInitialization(this);
super.onCreate(savedInstanceState);
GeneratedPluginRegistrant.registerWith(this);
}
}
打开这个activity就能运行到Flutter的main.dart中的main()方法了,可以看到我们还有传参,那么这个参数就可以在flutter里面接收到,我们可以根据这个参数的值来跳转对应的界面。
与原生交互方式跟第一种集成方式有一点点区别,就是FlutterView,由于FlutterMainActivity继承了FlutterActivity,所以可以直接通过getFlutterView()来获取FlutterView
根据定义的method名称调用native相应的api并回调
假如你的flutter工程依赖了三方的flutter plugin,那么打包aar没法把plugin内容也打进去。
这个时候就可以使用fat-aar-android来实现将第三方库的android代码打进aar
一、 flutter module 中
1 使用插件
apply plugin: 'com.kezong.fat-aar'
2 在dependencies 中添加
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) }
}
plugins.each { name, _ ->
println name
embed project(path: ":$name", configuration: 'default')
}
二、 project 中的 build.gradle 里添加
classpath 'com.kezong:fat-aar:1.0.3'
三、setting.gradle 中 添加
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) }
}
plugins.each { name, path ->
def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile()
include ":$name"
project(":$name").projectDir = pluginDirectory
}
复制代码