一招解决Flutter离线化嵌入安卓项目的问题(2)
概述
但是,如果我们有 flutter工程代码或者配置的改动,或者更改了flutterSDK,那么我们必须手动将 flutter的最终产物一个一个拷贝到 安卓工程中,这个显然不是我们想要的结果。
作为一个有追求的程序员,要把能自动化的操作尽可能由程序或者脚本来完成,减少人为操作导致的差错。
谁适合阅读
如果你是安卓开发+flutter
开发,你的团队中有安卓,iOS
,Leader
要求你在项目中嵌入flutter模块
来实现混合开发,但是不能对不会flutter
的那些人有影响,那么 请放心阅读此文,一定有帮助。
Flutter产物回顾
flutter产物 aar
产自 flutter build aar命令,命令执行完成且无错误之后,检查
\build\host\outputs\repo
目录,从中找出所有release名字的aar包。aar数量的多少,与flutter引用 插件依赖的数量有关,引入的插件越多,我们需要同步的aar越多。
多种CPU架构下的 libflutter.so
产自 flutter build apk --release ,命令执行完毕且正常无误之后,检查
build/host/outputs/apk
目录,找到其中的app-release.apk
文件,解压它,找到 libs 目录,然后提炼出每个cpu架构下的 libflutter.so 文件,保持原有的目录结构,删掉其中的 libapp.so ,最后组成一个 jnilibs目录,放置到安卓工程中,记得配置sourceSets
将jniLibs.srcDirs
指向src/main/jniLibs
flutter框架代码的jar
产物来自 本机的flutterSDK缓存文件,与当前的flutterSDK版本有关,通常在
C:\Users\xxx.gradle\caches\modules-2\files-2.1\io.flutter\flutter_embedding_debug
目录下,处理方式为:清空该目录,然后 重新flutter run
一个工程,则可以生成新的缓存文件,在你本地存在多个版本flutterSDK
的时候,可以选择这种方式。原本以为这个文件是存在于flutter SDK内部,但是搜索以后发现并没有,所以猜测,此jar包也是flutterSDK在运行项目时临时从网络上下载下来存放在 本机缓存目录下的,属于远程依赖的性质。
自动化流程设计
首先明确我们设计自动化流程的目的:
在Flutter侧发生变化时,要能够一键发布到 原生工程。
就相当于 离线H5应用那种概念,将离线H5包文件整个从本地或者云端,直接拷贝到 原生工程内的正确位置,让原生打包时直接使用最新的离线包资源。
关键词
-
变化
所谓变化,就是flutter侧的代码,资源,依赖,配置文件等,这些发生变化,都会导致flutter的产物发生变化。
-
一键发布
要降低人为操作的难度,最好是降低到双击某个按钮就能实现的程度。
角色
除了两个关键词之外,还有两个角色我们要注意:
-
Flutter开发者
这个角色会自己安装flutter开发环境,并且能够进行fluttter程序开发,还要承担将 flutter离线产物 发布到工程内的职责。
-
普通安卓原生开发者
一个研发团队,不能强制要求每个人都接纳
flutter
。在他们的概念里,flutter离线产物就是一个普通的 依赖包,随着编译打包,成为apk的一个部分。所以,flutter对他们而言实际上是无感的。他们也不会参与到flutter模块的开发工作中来。
任务拆分
分析到这里,我们要做的事情就很明确了。
-
在 原生安卓工程内部,设计 gradle task:flutter_publish,它的功能是:
-
生成flutter最新产物,并将它们拷贝到原生工程中的 正确位置
每次flutter侧有任何改动,都要将最新产物 发布到原生工程, 以便原生功能在下次打包时能带上最新的flutter模块。
-
将最新flutter产物,通过 git 上传到 独立于原生工程之外c的代码仓库
之所以放到另外的代码仓库,而不是 原生安卓工程仓库,两个原因:
首先,flutter产物毕竟是独立与原生安卓代码之外的插件,它的版本变化与原生安卓的版本变化没有必然联系。
其次,虽然本文只是提到了 安卓侧的flutter产物,而flutter离线化嵌入方案要完整的话,iOS必须跟上,iOS的flutter产物也隶属同一个flutter工程,最好是放到同一个 git仓库 进行统一管理。
PS:
这里使用git,而不是某些网盘或者文件托管平台,实际上也是因为git纯天然支持文件差异对比,发布新的flutter产物时,哪些文件有变化一目了然
。
-
-
在 原生安卓工程内部,设计 gradle task:flutter_sync,它的功能是:
将
最新的flutter产物
从git
仓库上 下载下来,并拷贝到原生工程的正确位置。
这样就行了吗?
不,以上两个只是 核心工作,我们还需要做一些辅助工作来帮助提升工作效率。
-
设计 gradle全局开关,
isFlutterDebugging
通常,在我们作为
flutter
开发者的角色来对flutter工程做修改时,让它为 true,此时为 flutter的开发模式,此时,原生工程依赖的是 flutter工程本身。而当flutter开发者完成了工作时,就可以使用 flutter_publish 任务来发布flutter最新产物,然后将 它改为false,在其他人打包时,直接就能使用 flutter最新产物,此时为 flutter的发布模式。而由于 flutter的开发模式与发布模式的依赖引用的细微差别,我们要将isFlutterDebugging
运用在 原生gradle配置的关键位置,来让 flutter的开发模式与发布模式 自由切换,且不引起编译问题。 -
让
flutter_sync
任务与assemble
任务产生依赖熟悉安卓打包命令的应该都知道,我们生成安卓的最终产物,apk,或者aar,aba等,都是通过
assemble
来完成的。我们要让 普通的,不熟悉flutter的开发者对flutter产物的到来产生无感的效果的话,就不能要求他们手动去调用 flutter_sync 任务之后再去assemble。所以,让这两个任务建立联系,每次assemble之前,都去自动flutter_sync
是最好的方式,当然,为了避免无用功,我们可以在本地保存一个 flutter产物仓库的 最新提交记录的编码,每次去flutter_sync
时,都先对比 是否有必要更新,没必要,则可以不用 执行 flutter_sync 任务(当然,本地完全没有 flutter_sync 过 的情况除外)
图形化解释
上面的说法比较模糊,我用一张图来解释:
此图展示了一个普通的安卓项目 在 嵌入了Flutter模块前后的主要区别。
其中有两种角色。
一个 是普通开发者,他们只需要关注 原生工程的开发内容,对于Flutter 内容的存在完全是零感知。
另一种就是 Flutter 兼原生开发者,他们关注两侧的代码内容,对Flutter有开发权限,并负责 执行 flutter_publish 发布最新的flutter产物到 git仓库。
普通开发者无论是在 AndroidStudio
中点击绿色三角形Run
项目,或者是 手动执行 assemble
任务来生成apk包,都会默认执行 flutter_sync 任务来同步flutter最新产物。这样,对于对Flutter陌生的普通开发者来说,flutter的存在都不会对他们有负面影响(开发环境也不需要有任何变动)。
必会的技术
要完成以上这些流程,我们得对如下技术点有一定程度的了解:
- Flutter 开发环境搭建以及基本开发
- Flutter 混合开发的主流框架,主要是FlutterBoost的 集成流程
- Android Gradle 基础以及 gradle 脚本开发
- Andrioid Apk 文件的主要结构
主要脚本代码
全局配置 flutter_config.gradle
ext {
// flutter模块是否处于调试状态
// - true 调试状态直接依赖 flutter工程,
// - false 非调试状态则是依赖 flutter工程生成的aar包
flutterModuleTestMode = false
// flutter项目的名称
flutterModuleName = "flutter_module"
// 产物仓库地址
gitRepoUrl = "ssh://git@codehub-dg-g.huawei.com:2222/zWX1245985/flutter_repo_just_for_test.git"
// 仓库名称
repoName = "flutter_repo_just_for_test"
// 一个仓库可能存放不同的flutter项目的产物,故 使用proName来区分
proName = "kbz_b"
}
文件(夹)的拷贝
def copyFile(sourceFilePath, destinationFilePath) {
def sourceFile = file(sourceFilePath)
def destinationFile = file(destinationFilePath)
ant.copy(todir: destinationFile.parent) {
fileset(file: sourceFile)
}
}
void copyFiles(String sourceDirPath, String targetDirPath) {
File sourceDir = file(sourceDirPath)
File destinationDir = file(targetDirPath)
if (!sourceDir.exists()) {
println("原始目录不存在")
return
}
if (!destinationDir.exists()) {
destinationDir.mkdirs()
}
FileTree sourceFiles = fileTree(dir: sourceDir)
sourceFiles.each { sourceFile ->
File destinationFile = new File(destinationDir, sourceDir.relativePath(sourceFile).toString())
copyFile(sourceFile, destinationFile)
}
}
生成aar
void buildAar(String flutterRoot) {
def commandCd = "cd ${flutterRoot}"
def commandFlutterBuildAar = "flutter build aar --no-profile"
def result = project.exec {
commandLine "cmd", "/c", "$commandCd && $commandFlutterBuildAar"
}
println "打aar包的命令执行完成,exitValue 是: ${result.exitValue}"
if (result.exitValue != 0) {
println "aar打包失败,尝试在 $flutterRoot\.android\gradle.properties中添加 org.gradle.java.home=D://env//androidStudio//jbr "
println "打开笔记本的热点功能有可能也会引起打aar失败,请关闭热点再试 "
}
}
生成apk
void buildApk(String flutterRoot) {
def commandCd = "cd ${flutterRoot}"
def commandFlutterBuildApk = "flutter build apk --release"
def result = project.exec {
commandLine "cmd", "/c", "$commandCd && $commandFlutterBuildApk"
}
println "打apk包的命令执行完成,exitValue 是: ${result.exitValue}"
}
克隆flutter产物的 git仓库
/**
* 将 flutter离线依赖资源下载到我项目本地
*
*/
boolean cloneFlutterOfflineRelay() {
println("cloneFlutterOfflineRelay->$repoName")
// 检查 repoName 目录是否存在,如果存在,先删除
def flutterProductRepo = file("$repoName")
if (flutterProductRepo.exists()) {
def deleteRes = deleteRepo()
if (!deleteRes) {
println("$repoName 目录删除失败,请手动删除后重试")
return false
}
}
println("gitRepoUrl-> $gitRepoUrl")
def result = project.exec {
commandLine "cmd", "/c", "git clone $gitRepoUrl"
}
println "clone 命令执行完成,exitValue 是: ${result.exitValue}"
println "从云端下载flutter模块离线资源成功"
return true
}
推送本地改动到git远端
void pushFlutterRepoCommit() {
def commandCd = "cd $repoName"
def cmdAdd = "git add --all"
def cmdCommit = "git commit -m "sync""
def cmdPush = "git push origin"
def result = project.exec {
commandLine "cmd", "/c", "$commandCd && $cmdAdd && $cmdCommit && $cmdPush"
}
println "提交的命令执行完成,exitValue 是: ${result.exitValue}"
}
将 libs和so拷贝到原生工程的对应位置
/**
* 将所有的aar包放置到boost_core模块的libs中
*/
void applyAar() {
def localAar = "libs" // so将要拷贝到的目录
def remoteAar = "$repoName/$proName/libs" // apk解压之后的so存放目录
copyFiles(localAar, remoteAar)
println "同步aar资源成功"
}
/**
* 将所有的aar包放置到boost_core模块的libs中
*/
void applySo() {
def localSo = "src/main/jniLibs/" // so将要拷贝到的目录
def remoteSo = "$repoName/$proName/jniLibs" // apk解压之后的so存放目录
if (file(remoteSo).exists()) {
delete remoteSo
}
copyFiles(localSo, remoteSo)
println "同步so资源成功"
}
完整Demo
了解一项架构,最重要的就是要有 实验环境,此Demo包含了完整的Flutter混合架构的可执行项目。
github地址如下:github.com/18598925736…
- 注意阅读
README.md
- 注意阅读
README.md
- 注意阅读
README.md
如运行有问题,欢迎留言!
转载自:https://juejin.cn/post/7269042529249116218