本文共 13156 字,大约阅读时间需要 43 分钟。
上一篇文章有优点和也有缺点:
优点
缺点
Flutter
并且都要安装Flutter
环境Flutter
项目和Native
项目Flutter
会直接侵入到Native
项目中去Android
混合Flutter
除了上面所说的源码集成方式还有没有其他方式呢?答案肯定是有的,那就是Flutter
以产物的方式集成到Native
,简而言之将开发的Flutter
项目单独编译成aar文件,然后以组件的形式被主工程(Native工程)依赖,aar
文件可以以maven方式(远程方式)的依赖,本文主要为了体验产物集成和源码集成方案对比,就先用本地依赖的方式来集成。
这里和源码集成不同的是在New Flutter Project
选择的是Flutter Application
而不是Flutter Module
:
项目结构具体如下:
把上一篇源码集成Flutter
项目的dart
文件拉到这个项目中,代码就不贴出来了,主要是根据路由去跳转不同的页面。
首先看android
目录下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
任务,简单的话用一下一张图描述:
大体结构如下:
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
依赖了mergeAssets
和flutterTask
,也就是当mergeAssets
(Android的assets处理完成后)和flutterTask
(flutter编译完)和执行完,Flutter
产物就会被copyFlutterAssetsTask
根据debug还是release复制到build/app/intermediates/merged_assets/debug/mergeDebugAssets/out
或者build/app/intermediates/merged_assets/release/mergeReleaseAssets/out
,Flutter
的编译产物,具体是在flutterTask
的getAssets
方法指定的:
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
构建代码:
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
模式下的产物:
可以发现lib文件下多了x86_64、x86、arm64-v8a文件并对应的so文件,并且少了isolate_snapshot_instr
和vm_snapshot_instr
,因为在debug
下只会执行一个命令flutter build bundle
,它会生成assets
、vm_snapshot_data
、isolate_snapshot_data
。release
模式下,会执行两个命令:flutter build aot
,flutter build bundle --precomiled
,Android
默认使用app-aot-blobs
,这种模式会生成isolate_snapshot_data
、isolate_snapshot_instr
、vm_snapshot_data
和vm_snapshot_instr
四个文件,多生成两个文件只要是为了执行速度更快。
3.1.assets文件夹
assets
文件夹有isolate_snapshot_instr
,flutter_assets
,vm_snapshot_data
,vm_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文件,Flutter
在Android
平台下会默认生成arm-v7
架构的so库,debug模式下同时生成x86_64、x86和arm64-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/"}复制代码
本文暂时还不需要用到这两步。
上面通过编译命令得到apk,那么如果想打包aar,只要把app/build.gradle
中的apply plugin: 'com.android.application'
改为apply plugin: 'com.android,library'
并且把applicationId "com.example.flutter_app"
注释
在android
目录下的AndroidManifest.xml
把android:label="xxx"
和android:name="xxxxx"
注释掉:
在Terminal
执行下面命令,就能得到app-release.aar
文件
首先创建完Android项目,将上面打包成功的aar文件
以普通的aar
集成到Android项目中去,首先将aar
文件拷贝到libs
目录下:
并且在app
模块下配置build.gradle
,对aar
文件的依赖
这时候你会发现没有Flutter
类和FlutterFragment
,在有提过,创建Flutter Module
的时候,在.android
->Flutter
->io.flutter
->facade
会生成两个java
文件,分别是Flutter
和FlutterFragment
:
下面把这两个文件复制过来:
最后Android
原生调用Flutter
方式:
@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; }复制代码
@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(); }复制代码
@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
没找到修改的地方,可以采用这两篇文章和的方法来实现。
如果想要以混编的方式来开发项目,可以自行根据这两种方案的特点来选择,下面附上两种方案的优缺点: