把flutter项目作为aar添加到已有的Android工程上

农村四月闲人少,勤学苦攻把名扬。这篇文章主要讲述把flutter项目作为aar添加到已有的Android工程上相关的知识,希望能为你提供帮助。
对于已有工程想要尝鲜 Flutter, 很多公司给出了最佳实践方案, android 中是使用 aar 加入项目中, 这样原生开发对于 flutter 环境就没有要求了, 只要 flutter 打包后上传 maven 即可, 但是这部分的过程坑很多, 后面我会再补充这种方案
我也摸索了一个实践方案, 将所有项目的 aar 由 flutter 方打包 aar 后将 aar 置入某一个固定位置 ,并置入一个 git 库管理, 然后 android 原生方直接 pull 后引入项目即可
高能预警: 本篇会结合 flutter, android, aar, gradle, maven, docker 的知识来完成所有的步骤
并不是每一个都会详细说明, 如果有不明白的可以在  https://www.kikt.top  的本文下面留言, 我会更新文章或给予解答, 其他渠道的可能不会有时间看

  • 开发环境
    • 本人设备环境
    • 预计需要的环境
  • flutter
    • 创建 flutter module
  • maven 的处理方式(看看就行,作为错误尝试的步骤)
    • 配置
    • 运行
    • 上传 aar
      • 使用 gradle 上传 aar
  • Android 项目(host)
    • 新建项目
    • 引入 maven 依赖
      • 添加仓库
    • 编码
    • 运行项目
    • 在 flutter 中添加带有原生功能的库
      • 在 flutter 中添加库
      • 上传新版本的 aar
  • 思考解决方案
    • fat-aar
    • flutter 的插件库上传至 maven
  • 解决方案-使用 aar 和 git 管理
    • 处理 flutter 端
    • 新建一个目录用于存放 aar
    • 修改 android 主工程
  • 总结一下所有修改
    • dart 脚本
    • 原生部分修改
    • 运行脚本
  • 后记
开发环境 本人设备环境
MacOS 10.13.6 (17G65)
flutter: Flutter 1.5.4-hotfix.2 • channel stable
2019-10-25 更新说明: 这篇文章因为发布时效的原因, 当时还没有 `$ flutter build aar` 这个命令 所以本人并没有实测两个东西的优劣性

Bash预计需要的环境
xcode android sdk gradle android studio flutter sdk docker # 这个

Bash这些环境我默认你都有, 没有的话本篇不讲
windows 用户? 对不住, 自己找寻其中的差别吧…
flutter 创建 flutter module
使用命令行创建:
$ flutter create -t module flutter_module
cd flutter_module flutter build apk

Bash这里理论上会生成一个 aar
tree .android/Flutter/build/outputs .android/Flutter/build/outputs ├── aar │└── flutter-release.aar └── logs └── manifest-merger-release-report.txt

Bash嗯,就这个东西
我们其实可以直接把这个 aar 放在宿主中,然后通过配置 aar 本地引用来直接使用这个工程, 但是这样可能并不利于持续集成
所以我们要用到 maven 这个利器
ps: 这里有个坑, 就是纯 flutter 项目可以, 但是如果你的 flutter 项目包含了对于第三方项目的依赖, 则 aar 可能不会包含其他的内容, 我们放在最后面再想办法解决
maven 的处理方式(看看就行,作为错误尝试的步骤)本篇主要讲的是 maven 的方式, 没有原生 plugin 的很简单, 但是有原生 plugin 的 flutter 步骤过于复杂, 最终没实现, 当然理论上肯定是可以实现的
因为本篇讲解的是本人解决 flutter 附着到已有工程的尝试,所以将放弃的过程也记录下来,  如果你只是想看最终的实现方案可以跳过本篇和后续所有涉及到 maven 的步骤
maven 是一个包管理工具
如果你公司有自己的私服, 则跳过这一章直接看下一章, 我这里只是使用 docker 创建一个 maven 私服环境
使用的镜像是 sonatype/nexus3
配置
可选:  $ docker pull sonatype/nexus3
我比较熟悉的有两种方式:
命令行直接运行
docker run --name test_nexus -d -p 8099:8081 -v /Volumes/Evo512/docker/nexus/nexus-data:/nexus-data sonatype/nexus3

