博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Android 混合Flutter之产物集成方式
阅读量:4082 次
发布时间:2019-05-25

本文共 13156 字,大约阅读时间需要 43 分钟。

一、前言

上一篇文章有优点和也有缺点:

优点

  • 1.简单快捷,Google原生支持
  • 2.开发调试方便,和原生交互较多或需要依赖原生数据环境的时候特别能体现出来

缺点

  • 1.团队所有人都可能要会Flutter并且都要安装Flutter环境
  • 2.需要对现有的编译体系做出修改,也就是要同时编译Flutter项目和Native项目
  • 3.Flutter会直接侵入到Native项目中去
  • 4.编译速度慢

Android混合Flutter除了上面所说的源码集成方式还有没有其他方式呢?答案肯定是有的,那就是Flutter以产物的方式集成到Native,简而言之将开发的Flutter项目单独编译成aar文件,然后以组件的形式被主工程(Native工程)依赖,aar文件可以以maven方式(远程方式)的依赖,本文主要为了体验产物集成和源码集成方案对比,就先用本地依赖的方式来集成。

二、Flutter项目

1.编写Flutter项目代码

这里和源码集成不同的是在New Flutter Project选择的是Flutter Application而不是Flutter Module

 

创建Flutter项目

 

项目结构具体如下:

 

 

项目结构

 

把上一篇源码集成Flutter项目的dart文件拉到这个项目中,代码就不贴出来了,主要是根据路由去跳转不同的页面。

 

2.build.gradle

首先看android目录下build.gradle:

 

build.gradle

 

如果做过安卓领域的同学对这个文件很熟悉了,在原生安卓项目app模块下也有这个文件,这个文件是app模块的gradle构建脚本,一般用来管理app包名、版本号以及添加修改依赖库,在Flutter项目中,这个文件是由Flutter SDK生成的,相比原生安卓工程有些许不同,当然如果你根据gradle的知识体系来理解就行,下面看看这个文件:

 

//取得`local.properties`中的关于Flutter相关属性def localProperties = new Properties()def localPropertiesFile = rootProject.file('local.properties')if (localPropertiesFile.exists()) {    localPropertiesFile.withReader('UTF-8') { reader ->        localProperties.load(reader)    }}//获取flutter.sdk信息def flutterRoot = localProperties.getProperty('flutter.sdk')if (flutterRoot == null) {    throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")}//获取flutter.versionCodedef flutterVersionCode = localProperties.getProperty('flutter.versionCode')if (flutterVersionCode == null) {    flutterVersionCode = '1'}//获取flutter.versionNamedef flutterVersionName = localProperties.getProperty('flutter.versionName')if (flutterVersionName == null) {    flutterVersionName = '1.0'}//指定为应用程序模块apply plugin: 'com.android.application'//引用flutter.gradleapply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"android {    //编译版本    compileSdkVersion 28    //lint配置    lintOptions {        disable 'InvalidPackage'    }        //基本配置信息 包名,最低支持版本号 版本号 版本名字等    defaultConfig {        // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).        applicationId "com.example.flutter_app"        minSdkVersion 16        targetSdkVersion 28        versionCode flutterVersionCode.toInteger()        versionName flutterVersionName        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"    }    buildTypes {        release {            // TODO: Add your own signing config for the release build.            // Signing with the debug keys for now, so `flutter run --release` works.            //可以增加签名信息            signingConfig signingConfigs.debug        }    }}flutter {    source '../..'}dependencies {    testImplementation 'junit:junit:4.12'    androidTestImplementation 'com.android.support.test:runner:1.0.2'    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'}复制代码

apply from:"$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"这句话是引入flutter.gradle配置模块,可以这么理解向普通Android工程打包流程插入一些Flutter Task任务,简单的话用一下一张图描述:

 

gradle执行顺序

 

大体结构如下:

 

buildscript {    repositories {        google()        jcenter()    }    dependencies {        classpath 'com.android.tools.build:gradle:3.2.1'    }}android {    compileOptions {        sourceCompatibility 1.8        targetCompatibility 1.8    }}apply plugin:FlutterPluginclass FlutterPlugin implements Plugin
{...}class FlutterExtension {...}abstract FlutterTask extends BaseFlutterTask{...}gradle.useLogger(new FlutterEventLogger)class FlutterEventLogger extends BuildAdapter implements TaskExecutinListener{...}复制代码

