引子
盼望着,盼望着,华为鸿蒙升级了。Harmoney OS Next 不再支持 Android 系统了,整个移动端瞬间炸了锅。
作为一个曾经手机市场王者,任何一家公司都无法忽视华为的市场,Android 阵营瞬间就出现了学习鸿蒙系统的激进力量。
同时,怕步子超大了,Harmoney OS Next 还是支持 flutter 跨端技术的。鉴于我一直时做跨端开发的,无论是 React Native、小程序,还是阿里系的 mist、weex 都多少有所涉猎,也就直接把三年前的 flutter 代码拉出来遛遛了:
与此同时,也顺手建立另一个 my_fluter 库,把MeiTuanEdwin项目中 my_flutter 模块单独抽出来了:
弄得有其他人合作开发似的~
背景
大家对郭霖大佬都不陌生吧?! 前一段我刷他的文章,发现他在研究 flutter,然后还分享了一篇读者投稿。以此得知,微软未来会有很多的 flutter 的业务,并且大部分处于起步阶段。所以郭老师就开始之前很少关注的 flutter 研究生涯。 这不是众里寻她千百度,蓦然回首,那人却在微信公众号吗? 我也就立马着手继续自己的 flutter 研究之路了。如果你对 flutter 一知半解,那我劝你先去读读这篇文章:juejin.cn/post/730657... 跟你想的基本一致,我就是读这篇文章的时候发现,搭建环境根本不是那么回事儿。
八卦:大厂是怎么玩儿的?
很多有电商味道的大厂,都是精细化操作,整个App 采用组件化开发,每个组件都是一个单独的小组 客户端+前端,其中客户端由 Android+iOS+跨端(外包) +小程序+h5组成,前端由小程序+h5+web 组成,这样整个大前端团队就形成了。 现在开始卷,客户端团队的架构组经过无数次的迭代重构,终于在 325 下来之前,憋出了 taro 、uniapp或者 morjs。由于实现了多端一体化,开发一次代码的成果可以在 Android、iOS、小程序、Web 端等所有平台运行,那么写**跨端(外包)**的同学就愉快的下岗了。同时,原来的小程序开发时微信、抖音、支付宝,每个平台一个主要负责同学,现在另外两个同学只要不去劳动局都好商量。 你可能会问,剩余的技术空缺谁来补上。听好啊: 下一年的 KPI主要是两个方向, 1. 客户端同学除了会用 Android、iOS 开发,也要具备跨端开发的能力,并且要互为 backup 2. 前端同学除了自己负责B 端开发业务,也要能够胜任微信小程序各平台的开发工作 基于第一点,我愉快的玩起了 flutter 。
进入Flutter篇
工欲善其事,必先利其器。按照Flutter 开发过程主要是依赖 aar包开发,和依赖源码开发,官方链接:docs.flutter.dev/add-to-app/... 通过照猫画虎的骚操作后,发现根本没有卵用,官方给的文档毕竟玩不转。那么问题来了,你通常时怎么使用 aar 包的?
打包
官方打包
flutter build aar
你想要的打包命令
flutter clean & flutter build aar --build-number 1.1.1 --no-profile --no-release
profile解释: 在 Android 开发中,"profile" 版本通常指的是应用的性能分析版本。这种版本允许开发者在设备上运行应用程序以进行性能分析和调试。这种版本通常会包含更多的日志记录和性能分析工具,用于分析应用在不同设备上的性能表现,并检测可能存在的性能问题。 Flutter 构建过程中的 --no-profile 标志会指示系统不生成这种性能分析版本。这样做是为了加快构建过程并减小生成的输出文件大小。
通过以上命令,你会在"/your_flutter/build/host/outputs/repo"目录下拿到 aar包,如下:
那么,aar包怎么用呢?
aar 包导入推荐策略
策略1:官方推荐导入
配置完成你会发现,flutter 引擎的依赖依赖项一个也拿不到,就是下面这个几个货:
你肯定觉得卧槽了:是的,兵法上没写,但是打仗的时候要用。马谡就是这么丢街亭的~ 那《兵法》上咋说的呢?
兵法上说,你按照我说的运行完 flutter build aar 命令,配置你的 project 依赖绝对没问题。看看这句无耻的话:
说无耻,其实有些过分了。毕竟人家写兵法的人言简意深来着~
First of all,这兵法主要是给资本主义市场使用的
Secondly ,为了迎合中国市场,也告诉你要配置中国镜像
问题就在这里,切换镜像之后,flutter 模块的打包aar的依赖没有被上传到指定的服务端。
墙外:
执行flutter build aar 后,在'/Users/edwin/edwin/MeiTuanEdwin/my_flutter/build/host/outputs/repo文件夹下面会生成aar包和pom文件,后者标示了flutter引擎依赖项,如下:
flutter_debug-1.1.4.pom文件里存放的是依赖项信息:
xml
<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<!-- This module was also published with a richer model, Gradle metadata, -->
<!-- which should be used instead. Do not delete the following line which -->
<!-- is to indicate to Gradle or any Gradle module metadata file consumer -->
<!-- that they should prefer consuming it instead. -->
<!-- do_not_remove: published-with-gradle-metadata -->
<modelVersion>4.0.0</modelVersion>
<groupId>com.example.my_flutter</groupId>
<artifactId>flutter_debug</artifactId>
<version>1.1.4</version>
<packaging>aar</packaging>
<dependencies>
<dependency>
<groupId>io.flutter</groupId>
<artifactId>flutter_embedding_debug</artifactId>
<version>1.0.0-9064459a8b0dcd32877107f6002cc429a71659d1</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>io.flutter</groupId>
<artifactId>armeabi_v7a_debug</artifactId>
<version>1.0.0-9064459a8b0dcd32877107f6002cc429a71659d1</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>io.flutter</groupId>
<artifactId>arm64_v8a_debug</artifactId>
<version>1.0.0-9064459a8b0dcd32877107f6002cc429a71659d1</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>io.flutter</groupId>
<artifactId>x86_64_debug</artifactId>
<version>1.0.0-9064459a8b0dcd32877107f6002cc429a71659d1</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>io.flutter</groupId>
<artifactId>x86_debug</artifactId>
<version>1.0.0-9064459a8b0dcd32877107f6002cc429a71659d1</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>
这些都是Gradle发布的 Meta data,最终被发布到storage.googleapis.com 这个服务端,点击下面的链接试试
storage.googleapis.com/download.fl...
上面提到 flutter 库在编译时就已经做了架构适配和引擎依赖构建,原则上这些元数据都应该被发布到服务端,这样才能通过 dependencies 配置依赖获取相应的aar包,事实上根本没有我们想要下载的依赖包,搜索唯一标记 9064459a8b0dcd32877107f6002cc429a71659d1(以编译生成POM里的version为准) 无法得到想要的依赖。
墙内
flutter相关配置如下:
根据官方指导,domain切换后,源文件会发布到 storage.flutter-io.cn/download.fl... 点击进入这个站点,同样找不到对应的POM文件中生成的依赖信息
找不到的原因是,flutter build aar 的发布产物是本地的 repo 库,人家压根没有上传到服务的,发布个毛线。 嗨,
兵法坑死人~
尽信书不如无书啊!
这就造成了我们在使用aar包时,需要逐个引入POM文件中的依赖项,进行引擎依赖配置:
arduino
implementation 'io.flutter:flutter_embedding_debug:1.0.0-9064459a8b0dcd32877107f6002cc429a71659d1'
implementation 'io.flutter:armeabi_v7a_debug:1.0.0-9064459a8b0dcd32877107f6002cc429a71659d1'
implementation 'io.flutter:arm64_v8a_debug:1.0.0-9064459a8b0dcd32877107f6002cc429a71659d1'
implementation 'io.flutter:x86_64_debug:1.0.0-9064459a8b0dcd32877107f6002cc429a71659d1'
implementation 'io.flutter:x86_debug:1.0.0-9064459a8b0dcd32877107f6002cc429a71659d1'
我自然是不愿意搞这么多配置的,关于aar包源数据发布上传这部分,我耗了很久反复阅读官方文档,并且在网上查询相关资料,得到如下提示:
1)Stack Overflow 给出的策略,是直接更改flutter SDK包里的 aar_init_script.gradle文件,很多大厂也是这么干的 stackoverflow.com/questions/7...
2)华为开发者联盟
分析的很透彻,最后希望你通过脚本实现,一言以蔽之:通过执行 flutter build aar -v 查看flutter build aar 的执行秘密,文章在这儿
我还真就这么干了,好奇心害死猫。咳~~~
3)官方指导文档真坑爹
在我们运行flutter build aar时,其实他是借助于.android项目配置Gradle或者Gradle Wrapper能力,其核心工作在 flutter-SDK-Dir/flutter/packages/flutter_tools/gradle/aar_init_script.gradle 文件中完成aar发布工作,具体逻辑如下:
aar文档相关源数据都在本地repo库里,所以通过Maven依赖的远程服务是断断无法获取到的。
综上所述,官方文档给出的关于aar的配置,是指导开发人员使用发布到本地的aar文件的方法,而且还缺少引擎依赖的相关指导
为了能够简化使用flutter模块 aar包的引入流程,我们需要有自己的Maven私服,并且将flutter项目的aar包及相关metadata文件,发布到云端进行更新和迭代。这里选择阿里云效,完成这个任务
发布AAR包到Maven私服
云端选择阿里云效 ,打开阿里云的云效Packages官网:
你会发现阿里云提供的免费私有Maven库服务,并且介绍了Gradle 7.0前后如何发布项目文档至私有仓库。
上面提到了,发布到私服有两个方案:
1)更改aar_init_script.gradle文件
更改方便,但是侵入性比较强,而且每个人都要更改自己的SDK包。哪天编译脚本升级了,大家一起抓瞎~
2)在flutter项目中直接发布
As we all know,aar包之所以能够通过Gradle编译成功,是依赖其my_flutter/.android项目,该项目中的有一个Flutter库Module,是执行flutter build aar时自动生成的。通过flutter build aar -v 可以很清晰的看到,编译过程中,flutter 相关产物都放到了.android/Flutter模块的build文件夹下,看编译日志:
bash
Starting process 'command '/Users/edwin/Library/flutter/bin/flutter''.
Working directory: /Users/edwin/edwin/MeiTuanEdwin/my_flutter
Command: /Users/edwin/Library/flutter/bin/flutter #Flutter 工具的路径
--verbose #启用详细输出,以便查看更多构建信息
assemble #执行组装操作,即构建过程
--no-version-check #禁用版本检查,Flutter 不会检查是否有新版本
--depfile /Users/edwin/edwin/MeiTuanEdwin/my_flutter/.android/Flutter/build/intermediates/flutter/debug/flutter_build.d #指定依赖文件的路径
--output /Users/edwin/edwin/MeiTuanEdwin/my_flutter/.android/Flutter/build/intermediates/flutter/debug #指定输出目录的路径
-dTargetFile=lib/main.dart #指定入口文件的路径
-dTargetPlatform=android #指定目标平台为 Android
-dBuildMode=debug #指定构建模式为调试模式
-dTrackWidgetCreation=true #启用 Flutter 的控件创建跟踪
debug_android_application
看明白了吧,该命令的目标是构建一个用于调试的 Android 版本的 Flutter 应用程序,output命令指定了输出目录。我们看看output文件夹所在的环境:
这说明我们的 Flutter 模块就是用来生成 aar 包的对应资源而建立的,那我们完全可以在 Flutter 项目的 build.gradle 文件中添加一个 task 用于发布 aar 包,发布任务通过publish.gradle文件完成。实现如下:
bash
// publish flutter-debug.aar
//plugins {
// id 'java'
// // Gradle 7.0+ 版本
// id 'maven-publish'
//}
group 'com.example.my_flutter'
version '1.1.4'
def artifactIdStr = 'flutter_debug'
apply plugin: 'maven-publish'
task comps { // 让我们看看它有哪些组件:
afterEvaluate {
println("Components: " + components*.name) // 代码 1
}
}
def directoryToPublish = "$rootDir.parentFile/build/host/outputs/repo/com/example/my_flutter/flutter_debug/$project.version"
afterEvaluate { // 代码 2
publishing {
publications {
flutter(MavenPublication) { // 代码 3
from components.debug
println("groupId = " + groupId + ",artifactId = " + artifactIdStr + ",version = " + version + ",buildDir = " + buildDir)
groupId = "$project.group" // 代码 4
artifactId = artifactIdStr
version = "$project.version"
// artifact "$buildDir/outputs/aar/flutter-debug.aar" //aar artifact you want to publish
// artifact "$directoryToPublish/${artifactIdStr}-${project.version}.aar"
// artifact "$directoryToPublish/${artifactIdStr}-${project.version}.module"
// artifact "$directoryToPublish/${artifactIdStr}-${project.version}.pom"
// artifact("$directoryToPublish/${artifactIdStr}-${project.version}.aar") /*{
// classifier 'aar' // 使用 'aar' 作为分类器
// }*/
// artifact("$directoryToPublish/${artifactIdStr}-${project.version}.module") /*{
// classifier 'module' // 使用 'module' 作为分类器
// }*/
// artifact("$directoryToPublish/${artifactIdStr}-${project.version}.pom")/* {
// classifier 'pom' // 使用 'pom' 作为分类器
// extension 'pom' // 设置为 'pom' 类型
// }*/
}
}
repositories { // 代码 5
// maven {
// url = 'https://packages.aliyun.com/maven/repository/2443959-release-V0Shv7/'
// credentials {
// username = '##############'
// password = '*****************'
// }
// }
maven {
url = 'https://packages.aliyun.com/maven/repository/2443959-snapshot-5T0GnA/'
credentials {
username = '###############'
password = '******************'
}
}
}
}
}
代码 1 处的 components*.name 用于确认当前Gradle 会构建那些组件用于发布,输出结果如下:
从打印结果来看,可以构建 debug、profile、release 三种组件及其组合组件all,我们使用 debug 版本
代码 2 处,评估之后开始进入发布工作,依赖 maven-publish 插件进行发布,gradle 要配置成 7.5 以上的版本
代码 3 处,定义了一个 MavenPublication,命名为 "flutter"。from components.debug 表示将 MavenPublication 的内容从 Gradle 构建中的 components.debug 组件中获取。这里的MavenPublication包含了该组件的输出,例如 AAR 文件、POM 文件等。这是为了将 Flutter 模块构建的输出发布到 Maven 仓库
代码 4 处配置了 GVA 用于唯一标识当前AAR 包,方便其他项目通过 Maven 或gradle 进行依赖,如下:
当然,现在依赖还是无法完成,因为aar 包要发布到远程 Maven,
代码 5 处就是用于配置远程服务地址的,我们选用的是阿里云效服务,用起来也算是麻烦+坑爹。
阿里云效:packages.aliyun.com/maven
如果玩不来,这里还有篇文章可以参考一如何把 library 发布到阿里云效:
shawlaw.github.io/Fragmentary...
发布任务编码完成~
然后在 Flutter 模块的 build.gradle 中 apply publish.gradle代码如下:
csharp
def currentTask = gradle.startParameter.taskNames.join(" ")
println("currentTask =" + currentTask)
if (!currentTask.contains("Aar")) {
apply from: "../../publish.gradle"
}
这段代码的意思就是说,在 Flutter 项目编译过程中,准确的说是评估的时候,把发布任务引入,等compile完成后执行发布任务。
最后,咱们再执行一遍 flutter build aar 指令,发现.android/Flutter/build/目录下没有生成 flutter 发布文件夹,
逗我呢?!
老铁,这里还得执行一遍 ./gradlew publish 命令,执行发布任务,现在可以愉快的看到如下效果了:
那么,现在赶快在另一个项目里试一下配置依赖吧。 首先在需要使用 aar包的项目根目录下 root.gradle 中配置 Maven 库,参考如下:
其次在需要使用 aar包的 module 中,配置 AVG 依赖如下:
现在同步代码,等待依赖完成。flutter 引擎的五个依赖如期而至:
Shell脚本一键打包发布
等等老哥~ 道理我都听明白了,上面的代码还是要手动更改 Flutter 项目中的 build.gradle 文件啊。更要命的是,如果执行 flutter clean 清理一遍编译工程,刚刚的文档不都白瞎了么。你这是让我哭吗?
我说老兄你等等,这不是上脚本呢嘛~
-
首先,我们把发布文件 publish.gradle 移动到 my_flutter 项目根目录下,并在根目录下创建一个shell 脚本文件 apply_patch.sh,如下:
-
然后,将 publish.gradle 导入到 Flutter 项目中的诉求未变,通过 shell 脚本实现如下:
shell
# 获取当前脚本所在目录的绝对路径
SCRIPT_DIR=$(dirname "$(readlink -f "$0")")
# 切换到Flutter项目的根目录
cd "$SCRIPT_DIR/.android/Flutter" || exit
# 在Flutter/build.gradle文件中执行命令:
# 1)在android配置项前插入 apply 发布应用
# 2)-i '.bak',备份原始文件
sed -i '.bak' '/^android {/i\
def currentTask = gradle.startParameter.taskNames.join(" ") \
println("currentTask =" + currentTask)\
if (!currentTask.contains("Aar")) {\
apply from: "../../publish.gradle"\
}' "$SCRIPT_DIR/.android/Flutter/build.gradle"
通过shell脚本将发布任务添加到 .android/Flutter 项目中,脚本的含义都写在了注释里。
- 插入完成后,还要进行发布工作,仍然通过脚本完成,如下:
bash
# 切换到 ./android 目录下执行发布任务
cd "$SCRIPT_DIR/.android" || exit
./gradlew publish --info > publish.log 2>&1
if [ $? -ne 0 ]; then
echo "发布任务失败!查看 publish.log 获取更多信息。"
exit 1
fi
这里把发布日志都打印在了 publish.log 文档里,如果发布失败./gradlew publish --info 会提供详细的失败信息。
- 版本控制,aar 包是有自己的版本号的,比如 1.1.4。为了统一控制 version,不要手动更改gradle 文件,我们引入如下脚本:
bash
# 替换 publish.gradle 中的版本号
OLD_VERSION=$(grep -o "version '[0-9]\+\.[0-9]\+\.[0-9]\+'" "$SCRIPT_DIR/publish.gradle" | head -n 1)
if [ -z "$OLD_VERSION" ]; then
echo "无法获取旧版本号!"
exit 1
fi
sed -i'.bak' "s/$OLD_VERSION/version '$BUILD_NUMBER'/" "$SCRIPT_DIR/publish.gradle" || { echo "版本号替换失败!"; exit 1; }
-
合并所有脚本,得到完整的发布脚本apply_patch.sh,脚本如下
作为一个广告引流专业户,还是详见 github 代码库吧~
-
执行 ./apply_patch.sh 脚本
到这里整个一键打包发布工作就完成了
FAQ
1.local.properties 类似文件属性配置时,不要加引号,不然在 gradle 文件中获取到的值要处理
- gradle 的版本要配置成 7.5,太高版本不见得稳定,而且低版本调用他的时候存在融合问题:
参考文献
- GitHub库地址 github.com/hongyi0609/...
- my_flutter 库地址 github.com/hongyi0609/...
- flutter 国内镜像 flutter.cn/community/c...
- aar 服务端 storage.googleapis.com/download.fl...
- 华为开发者联盟分析 blog.csdn.net/Ever69/arti...
- 私服配置 stackoverflow.com/questions/7...
- 阿里云效:packages.aliyun.com/maven