Bash
使用 docker-compose
version: ‘2‘services: my-nexus: image: sonatype/nexus3 ports: - 8099:8081 networks: - nexus-net volumes: - /Volumes/Evo512/docker/nexus/nexus-data:/nexus-datanetworks: nexus-net: driver: bridge

YAML
docker-compose -d up

Bash使用 docker-compose 就是类似于配置文件的方式
运行
在浏览器打开 http://localhost:8099
登录的用户名密码,默认是 admin admin123
点开 maven, 毛也没有
把flutter项目作为aar添加到已有的Android工程上

文章图片

上传 aar
使用 gradle 上传 aar使用 android studio 打开 flutter_module 下的.android 目录, 经过一顿同步得到的可能是这样的:
把flutter项目作为aar添加到已有的Android工程上

文章图片
一片空白毛都没有…
这时候请 close, 重新打开, 现在是这个鬼样子的
把flutter项目作为aar添加到已有的Android工程上

文章图片

采用 project 视图模式
把flutter项目作为aar添加到已有的Android工程上

文章图片

在.android 下增加一个 gradle 文件,名字自取
比如我的就叫 update_aar.gradle
apply plugin: ‘maven‘def GROUP = ‘top.kikt.flutter_lib‘ def ARTIFACT_ID = ‘module_example‘ def VERSION_NAME = "1.0.0"def SNAPSHOT_REPOSITORY_URL = ‘http://localhost:8099/repository/maven-snapshots/‘ def RELEASE_REPOSITORY_URL = ‘http://localhost:8099/repository/maven-releases/‘ def REPOSITORY_URL = VERSION_NAME.toUpperCase().endsWith("-SNAPSHOT") ? SNAPSHOT_REPOSITORY_URL : RELEASE_REPOSITORY_URLdef NEXUS_USERNAME = ‘admin‘ def NEXUS_PASSWORD = ‘admin123‘afterEvaluate { project -> uploadArchives { repositories { mavenDeployer { pom.groupId = GROUP pom.artifactId = ARTIFACT_ID pom.version = VERSION_NAME repository(url: REPOSITORY_URL) { authentication(userName: NEXUS_USERNAME, password: NEXUS_PASSWORD) } } } } task androidjavadocs(type: Javadoc) { source = android.sourceSets.main.java.srcDirs classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) } task androidJavadocsJar(type: Jar, dependsOn: androidJavadocs) { classifier = ‘javadoc‘ from androidJavadocs.destinationDir } task androidSourcesJar(type: Jar) { classifier = ‘sources‘ from android.sourceSets.main.java.sourceFiles }//解决 JavaDoc 中文注释生成失败的问题 tasks.withType(Javadoc) { options.addStringOption(‘Xdoclint:none‘, ‘-quiet‘) options.addStringOption(‘encoding‘, ‘UTF-8‘) options.addStringOption(‘charSet‘, ‘UTF-8‘) } artifacts { archives androidSourcesJar archives androidJavadocsJar } }