flutter.gradle配置了一个名为FlutterPlugin的插件,这个插件实现了Plugin<Project>接口的apply方法,这是标准的gradle plugin,那么它肯定会定义一些task和必要的依赖,在addFlutterTask这个方法可以体现:

.....           // We know that the flutter app is a subproject in another Android           // app when these tasks exist.           //我们知道flutter应用程序是另一个安卓系统的子项目            Task packageAssets = project.tasks.findByPath(":flutter:package${variant.name.capitalize()}Assets")            Task cleanPackageAssets = project.tasks.findByPath(":flutter:cleanPackage${variant.name.capitalize()}Assets")            Task copyFlutterAssetsTask = project.tasks.create(name: "copyFlutterAssets${variant.name.capitalize()}", type: Copy) {                dependsOn compileTasks                if (packageAssets && cleanPackageAssets) {                    //挡在flutter模块中,存在cleanPackageAssets和packageAssets时                    dependsOn packageAssets                    dependsOn cleanPackageAssets                    into packageAssets.outputDir                } else {                    //依赖于mergeAssets任务                    dependsOn variant.mergeAssets                    //依赖于cleanAssets任务                    dependsOn "clean${variant.mergeAssets.name.capitalize()}"                    variant.mergeAssets.mustRunAfter("clean${variant.mergeAssets.name.capitalize()}")                    into variant.mergeAssets.outputDir                }                compileTasks.each { flutterTask ->                    //执行flutterTask的getAssets方法                    with flutterTask.assets                }            }            //processResource依赖于copyFlutterAssetsTask            variant.outputs.first().processResources.dependsOn(copyFlutterAssetsTask)复制代码

从上面源码可以看出processResource这个Task依赖于copyFlutterAssetsTask,意思是要先执行完copyFlutterAssetsTask才能执行processResource,看英文意思就把flutter相关Task加到gradle的编译流程中,另外copyFlutterAssetsTask依赖了mergeAssetsflutterTask,也就是当mergeAssets(Android的assets处理完成后)和flutterTask(flutter编译完)和执行完,Flutter产物就会被copyFlutterAssetsTask根据debug还是release复制到build/app/intermediates/merged_assets/debug/mergeDebugAssets/out或者build/app/intermediates/merged_assets/release/mergeReleaseAssets/outFlutter的编译产物,具体是在flutterTaskgetAssets方法指定的:

