把flutter项目作为aar添加到已有的Android工程上
文章目录
对于已有工程想要尝鲜 Flutter, 很多公司给出了最佳实践方案, android 中是使用 aar 加入项目中, 这样原生开发对于 flutter 环境就没有要求了, 只要 flutter 打包后上传 maven 即可, 但是这部分的过程坑很多, 后面我会再补充这种方案
我也摸索了一个实践方案, 将所有项目的 aar 由 flutter 方打包 aar 后将 aar 置入某一个固定位置 ,并置入一个 git 库管理, 然后 android 原生方直接 pull 后引入项目即可
高能预警: 本篇会结合 flutter, android, aar, gradle, maven, docker 的知识来完成所有的步骤
并不是每一个都会详细说明, 如果有不明白的可以在 的本文下面留言, 我会更新文章或给予解答, 其他渠道的可能不会有时间看
开发环境
本人设备环境
MacOS 10.13.6 (17G65)
flutter: Flutter 1.5.4-hotfix.2 • channel stable
12019-10-25 更新说明: 这篇文章因为发布时效的原因, 当时还没有 `$ flutter build aar` 这个命令
2所以本人并没有实测两个东西的优劣性
预计需要的环境
1xcode
2android sdk
3gradle
4android studio
5flutter sdk
6docker # 这个
这些环境我默认你都有, 没有的话本篇不讲
windows 用户? 对不住, 自己找寻其中的差别吧...
flutter
创建 flutter module
使用命令行创建:
$ flutter create -t module flutter_module
1cd flutter_module
2flutter build apk
这里理论上会生成一个 aar
1tree .android/Flutter/build/outputs
2.android/Flutter/build/outputs
3├── aar
4│ └── flutter-release.aar
5└── logs
6 └── manifest-merger-release-report.txt
嗯,就这个东西
我们其实可以直接把这个 aar 放在宿主中,然后通过配置 aar 本地引用来直接使用这个工程, 但是这样可能并不利于持续集成
所以我们要用到 maven 这个利器
ps: 这里有个坑, 就是纯 flutter 项目可以, 但是如果你的 flutter 项目包含了对于第三方项目的依赖, 则 aar 可能不会包含其他的内容, 我们放在最后面再想办法解决
maven 的处理方式(看看就行,作为错误尝试的步骤)
本篇主要讲的是 maven 的方式, 没有原生 plugin 的很简单, 但是有原生 plugin 的 flutter 步骤过于复杂, 最终没实现, 当然理论上肯定是可以实现的
因为本篇讲解的是本人解决 flutter 附着到已有工程的尝试,所以将放弃的过程也记录下来, 如果你只是想看最终的实现方案可以跳过本篇和后续所有涉及到 maven 的步骤
maven 是一个包管理工具
如果你公司有自己的私服, 则跳过这一章直接看下一章, 我这里只是使用 docker 创建一个 maven 私服环境
使用的镜像是 sonatype/nexus3
配置
可选: $ docker pull sonatype/nexus3
我比较熟悉的有两种方式:
命令行直接运行
1docker run --name test_nexus -d -p 8099:8081 -v /Volumes/Evo512/docker/nexus/nexus-data:/nexus-data sonatype/nexus3
使用 docker-compose
1version: '2'
2
3services:
4 my-nexus:
5 image: sonatype/nexus3
6 ports:
7 - 8099:8081
8 networks:
9 - nexus-net
10 volumes:
11 - /Volumes/Evo512/docker/nexus/nexus-data:/nexus-data
12
13networks:
14 nexus-net:
15 driver: bridge
1docker-compose -d up
使用 docker-compose 就是类似于配置文件的方式
运行
在浏览器打开 http://localhost:8099
登录的用户名密码,默认是 admin admin123
点开 maven, 毛也没有
上传 aar
使用 gradle 上传 aar
使用 android studio 打开 flutter_module 下的.android 目录, 经过一顿同步得到的可能是这样的:
一片空白毛都没有...
这时候请 close, 重新打开, 现在是这个鬼样子的
采用 project 视图模式
在.android 下增加一个 gradle 文件,名字自取
比如我的就叫 update_aar.gradle
1apply plugin: 'maven'
2
3def GROUP = 'top.kikt.flutter_lib'
4def ARTIFACT_ID = 'module_example'
5def VERSION_NAME = "1.0.0"
6
7def SNAPSHOT_REPOSITORY_URL = 'http://localhost:8099/repository/maven-snapshots/'
8def RELEASE_REPOSITORY_URL = 'http://localhost:8099/repository/maven-releases/'
9def REPOSITORY_URL = VERSION_NAME.toUpperCase().endsWith("-SNAPSHOT") ? SNAPSHOT_REPOSITORY_URL : RELEASE_REPOSITORY_URL
10
11
12def NEXUS_USERNAME = 'admin'
13def NEXUS_PASSWORD = 'admin123'
14
15afterEvaluate { project ->
16 uploadArchives {
17 repositories {
18 mavenDeployer {
19 pom.groupId = GROUP
20 pom.artifactId = ARTIFACT_ID
21 pom.version = VERSION_NAME
22 repository(url: REPOSITORY_URL) {
23 authentication(userName: NEXUS_USERNAME, password: NEXUS_PASSWORD)
24 }
25 }
26 }
27 }
28 task androidJavadocs(type: Javadoc) {
29 source = android.sourceSets.main.java.srcDirs
30 classpath += project.files(android.getBootClasspath().join(File.pathSeparator))
31 }
32 task androidJavadocsJar(type: Jar, dependsOn: androidJavadocs) {
33 classifier = 'javadoc'
34 from androidJavadocs.destinationDir
35 }
36 task androidSourcesJar(type: Jar) {
37 classifier = 'sources'
38 from android.sourceSets.main.java.sourceFiles
39 }
40
41 //解决 JavaDoc 中文注释生成失败的问题
42 tasks.withType(Javadoc) {
43 options.addStringOption('Xdoclint:none', '-quiet')
44 options.addStringOption('encoding', 'UTF-8')
45 options.addStringOption('charSet', 'UTF-8')
46 }
47 artifacts {
48 archives androidSourcesJar
49 archives androidJavadocsJar
50 }
51}
这个文件呢, 就是上传用的 gradle 文件, 来源于
前几个 def 要根据你的 maven 来修改, 包名, 端口, 用户名,密码
接着引入 gradle 文件到项目中
修改: Flutter/build.gradle
1android{
2 /// ....
3}
4
5apply from: "${rootDir.path}/update_aar.gradle"
按照下图点击
可能会报错
111:58:23: Executing task 'uploadArchives'...
2
3Executing tasks: [uploadArchives]
4
5
6FAILURE: Build failed with an exception.
7
8* Where:
9Settings file '/Volumes/Evo512/code/flutter/add_to_exists_android/flutter_module/.android/settings.gradle' line: 7
10
11* What went wrong:
12A problem occurred evaluating settings 'android_generated'.
13> /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)
14
15* Try:
16Run 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.
17
18* Get more help at https://help.gradle.org
19
20BUILD FAILED in 0s
2111:58:23: Task execution finished 'uploadArchives'.
似乎是由于路径不对的原因, 请使用如下的方式修改 setting.gradle:
1// Generated file. Do not edit.
2include ':app'
3
4rootProject.name = 'android_generated'
5setBinding(new Binding([gradle: this]))
6//evaluate(new File('include_flutter.groovy'))
7evaluate(new File("$rootDir.path/include_flutter.groovy"))
同步 gradle 后
接着双击
就可以上传成功了
然后打开 nexus 查看: http://localhost:8099/#browse/search/maven
有显示, 说明这个 aar 上传是成功的
后面再上传更改版本号即可
Android 项目(host)
新建项目
引入 maven 依赖
添加仓库
根目录 build.gradle, 根据节点增加一个 maven 仓库:
1allprojects {
2 repositories {
3 google()
4 jcenter()
5 maven {
6 url 'http://localhost:8099/repository/maven-releases/'
7 }
8 }
9}
引入库, 在 nexus 的管理界面里可以查看引用方式:
接着在app/build.gradle
中修改
1
2dependencies {
3 // ...
4 implementation 'top.kikt.flutter_lib:module_example:1.0.0'
5}
经过 sync 以后,使用 project 视图, 可以找到这个库:
编码
新建 MyFlutterActivity.java
1package top.kikit.androidhost;
2
3import android.os.Bundle;
4
5import io.flutter.app.FlutterActivity;
6import io.flutter.plugins.GeneratedPluginRegistrant;
7
8/// create 2019-06-14 by cai
9
10public class MyFlutterActivity extends FlutterActivity {
11
12 @Override
13 protected void onCreate(Bundle savedInstanceState) {
14 super.onCreate(savedInstanceState);
15 GeneratedPluginRegistrant.registerWith(this);
16 }
17}
添加到清单文件
1<application>
2 <activity android:name=".MyFlutterActivity" />
3</application>
修改 MainActivity.java
1package top.kikit.androidhost;
2
3import android.content.Intent;
4import android.os.Bundle;
5
6import androidx.appcompat.app.AppCompatActivity;
7
8public class MainActivity extends AppCompatActivity {
9
10 @Override
11 protected void onCreate(Bundle savedInstanceState) {
12 super.onCreate(savedInstanceState);
13 setContentView(R.layout.activity_main);
14 Flutter.startInitialization(this.getApplicationContext());
15 Intent intent = new Intent(this, MyFlutterActivity.class);
16 startActivity(intent);
17 }
18}
这里模拟一进来直接进 FlutterActivity 的场景
建议你的 Android 同事在合适的时机调用 Flutter.startInitialization(this.getApplicationContext());
这个是官方给出的初始化 flutter 引擎的代码, 否则首屏可能会慢
运行项目
初次运行可能会报错 提示一个 androidO 什么的玩意
两种方案
- minSDK 修改为 26, 这个简直不科学
- 在 app/build.gradle 下的 android 节点下增加这个代码
1android{
2 compileOptions {
3 sourceCompatibility 1.8
4 targetCompatibility 1.8
5 }
6}
将源码和目标代码等级都设置为 1.8
嗯 这里插一句, 我的 host 使用的是 androidX, 而 flutter 使用的是 android.support, 所以需要按照 androidX 的迁移流程修改一下, 如果你新建项目的时候勾选了 androidX, 则这里应该不用修改
androidX 的问题可以查看我的, 虽然是 flutter 分类下的,但是对于普通 android 工程也适用
运行结果如下:
在 flutter 中添加带有原生功能的库
这里注意!!!!!!, 请先备份前面几个文件
这里注意!!!!!!, 请先备份前面几个文件
这里注意!!!!!!, 请先备份前面几个文件
这里注意!!!!!!, 请先备份前面几个文件
这里注意!!!!!!, 请先备份前面几个文件
这里注意!!!!!!, 请先备份前面几个文件
这里注意!!!!!!, 请先备份前面几个文件
这里注意!!!!!!, 请先备份前面几个文件
因为一旦 flutter packages get, 则 前面的文件就木有了
在 flutter 中添加库
这里简单举例一下, 使用一个比较常用的shared_preferences
修改 flutter 的 yaml 文件
1dependencies:
2 shared_preferences: ^0.5.3+1
$ flutter packages get
这一步后, 之前的那几个文件没有了...
建议: 把 build.gradle 和 setting.gradle 复制到 module 级别的某个目录下, 比如叫 template
然后用脚本来做这个上传的事情
- 复制模板到对应目录
- 通过环境变量设置 aar 的版本号
- 使用 gradle 命令来完成插件的调用
上传新版本的 aar
修改版本号为 1.0.1
这里上传成功了
到 android host 中用了一下, 果不其然和网上的朋友们说的一样报错了
1ERROR: Unable to resolve dependency for ':app@debug/compileClasspath': Could not resolve io.flutter.plugins.sharedpreferences:shared_preferences:1.0-SNAPSHOT.
2Show Details
3Affected Modules: app
4
5
6ERROR: Unable to resolve dependency for ':app@debugAndroidTest/compileClasspath': Could not resolve io.flutter.plugins.sharedpreferences:shared_preferences:1.0-SNAPSHOT.
7Show Details
8Affected Modules: app
9
10
11ERROR: Unable to resolve dependency for ':app@debugUnitTest/compileClasspath': Could not resolve io.flutter.plugins.sharedpreferences:shared_preferences:1.0-SNAPSHOT.
12Show Details
13Affected Modules: app
查看对应的 pom.xml(我这里是 1.0.2),道理是一样的
1<project xmlns="http://maven.apache.org/POM/4.0.0"
2 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">
3 <modelVersion>4.0.0</modelVersion>
4 <groupId>top.kikt.flutter_lib</groupId>
5 <artifactId>module_example</artifactId>
6 <version>1.0.2</version>
7 <packaging>aar</packaging>
8 <dependencies>
9 <dependency>
10 <groupId>io.flutter.plugins.sharedpreferences</groupId>
11 <artifactId>shared_preferences</artifactId>
12 <version>1.0-SNAPSHOT</version>
13 <scope>compile</scope>
14 </dependency>
15 <dependency>
16 <groupId>com.android.support</groupId>
17 <artifactId>support-v13</artifactId>
18 <version>27.1.1</version>
19 <scope>compile</scope>
20 </dependency>
21 <dependency>
22 <groupId>com.android.support</groupId>
23 <artifactId>support-annotations</artifactId>
24 <version>27.1.1</version>
25 <scope>compile</scope>
26 </dependency>
27 </dependencies>
28</project>
这里有一个 io.flutter.plugins.sharedpreferences 就是报错的元凶了
思考解决方案
看到这里我感觉有如下的方案
- 将所有文件打包到同一个 aar 库中, 然后再上传(也就是网上那个 fat-aar 的方案)
- 修改 flutter 打包脚本, 然后将中间的三方库产物(sp 插件)上传至私服 maven, flutter 项目使用 api 的方式依赖这些库, 完成 host=>flutter=>other plugin 的目的
- 不用 maven, 只用 aar
个人第一感觉, 觉得第一个实施起来可能会简单一些, 先尝试一下
fat-aar
这个找到了两个项目:
一个 gradle 文件的方式:
一个是 plugin 的方式:
但是都要用到一个类似embed
这样的关键字来替换 compile(api/implementation), 无奈找遍 gradle 没找到修改的地方, 只能暂时放弃
flutter 的插件库上传至 maven
这个初始来看很可行.. 但仔细一想, 因为那个版本号的作祟, 需要改动的地方不算很少
每个插件包内的 gradle 文件都需要修改:
- 修改 version 版本号,这个应该是可以通过 环境变量/gradle 命令 来指定为佳, 不能指定的话理论上和 pub 的版本号相同也可以, 如果是 git 依赖, 就用 ref, path 依赖就很比较难自动取了
- 上传脚本,这个要读取上面的版本号, 还要读取一个
为什么要修改版本号呢? flutter 依赖的插件的版本号会被带到 aar 对应的 maven 库中的 pom.xml 文件中
这里要插一句: pom.xml 中依赖的版本号是定义在每个插件自己的 build.gradle 中的,如下面的连接那样
如下所示:
我这里说需要修改的就是这个版本号,否则你上传 maven 的 flutter 库的版本号和插件的 maven 版本号没对上的话,依然会报错
修改版本号并上传需要遵循如下的步骤:
- 读取本地
.flutter-plugins
文件的内容,将其中的版本号字段取出来 - 找到插件文件夹,替换掉版本号字段的内容
- 将上传插件的脚本复制至对应文件夹,并将版本号,group 名与插件统一
- 启动上传脚本
- 将对原生文件的修改内容还原
为什么要做最后一步呢? 这种"从远端"镜像下来的东西,修改回去是一个好习惯, 因为修改了会破坏仓库本身版本的完整性
解决方案-使用 aar 和 git 管理
这个就是我开篇说的解决方案, 不使用 maven, 只是打包出 aar, 集中起来, 置入 git 仓库,如果有必要就打 tag 后 push 到远端, 方便根据版本来引用
然后作为 android 原生方, 在 project 的 gradle 中引入 aar 库即可, 当然如果你是大公司有自己的要求, 还是用上一种比较好
git 和 aar 引入也是很成熟的使用方案了, 无非就是如何拼接而已的问题, 何况这一步还可以通过 gradle 自动完成
处理 flutter 端
这次使用 dart 来作为脚本, 毕竟 dart 语言对于 flutter 开发者来说会很熟悉, 当然这一步可以用任何你熟悉的方式,比如: shell/python 等等, 这一步的执行需要将 dart 放入环境变量中
build_module.dart
:
1import 'dart:io';
2
3var outputDir = Directory("../output");
4var targetDir = Directory("../../flutter-aar");
5
6Future main() async {
7 List<AAR> list = [];
8
9 outputDir.deleteSync(recursive: true);
10 outputDir.createSync(recursive: true);
11 var file = File("../.flutter-plugins");
12 var plugins = file.readAsLinesSync();
13 for (var value in plugins) {
14 if (value.trim().isEmpty) {
15 continue;
16 }
17 var splitArr = value.split("=");
18 var name = splitArr[0];
19 var path = splitArr[1];
20
21 var aar = handlePlugin(name, path);
22 list.add(aar);
23 }
24
25 var aar = await handleFlutter();
26 list.add(aar);
27
28 handleAAR(list);
29}
30
31void handleAAR(List<AAR> list) {
32 targetDir.deleteSync(recursive: true);
33 targetDir.createSync();
34 list.forEach((aar) {
35 var targetPath = "${targetDir.path}/${aar.aarName}";
36 var targetFile = aar.file.copySync(targetPath);
37 print(
38 '\ncopy "${aar.file.absolute.path}" to "${targetFile.absolute.path}"');
39 });
40}
41
42AAR handlePlugin(String name, String path) {
43 var result = Process.runSync("./gradlew", ["$name:assRel"],
44 workingDirectory: "../.android");
45 print(result.stdout);
46
47 var aarFile = File("$path/android/build/outputs/aar/$name-release.aar");
48 var aarName = aarFile.path.split("/").last;
49 var pathName = "${outputDir.path}/$aarName";
50 var targetFile = aarFile.copySync(pathName);
51 return AAR()
52 ..file = targetFile
53 ..aarName = aarName;
54}
55
56Future<AAR> handleFlutter() async {
57 var processResult = await Process.run(
58 "flutter",
59 ["build", "apk"],
60 workingDirectory: "..",
61 runInShell: true,
62 );
63
64 print(processResult.stdout);
65
66 var name = "flutter-release.aar";
67
68 var file = File("../.android/Flutter/build/outputs/aar/flutter-release.aar");
69 var target = file.copySync("${outputDir.path}/$name");
70
71 return AAR()
72 ..file = target
73 ..aarName = name;
74}
75
76class AAR {
77 String aarName;
78 File file;
79
80 String get noExtensionAarName => aarName.split(".").first;
81
82 @override
83 String toString() {
84 return 'AAR{aarName: $aarName, file: $file, noExtensionAarName: $noExtensionAarName}';
85 }
86}
大概解释下脚本的功能:
- 处理
.flutter-plugins
文件,获取 android 所在目录 - 执行
flutter/.android
下的 gradle 命令来生成 aar - 根据插件所在目录来获取 aar 文件
- 打包 flutter 本身的 aar, 这一步因为一些资源的原因, 直接使用 flutter build apk, 会完成所有的中间产物的生成
- 将 插件和 flutter 的 aar 文件复制到 output/flutter-aar 文件夹下
output 文件夹就是我们作为 git 依赖使用的文件夹, 这个文件夹
命令: $ dart build_aar.dart
新建一个目录用于存放 aar
因为 git submodule 的管理方式对于新手不友好, 所以使用更简单一点的方案管理
新建一个目录,把所有的 aar 文件都放在一起 (我的示例代码是放在一个仓库里的, 不过是同级目录)
当前的目录结构是这样的:
1tree -L 2
2.
3├── README.md
4├── android-host
5│ ├── android-host.iml
6│ ├── app
7│ ├── build
8│ ├── build.gradle
9│ ├── gradle
10│ ├── gradle.properties
11│ ├── gradlew
12│ ├── gradlew.bat
13│ ├── local.properties
14│ └── settings.gradle
15├── flutter-aar
16│ ├── flutter-release.aar
17│ └── shared_preferences-release.aar
18└── flutter_module
19 ├── README.md
20 ├── build
21 ├── flutter_module.iml
22 ├── flutter_module_android.iml
23 ├── lib
24 ├── output
25 ├── pubspec.lock
26 ├── pubspec.yaml
27 ├── shell
28 ├── template
29 └── test
这样分级的好处是仓库权限的分级:
android 组允许访问 android-host 和 flutter-aar
flutter 组允许访问 flutter_module 和 flutter-aar
我示例代码是一个仓库, 但实际上对于项目来说应该是 3 个仓库为佳
修改 android 主工程
build.gradle:
1
2def aarDir = "${rootProject.projectDir.path}/../flutter-aar"
3
4repositories {
5 flatDir {
6 dirs aarDir
7 }
8}
9
10dependencies {
11 implementation fileTree(dir: 'libs', include: ['*.jar'])
12 implementation 'androidx.appcompat:appcompat:1.0.2'
13 implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
14 testImplementation 'junit:junit:4.12'
15 androidTestImplementation 'androidx.test:runner:1.2.0'
16 androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
17
18 def file = new File(aarDir)
19 file.listFiles(new FilenameFilter() {
20 @Override
21 boolean accept(File dir, String name) {
22 return name.endsWith("aar")
23 }
24 }).each { f ->
25 def aar = f.name.split("\\.").first()
26 println("f.name = ${f.name} , aar = $aar")
27 api(name: f.name.split("\\.").first(), ext: 'aar')
28 }
29}
这样的情况下这个目录就完成了对于所有 aar 文件的引用
总结一下所有修改
dart 脚本
- 复制我提供的仓库下
flutter_module/shell/build_module.dart
到你的 flutter 下的 shell 目录 - 修改这个 dart 脚本中的 targetDir 目录到任何你想要的目录(无论是直接到原生还是到单独仓库内)
原生部分修改
修改 build.gradle 加入对于 aar 的引用
这里使用仓库还是直接在原生工程里看你们项目管理的要求
这一步可以从原生项目的 app/build.gradle 看到所有修改
运行脚本
总结一下我的运行步骤:
- 命令行在根目录下执行
cd flutter_module/shell && dart build_module.dart
- 运行 android 项目
建议的步骤如下:
对于 flutter 开发者来说:
cd flutter_project/shell && dart build_module.dart
cd android-aar
- 操作 git 仓库,上传 aar
对于安卓原生来说:
$ cd android-aar
$ git pull
- 运行项目
后记
本篇详细介绍了我是如何解决 flutter 添加到已有工程的方案, 虽然字数多, 但是实际引入并不复杂
可能有遗漏, 有不清楚的请在 下评论留言, csdn 仅作为文章的同步发布平台, 评论可能没有时间看
嗯,仓库在这里:
以上