Groovy这个文件呢, 就是上传用的 gradle 文件, 来源于网络
前几个 def 要根据你的 maven 来修改, 包名, 端口, 用户名,密码
接着引入 gradle 文件到项目中
【把flutter项目作为aar添加到已有的Android工程上】修改:  Flutter/build.gradle
android{ /// .... }apply from: "${rootDir.path}/update_aar.gradle"

Groovy按照下图点击
把flutter项目作为aar添加到已有的Android工程上

文章图片

可能会报错
11:58:23: Executing task ‘uploadArchives‘...Executing tasks: [uploadArchives]FAILURE: Build failed with an exception.* Where: Settings file ‘/Volumes/Evo512/code/flutter/add_to_exists_android/flutter_module/.android/settings.gradle‘ line: 7* What went wrong: A problem occurred evaluating settings ‘android_generated‘. > /Volumes/Evo512/code/flutter/add_to_exists_android/flutter_module/.android/Flutter/include_flutter.groovy (/Volumes/Evo512/code/flutter/add_to_exists_android/flutter_module/.android/Flutter/include_flutter.groovy)* Try: Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output. Run with --scan to get full insights.* Get more help at https://help.gradle.orgBUILD FAILED in 0s 11:58:23: Task execution finished ‘uploadArchives‘.

Bash似乎是由于路径不对的原因, 请使用如下的方式修改 setting.gradle:
// Generated file. Do not edit. include ‘:app‘rootProject.name = ‘android_generated‘ setBinding(new Binding([gradle: this])) //evaluate(new File(‘include_flutter.groovy‘)) evaluate(new File("$rootDir.path/include_flutter.groovy"))

Groovy同步 gradle 后
接着双击
把flutter项目作为aar添加到已有的Android工程上

文章图片

就可以上传成功了
然后打开 nexus 查看: http://localhost:8099/#browse/search/maven
把flutter项目作为aar添加到已有的Android工程上

文章图片

把flutter项目作为aar添加到已有的Android工程上

文章图片
有显示, 说明这个 aar 上传是成功的
后面再上传更改版本号即可
Android 项目(host) 新建项目
把flutter项目作为aar添加到已有的Android工程上

文章图片

引入 maven 依赖
添加仓库根目录 build.gradle, 根据节点增加一个 maven 仓库:
allprojects { repositories { google() jcenter() maven { url ‘http://localhost:8099/repository/maven-releases/‘ } } }

Groovy引入库, 在 nexus 的管理界面里可以查看引用方式:
把flutter项目作为aar添加到已有的Android工程上

文章图片

接着在app/build.gradle中修改
dependencies { // ... implementation ‘top.kikt.flutter_lib:module_example:1.0.0‘ }

Groovy经过 sync 以后,使用 project 视图, 可以找到这个库:
把flutter项目作为aar添加到已有的Android工程上

文章图片

编码
新建 MyFlutterActivity.java
package top.kikit.androidhost; import android.os.Bundle; import io.flutter.app.FlutterActivity; import io.flutter.plugins.GeneratedPluginRegistrant; /// create 2019-06-14 by caipublic class MyFlutterActivity extends FlutterActivity {@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); GeneratedPluginRegistrant.registerWith(this); } }

Java添加到清单文件
< application> < activity android:name=".MyFlutterActivity" /> < /application>

XML修改 MainActivity.java
package top.kikit.androidhost; import android.content.Intent; import android.os.Bundle; import androidx.appcompat.app.AppCompatActivity; public class MainActivity extends AppCompatActivity {@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Flutter.startInitialization(this.getApplicationContext()); Intent intent = new Intent(this, MyFlutterActivity.class); startActivity(intent); } }

Dart这里模拟一进来直接进 FlutterActivity 的场景
建议你的 Android 同事在合适的时机调用  Flutter.startInitialization(this.getApplicationContext());   这个是官方给出的初始化 flutter 引擎的代码, 否则首屏可能会慢
运行项目
初次运行可能会报错 提示一个 androidO 什么的玩意
两种方案
  1. minSDK 修改为 26, 这个简直不科学
  2. 在 app/build.gradle 下的 android 节点下增加这个代码
android{ compileOptions { sourceCompatibility 1.8 targetCompatibility 1.8 } }

Groovy将源码和目标代码等级都设置为 1.8
嗯 这里插一句, 我的 host 使用的是 androidX, 而 flutter 使用的是 android.support, 所以需要按照 androidX 的迁移流程修改一下, 如果你新建项目的时候勾选了 androidX, 则这里应该不用修改
androidX 的问题可以查看我的另一篇文章, 虽然是 flutter 分类下的,但是对于普通 android 工程也适用
运行结果如下:
把flutter项目作为aar添加到已有的Android工程上

文章图片

在 flutter 中添加带有原生功能的库
这里注意!!!!!!, 请先备份前面几个文件
这里注意!!!!!!, 请先备份前面几个文件
这里注意!!!!!!, 请先备份前面几个文件
这里注意!!!!!!, 请先备份前面几个文件
这里注意!!!!!!, 请先备份前面几个文件
这里注意!!!!!!, 请先备份前面几个文件
这里注意!!!!!!, 请先备份前面几个文件
这里注意!!!!!!, 请先备份前面几个文件
因为一旦 flutter packages get, 则 前面的文件就木有了
在 flutter 中添加库这里简单举例一下, 使用一个比较常用的shared_preferences
修改 flutter 的 yaml 文件
dependencies: shared_preferences: ^0.5.3+1

YAML$ flutter packages get
这一步后, 之前的那几个文件没有了…
建议: 把 build.gradle 和 setting.gradle 复制到 module 级别的某个目录下, 比如叫 template
然后用脚本来做这个上传的事情
  1. 复制模板到对应目录
  2. 通过环境变量设置 aar 的版本号
  3. 使用 gradle 命令来完成插件的调用
上传新版本的 aar修改版本号为 1.0.1
这里上传成功了
到 android host 中用了一下, 果不其然和网上的朋友们说的一样报错了
ERROR: Unable to resolve dependency for ‘:app@debug/compileClasspath‘: Could not resolve io.flutter.plugins.sharedpreferences:shared_preferences:1.0-SNAPSHOT. Show Details Affected Modules: appERROR: Unable to resolve dependency for ‘:app@debugAndroidTest/compileClasspath‘: Could not resolve io.flutter.plugins.sharedpreferences:shared_preferences:1.0-SNAPSHOT. Show Details Affected Modules: appERROR: Unable to resolve dependency for ‘:app@debugUnitTest/compileClasspath‘: Could not resolve io.flutter.plugins.sharedpreferences:shared_preferences:1.0-SNAPSHOT. Show Details Affected Modules: app

Bash查看对应的 pom.xml(我这里是 1.0.2),道理是一样的
< project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> < modelVersion> 4.0.0< /modelVersion> < groupId> top.kikt.flutter_lib< /groupId> < artifactId> module_example< /artifactId> < version> 1.0.2< /version> < packaging> aar< /packaging> < dependencies> < dependency> < groupId> io.flutter.plugins.sharedpreferences< /groupId> < artifactId> shared_preferences< /artifactId> < version> 1.0-SNAPSHOT< /version> < scope> compile< /scope> < /dependency> < dependency> < groupId> com.android.support< /groupId> < artifactId> support-v13< /artifactId> < version> 27.1.1< /version> < scope> compile< /scope> < /dependency> < dependency> < groupId> com.android.support< /groupId> < artifactId> support-annotations< /artifactId> < version> 27.1.1< /version> < scope> compile< /scope> < /dependency> < /dependencies> < /project>

XML这里有一个 io.flutter.plugins.sharedpreferences 就是报错的元凶了
思考解决方案看到这里我感觉有如下的方案
  1. 将所有文件打包到同一个 aar 库中, 然后再上传(也就是网上那个 fat-aar 的方案)
  2. 修改 flutter 打包脚本, 然后将中间的三方库产物(sp 插件)上传至私服 maven, flutter 项目使用 api 的方式依赖这些库, 完成 host=> flutter=> other plugin 的目的
  3. 不用 maven, 只用 aar
个人第一感觉, 觉得第一个实施起来可能会简单一些, 先尝试一下
fat-aar
这个找到了两个项目:
一个 gradle 文件的方式:  https://github.com/adwiv/android-fat-aar
一个是 plugin 的方式:  https://github.com/Vigi0303/fat-aar-plugin
但是都要用到一个类似embed这样的关键字来替换 compile(api/implementation), 无奈找遍 gradle 没找到修改的地方, 只能暂时放弃
flutter 的插件库上传至 maven
这个初始来看很可行.. 但仔细一想, 因为那个版本号的作祟, 需要改动的地方不算很少
每个插件包内的 gradle 文件都需要修改:
  1. 修改 version 版本号,这个应该是可以通过 环境变量/gradle 命令 来指定为佳, 不能指定的话理论上和 pub 的版本号相同也可以, 如果是 git 依赖, 就用 ref, path 依赖就很比较难自动取了
  2. 上传脚本,这个要读取上面的版本号, 还要读取一个
为什么要修改版本号呢? flutter 依赖的插件的版本号会被带到 aar 对应的 maven 库中的 pom.xml 文件中
这里要插一句: pom.xml 中依赖的版本号是定义在每个插件自己的 build.gradle 中的,如下面的连接那样
如下所示:  https://github.com/OpenFlutter/flutter_image_compress/blob/e841181d16df44b94c45e77ee1dcd36ebdc27905/android/build.gradle#L1-L2
https://github.com/flutter/plugins/blob/e9766e668b4a84ac526414e26981a23c661aff18/packages/shared_preferences/android/build.gradle#L14-L15
我这里说需要修改的就是这个版本号,否则你上传 maven 的 flutter 库的版本号和插件的 maven 版本号没对上的话,依然会报错
修改版本号并上传需要遵循如下的步骤:
  1. 读取本地.flutter-plugins文件的内容,将其中的版本号字段取出来
  2. 找到插件文件夹,替换掉版本号字段的内容
  3. 将上传插件的脚本复制至对应文件夹,并将版本号,group 名与插件统一
  4. 启动上传脚本
  5. 将对原生文件的修改内容还原
为什么要做最后一步呢? 这种"从远端"镜像下来的东西,修改回去是一个好习惯, 因为修改了会破坏仓库本身版本的完整性
解决方案-使用 aar 和 git 管理这个就是我开篇说的解决方案, 不使用 maven, 只是打包出 aar, 集中起来, 置入 git 仓库,如果有必要就打 tag 后 push 到远端, 方便根据版本来引用
然后作为 android 原生方, 在 project 的 gradle 中引入 aar 库即可, 当然如果你是大公司有自己的要求, 还是用上一种比较好
git 和 aar 引入也是很成熟的使用方案了, 无非就是如何拼接而已的问题, 何况这一步还可以通过 gradle 自动完成
处理 flutter 端
这次使用 dart 来作为脚本, 毕竟 dart 语言对于 flutter 开发者来说会很熟悉, 当然这一步可以用任何你熟悉的方式,比如: shell/python 等等, 这一步的执行需要将 dart 放入环境变量中
build_module.dart:
import ‘dart:io‘; var outputDir = Directory("../output"); var targetDir = Directory("../../flutter-aar"); Future main() async { List< AAR> list = []; outputDir.deleteSync(recursive: true); outputDir.createSync(recursive: true); var file = File("../.flutter-plugins"); var plugins = file.readAsLinesSync(); for (var value in plugins) { if (value.trim().isEmpty) { continue; } var splitArr = value.split("="); var name = splitArr[0]; var path = splitArr[1]; var aar = handlePlugin(name, path); list.add(aar); }var aar = await handleFlutter(); list.add(aar); handleAAR(list); }void handleAAR(List< AAR> list) { targetDir.deleteSync(recursive: true); targetDir.createSync(); list.forEach((aar) { var targetPath = "${targetDir.path}/${aar.aarName}"; var targetFile = aar.file.copySync(targetPath); print( ‘ copy "${aar.file.absolute.path}" to "${targetFile.absolute.path}"‘); }); }AAR handlePlugin(String name, String path) { var result = Process.runSync("./gradlew", ["$name:assRel"], workingDirectory: "../.android"); print(result.stdout); var aarFile = File("$path/android/build/outputs/aar/$name-release.aar"); var aarName = aarFile.path.split("/").last; var pathName = "${outputDir.path}/$aarName"; var targetFile = aarFile.copySync(pathName); return AAR() ..file = targetFile ..aarName = aarName; }Future< AAR> handleFlutter() async { var processResult = await Process.run( "flutter", ["build", "apk"], workingDirectory: "..", runInShell: true, ); print(processResult.stdout); var name = "flutter-release.aar"; var file = File("../.android/Flutter/build/outputs/aar/flutter-release.aar"); var target = file.copySync("${outputDir.path}/$name"); return AAR() ..file = target ..aarName = name; }class AAR { String aarName; File file; String get noExtensionAarName => aarName.split(".").first; @override String toString() { return ‘AAR{aarName: $aarName, file: $file, noExtensionAarName: $noExtensionAarName}‘; } }

Dart大概解释下脚本的功能:
  1. 处理.flutter-plugins文件,获取 android 所在目录
  2. 执行flutter/.android下的 gradle 命令来生成 aar
  3. 根据插件所在目录来获取 aar 文件
  4. 打包 flutter 本身的 aar, 这一步因为一些资源的原因, 直接使用 flutter build apk, 会完成所有的中间产物的生成
  5. 将 插件和 flutter 的 aar 文件复制到 output/flutter-aar 文件夹下
output 文件夹就是我们作为 git 依赖使用的文件夹, 这个文件夹
命令:  $ dart build_aar.dart
新建一个目录用于存放 aar
因为 git submodule 的管理方式对于新手不友好, 所以使用更简单一点的方案管理
新建一个目录,把所有的 aar 文件都放在一起 (我的示例代码是放在一个仓库里的, 不过是同级目录)
当前的目录结构是这样的:
tree -L 2 . ├── README.md ├── android-host │├── android-host.iml │├── app │├── build │├── build.gradle │├── gradle │├── gradle.properties │├── gradlew │├── gradlew.bat │├── local.properties │└── settings.gradle ├── flutter-aar │├── flutter-release.aar │└── shared_preferences-release.aar └── flutter_module ├── README.md ├── build ├── flutter_module.iml ├── flutter_module_android.iml ├── lib ├── output ├── pubspec.lock ├── pubspec.yaml ├── shell ├── template └── test

Bash这样分级的好处是仓库权限的分级:
android 组允许访问 android-host 和 flutter-aar
flutter 组允许访问 flutter_module 和 flutter-aar
我示例代码是一个仓库, 但实际上对于项目来说应该是 3 个仓库为佳
修改 android 主工程
build.gradle:
def aarDir = "${rootProject.projectDir.path}/../flutter-aar"repositories { flatDir { dirs aarDir } }dependencies { implementation fileTree(dir: ‘libs‘, include: [‘*.jar‘]) implementation ‘androidx.appcompat:appcompat:1.0.2‘ implementation ‘androidx.constraintlayout:constraintlayout:1.1.3‘ testImplementation ‘junit:junit:4.12‘ androidTestImplementation ‘androidx.test:runner:1.2.0‘ androidTestImplementation ‘androidx.test.espresso:espresso-core:3.2.0‘def file = new File(aarDir) file.listFiles(new FilenameFilter() { @Override boolean accept(File dir, String name) { return name.endsWith("aar") } }).each { f -> def aar = f.name.split("\.").first() println("f.name = ${f.name} , aar = $aar") api(name: f.name.split("\.").first(), ext: ‘aar‘) } }

Groovy这样的情况下这个目录就完成了对于所有 aar 文件的引用
总结一下所有修改 dart 脚本
  1. 复制我提供的仓库下flutter_module/shell/build_module.dart到你的 flutter 下的 shell 目录
  2. 修改这个 dart 脚本中的 targetDir 目录到任何你想要的目录(无论是直接到原生还是到单独仓库内)
原生部分修改
修改 build.gradle 加入对于 aar 的引用
这里使用仓库还是直接在原生工程里看你们项目管理的要求
这一步可以从原生项目的 app/build.gradle 看到所有修改
运行脚本
总结一下我的运行步骤:
  1. 命令行在根目录下执行  cd flutter_module/shell & & dart build_module.dart
  2. 运行 android 项目
建议的步骤如下:
对于  flutter  开发者来说:
  1. cd flutter_project/shell & & dart build_module.dart
  2. cd android-aar
  3. 操作 git 仓库,上传 aar
对于安卓原生来说:
  1. $ cd android-aar
  2. $ git pull
  3. 运行项目
后记本篇详细介绍了我是如何解决 flutter 添加到已有工程的方案, 虽然字数多, 但是实际引入并不复杂
可能有遗漏, 有不清楚的请在官方 blog  下评论留言, csdn 仅作为文章的同步发布平台, 评论可能没有时间看
嗯,仓库在这里:  gitee
以上
See Also
  • Flutter flutter.gradle分析
  • dart中的生成器函数
  • 在Profile下分析android内存占用
  • 在原 Android 工程的基础上构建一个flutter工程
  • Finish FlutterActivity 源码解析
转载:https://www.kikt.top/posts/flutter/exists/android-as-aar-to-maven/

    推荐阅读