class FlutterTask extends BaseFlutterTask {    @OutputDirectory    File getOutputDirectory() {        return intermediateDir    }    CopySpec getAssets() {        return project.copySpec {            from "${intermediateDir}"            include "flutter_assets/**" // the working dir and its files        }    }    ......}复制代码

也就是说,这些产物就是build/app/intermediates/flutter/xxx(xxx指debug或者release)下面的flutter_assets/目录中的所有内容,那现在在命令行输入打包命令flutter build apk,会编译生成apk文件,路径位于build/app/outputs/apk/release/app-release.apk,注意如果你输入flutter build apk,实际默认打release包,也就是等价于flutter build --release,如果需要打debug包,可以输入flutter build apk --debug:

 

Flutter_build_apk

 

 

 

Flutter产物分析

 

可以生成很多产物,这些产物都是来自Flutter构建代码:

 

final String assembly = fs.path.join(outputDir.path, 'snapshot_assembly.S');                                              if (buildSharedLibrary || platform == TargetPlatform.ios) {                                                                 // Assembly AOT snapshot.                                                                                                 outputPaths.add(assembly);                                                                                                genSnapshotArgs.add('--snapshot_kind=app-aot-assembly');                                                                  genSnapshotArgs.add('--assembly=$assembly');                                                                            } else {                                                                                                                    // Blob AOT snapshot.                                                              final String vmSnapshotData = fs.path.join(outputDir.path, 'vm_snapshot_data');                                           final String isolateSnapshotData = fs.path.join(outputDir.path, 'isolate_snapshot_data');                                 final String vmSnapshotInstructions = fs.path.join(outputDir.path, 'vm_snapshot_instr');                                  final String isolateSnapshotInstructions = fs.path.join(outputDir.path, 'isolate_snapshot_instr');                        outputPaths.addAll(
[vmSnapshotData, isolateSnapshotData, vmSnapshotInstructions, isolateSnapshotInstructions]); genSnapshotArgs.addAll(
[ '--snapshot_kind=app-aot-blobs', '--vm_snapshot_data=$vmSnapshotData', '--isolate_snapshot_data=$isolateSnapshotData', '--vm_snapshot_instructions=$vmSnapshotInstructions', 复制代码

看看debug模式下的产物:

 

Flutter下debug下的产物

 

可以发现lib文件下多了x86_64x86arm64-v8a文件并对应的so文件,并且少了isolate_snapshot_instrvm_snapshot_instr,因为在debug下只会执行一个命令flutter build bundle,它会生成assetsvm_snapshot_dataisolate_snapshot_datarelease模式下,会执行两个命令:flutter build aotflutter build bundle --precomiledAndroid默认使用app-aot-blobs,这种模式会生成isolate_snapshot_dataisolate_snapshot_instrvm_snapshot_datavm_snapshot_instr四个文件,多生成两个文件只要是为了执行速度更快。

 

3.产物分析

3.1.assets文件夹

assets文件夹有isolate_snapshot_instrflutter_assetsvm_snapshot_datavm_snapshot_instr

  • flutter_assets:是Flutter工程产生的assets文件,包含字体文件,协议等。
  • isolate_snapshot_instr:包含由Dart isolate执行的AOT代码。
  • isolate_snapshot_data:表示isolates堆存储区的初始状态和特定的信息,和vm_snapshot_data配合,更快启动Dart_VM。
  • vm_snapshot_data:表示isolates之间共享的Dart堆存储区的初始状态,用于更快的启动Dart VM。
  • vm_snapshot_instr:包含VM中所有的isolates之间的常见例程指令。

3.2.lib文件夹

lib文件夹是特定平台(arm或者x86)的so文件,FlutterAndroid平台下会默认生成arm-v7架构的so库,debug模式下同时生成x86_64x86arm64-v8a的so文件,当然有的项目可能配置了

ndk{    abiFilters 'armeabi'}复制代码

为了解决so对其问题,需要在Flutter项目中手动armeabi的so文件,这样的话打包出来就aar包含了armeabi的so文件,这个armeabi的so文件可以拷贝armeabi-v7下面的,一般情况下他们两个是没什么区别,在app目录下创建libs/armeabi,然后将libflutter.so拷贝到armeabi的目录下,然后在gradle中配置

android{	sourceSets {        main {            jniLibs.srcDirs = ['libs']        }    }}复制代码

因为Flutter SDK版本速度很快,每个版本打出的so文件可能稍有不同,所有只要升级sdk可能就需要拷贝so文件,比较麻烦,所以可以监听打包aar的任务来进行自动拷贝,在gradle文件中配置以下代码

//以下任务为了拷贝so  因为Flutter默认只生成v7的sotask copyFlutterSo(dependsOn: 'transformNativeLibsWithSyncJniLibsForRelease', type: Copy) {    //${buildDir} =  /Users/xueshanshan/project/flutter/flutter_debug_library/build/app    def dir = "${buildDir}/intermediates/library_and_local_jars_jni/release"    from "${dir}/armeabi-v7a/libflutter.so"    into "${dir}/armeabi/"}复制代码

本文暂时还不需要用到这两步。

4.打包aar文件

上面通过编译命令得到apk,那么如果想打包aar,只要把app/build.gradle中的apply plugin: 'com.android.application'改为apply plugin: 'com.android,library' 并且把applicationId "com.example.flutter_app"注释

 

打包aar文件一

 

android目录下的AndroidManifest.xmlandroid:label="xxx"android:name="xxxxx"注释掉:

 

 

AndroidMenifest.xml文件配置

 

Terminal执行下面命令,就能得到app-release.aar文件

 

  • flutter clean
  • cd android
  • ./gradlew assembleRelease

 

得到aar文件

 

 

三、Android项目

首先创建完Android项目,将上面打包成功的aar文件以普通的aar集成到Android项目中去,首先将aar文件拷贝到libs目录下:

 

添加aar文件

 

并且在app模块下配置build.gradle,对aar文件的依赖

 

 

添加aar文件依赖

 

这时候你会发现没有Flutter类和FlutterFragment,在有提过,创建Flutter Module的时候,在.android->Flutter->io.flutter->facade会生成两个java文件,分别是FlutterFlutterFragment

 

  • Flutter:Android应用程序中使用Flutter的主要入口点
  • FlutterFragment:Fragment来管理FlutterView

下面把这两个文件复制过来:

 

添加Flutter和FlutterFragment

 

最后Android原生调用Flutter方式:

 

  • 继承FlutterActivity
@Override    public FlutterView createFlutterView(Context context){        getIntentData();        WindowManager.LayoutParams matchParent = new WindowManager.LayoutParams(-1, -1);        //创建FlutterNativeView        FlutterNativeView nativeView = this.createFlutterNativeView();        //创建FlutterView        FlutterView flutterView = new FlutterView(FlutterMainActivity.this,(AttributeSet)null,nativeView);        //给FlutterView传递路由参数        flutterView.setInitialRoute(routeStr);        //FlutterView设置布局参数        flutterView.setLayoutParams(matchParent);        //将FlutterView设置进ContentView中,设置内容视图        this.setContentView(flutterView);        return flutterView;    }复制代码
  • 继承AppCompatActivity
@Override    protected void onCreate(@Nullable Bundle savedInstanceStae){        super.onCreate(savedInstanceStae);        String route = getIntent().getStringExtra("_route_");        String params = getIntent().getStringExtra("_params_");        JSONObject jsonObject = new JSONObject();        try{            jsonObject.put("pageParams",params);        } catch(JSONException e){            e.printStackTrace();        }        //将FlutterView设置进ContentView中,设置内容视图        //创建FlutterView        flutterView = Flutter.createView(this,getLifecycle(),route + "?" + jsonObject.toString());        //设置显示视图        setContentView(flutterView);        //插件注册        registerMethodChannel();    }复制代码
  • Fragment方式:
@Nullable    @Override    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState){        Log.d(TAG,"onCreateView-mRoute:"+mRoute);        mFlutterView = Flutter.createView(getActivity(),getLifecycle(),mRoute);        //综合解决闪屏,布局覆盖问题        mFlutterView.setZOrderOnTop(true);        mFlutterView.setZOrderMediaOverlay(false);        mFlutterView.getHolder().setFormat(Color.parseColor("#00000000"));        //注册channel       // GeneratedPluginRegistrant.registerWith(mFlutterView.getPluginRegistry());        //返回FlutterView        return mFlutterView;    }复制代码

实际效果如下图:

 

最终效果图

 

 

注意 这里会牵扯到如果Flutter工程依赖了第三方的Flutter plugin那么打包aar文件的时候是无法把Plugin内容打进去的,网上有文章说可以用或者,找遍gradle没找到修改的地方,可以采用这两篇文章和的方法来实现。

四、总结

如果想要以混编的方式来开发项目,可以自行根据这两种方案的特点来选择,下面附上两种方案的优缺点:

 

优缺点对比

 

 

五、参考资料

六、源码案例

作者:真丶深红骑士
链接:https://juejin.cn/post/6844903929868713992
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

你可能感兴趣的文章
React Native(一):搭建开发环境、出Hello World
查看>>
Winform多线程
查看>>
Spring AOP + Redis + 注解实现redis 分布式锁
查看>>
poj 1976 A Mini Locomotive (dp 二维01背包)
查看>>
大数据入门:Zookeeper结构体系
查看>>
大数据入门:Spark RDD基础概念
查看>>
大数据入门:Scala函数式编程
查看>>
C++报错:C4700:使用了非初始化的局部变量
查看>>
C++类、结构体、函数、变量等命名规则详解
查看>>
C++ goto语句详解
查看>>
【数据结构周周练】008 二叉树的链式创建及测试
查看>>
《软件体系结构》 第九章 软件体系结构评估
查看>>
《软件过程管理》 第六章 软件过程的项目管理
查看>>
《软件过程管理》 第九章 软件过程的评估和改进
查看>>
《数据库系统概论》 第三章 关系数据库标准语言SQL
查看>>
《计算机网络》第五章 运输层 ——TCP和UDP 可靠传输原理 TCP流量控制 拥塞控制 连接管理
查看>>
堆排序完整版,含注释
查看>>
二叉树深度优先遍历和广度优先遍历
查看>>
生产者消费者模型,循环队列实现
查看>>
IA32时钟周期的一些内容
查看>>