Flutter 打包aar并集成到Android项目

前言

flutter项目作为组件集成到原Android项目中,官方提供的集成方式只是一个wiki,网上有其他的集成方式,其中

第一种是以.groovy方式在settings.gradle中添加依赖,如图:

Flutter 打包aar并集成到Android项目_第1张图片

这种依赖方式需要团队每个人都要安装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模块

一、手动添加armeabi的so文件(libflutter.so)

看下我的flutter版本v1.7.8+hotfix.4

Flutter 打包aar并集成到Android项目_第2张图片

这是公司原项目的build.gradle,原项目中ndk只使用了armeabi下的ndk支持,没有采用armeabi-v7a的支持,而flutter的SDK中有偏偏没有armeabi的libflutter.so,所以需要自己复制armeabi-v7a中的libflutter.so如果项目采用了armeabi-v7a支持,那就不用复制libflutter.so这一步了,直接打aar集成就好。

Flutter 打包aar并集成到Android项目_第3张图片

 开始复制so

1).打开flutter项目

在命令行输入打包命令 flutter build apk

会编译生成apk文件 位于 build/app/outputs/apk/release/app-release.apk

打开apk可以看到,里面lib目录中的armeabi-v7a中有libflutter.so和libapp.so,这里有armeabi文件是因为我操作了第2)步才会生成。

Flutter 打包aar并集成到Android项目_第4张图片

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

Flutter 打包aar并集成到Android项目_第5张图片

3)然后在gradle中配置

android{
	sourceSets {
        main {
            jniLibs.srcDirs = ['libs']
        }
    }
}

4)在按第1)步重新打包apk就会看到第一步中的armeabi文件夹,里面有libflutter.so和libapp.so

Flutter 打包aar并集成到Android项目_第6张图片

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 打包aar并集成到Android项目_第7张图片

但是由于每次更新flutter代码重新编译后libapp.so都会不一样,此外flutter sdk版本升级也比较快,每个版本打出的so可能稍有不同,所以只要更新flutter代码或者升级sdk可能就需要拷贝so,比较麻烦,那么我通过定义一个Task监听打包aar的任务来进行自动拷贝,稍后我会贴出自定义拷贝so库的gradle代码。

二、打包aar

上面通过编译命令得到了apk,如果要想打包aar,理论上只需要把
app/build.gradle中的apply plugin: 'com.android.application
改为apply plugin: 'com.android.library,

Flutter 打包aar并集成到Android项目_第8张图片

Flutter 打包aar并集成到Android项目_第9张图片
同时注释掉applicationId "com.ffl.my_flutter",
并且将清单文件修改为:


然后执行以下命令,就能得到app-release.aar文件

flutter clean
cd android
./gradlew assembleRelease

解压app-release.aar文件就能看到jin/armeabi目录下有了libflutter.so和libapp.so

Flutter 打包aar并集成到Android项目_第10张图片

第一步最后说到,每次更新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,这时我的理解,有更好办法的小伙伴麻烦告知一下,共同学习。

三、集成到现有Android项目

上述打包成功的aar就可以作为普通的aar集成到Android项目中了

1、拷贝aar到现有android项目中,拷贝到libs目录下

Flutter 打包aar并集成到Android项目_第11张图片

2、配置build.gradle

repositories {
    flatDir { dirs 'libs' }
}

dependencies {
	compile(name: 'app-release', ext: 'aar')
}

3、展示Flutter界面

其实就是模仿新建的flutter项目,在application中初始化,或者在FlutterMainActivityonCreate中初始化

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
    }
 
复制代码

 

你可能感兴趣的:(Flutter)