Android 项目使用 Github Actions 实现自动打包发布
编辑背景
朋友的安卓项目[1]想要实现更新代码(发布版本)后自动打包成apk并发布。同时,他有这么一个需求:由于他的app需要支持安装和升级(安装时,app需要签名;同时,如果需要升级,必须用同一个key签名,才能保留上一版本的数据),因此需要增加签名这一步骤。
本文假定你已经掌握Android app的开发流程,熟悉Gradle,熟悉安卓应用的打包发布方法,本文重点介绍利用GitHub Actions 进行Android 项目CI/CD 的过程。
基础知识和思路
如果不使用CI/CD ,你大概需要使用 Android Studio 的 Generate Signed Bundle/APK
功能[2],关于应用打包发布的具体过程,这篇官方的文章讲得已经非常详细,就不再叙述了。后续的操作假定你已经熟悉使用这个功能进行打包,同时已经生成了一个供签名的key。
同时,文章也假定你已经熟悉Gradle[3],这是一个构建工具,能够自动化构建流程。在打包发布(Release)版本的的apk时,只需要使用命令gradle assembleRelease
即可构建供发布的apk。
最后,我们需要给apk签名。你可以使用apksigner
等,在此不多赘述。
因此,如果要使用一个自动化的流程替代我们手工的发布,需要完成以下几个步骤:
- 将新版本的代码构建成apk
- 将上一步生成的apk用我们的key签名
- 以某种方式将这个apk发布出去,供用户下载
经过一些资料的查,其实Github 的 Marketplace就提供了很多已经封装好的workflow,例如
- https://github.com/marketplace/actions/gradle-build-action 解决构建apk的问题
- https://github.com/marketplace/actions/sign-android-release 解决apk签名的问题
- https://github.com/marketplace/actions/create-release 解决发布的问题
所以我们要做的事情就是将这几个积木串起来就大功告成了。另外,我希望这个过程能够跟git 工作流程结合起来,我们并不需要每次push代码都构建发布一个版本,使用tag来进行版本的管理是比较理想的:每当一个tag被push到Github,就触发一次构建,发布一个版本。
走,实操走起
Talk is cheap, show me the code. 这里先把workflow的yaml贴出来,再慢慢解释。
name: Release
on:
push:
tags:
- "v*"
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v3
- uses: actions/setup-java@v3
with:
distribution: temurin
java-version: 11
- uses: gradle/gradle-build-action@v2
with:
gradle-version: current
arguments: assembleRelease
- uses: r0adkll/sign-android-release@v1
id: sign_app
with:
releaseDirectory: app/build/outputs/apk/release
signingKeyBase64: ${{ secrets.SIGNING_KEY }}
alias: ${{ secrets.ALIAS }}
keyStorePassword: ${{ secrets.KEY_STORE_PASSWORD }}
keyPassword: ${{ secrets.KEY_PASSWORD }}
- run: mv ${{steps.sign_app.outputs.signedReleaseFile}} LittleGooseOffice_$GITHUB_REF_NAME.apk
- uses: ncipollo/release-action@v1
with:
artifacts: "*.apk"
token: ${{ github.token }}
generateReleaseNotes: true
一个GitHub Action的yaml包括几个必须的元素:name、on和jobs。name就是这个action的名称,on就是这个action 的触发方式,jobs就是这个action要干的事情。
从这个on
开始解释,前面说到,我们希望“每当一个tag被push到Github,就触发一次构建,发布一个版本”。我们的tag应当使用semantic versioning
[4],例如v1.0.1-beta
。因此,on
这部分我们这样写:
on:
push:
tags:
- "v*"
表示在有tag被push,且tag是以v
开头时,运行这个action。
接下来到jobs
,这里定义这个action要干的事情。我们要做的事情比较简单,就只用一个叫build
的job就好啦。如果定义多个job,可以享受到一些例如复用job、job之间的依赖之类的功能[[https://docs.github.com/en/actions/using-jobs/using-jobs-in-a-workflow](https://docs.github.com/en/actions/using-jobs/using-jobs-in-a-workflow)],在这里就不多赘述啦。再往里看,首先是一个`runs-on`标签,表示这个job要在什么环境下运行。一般情况下我们使用`ubuntu-latest`就可以啦,有特殊需求的可以使用其他的系统[https://docs.github.com/en/actions/using-jobs/choosing-the-runner-for-a-job]。然后是一个permission
标签,这里不多叙述,感兴趣可以查阅文档[5],后面的steps
才是重点。
可以看到,steps
下面是多个单独的项,每个项里面又有uses
、with
等标签。steps
之间是串行运行的,意味着前一个step运行成功后才会进入下一个step,如果某一个step失败了,整个过程就会失败。uses
表示使用某个模板,with
则是传入模版的一些参数,模板的使用可以在marketplace的文档中找到。接下来我们逐个step往下看。
检出代码
- uses: actions/checkout@v3
这一步是基本上所有CI/CD流程必须的,把对应的代码从git仓库中检出,放到工作目录中。
Gradle打包
- uses: actions/setup-java@v3
with:
distribution: temurin
java-version: 11
- uses: gradle/gradle-build-action@v2
with:
gradle-version: current
arguments: assembleRelease
这里实际上有两步,第一步先配置好java环境,这一步就不多说了;第二步就是前面提到的将新版本的代码构建成apk。这里传入了两个参数(with
),gradle-version
表示要使用的gradle版本,这里选择current
,就是当前最新的稳定版本。arguments
表示gradle命令的参数,我们这里要Release,所以选assembleRelease
。根据经验,这个未签名的apk会生成在app/build/outputs/apk/release
,这里暂时用不到,但是我们先记下来,下一步有用。
给apk签名
- uses: r0adkll/sign-android-release@v1
id: sign_app
with:
releaseDirectory: app/build/outputs/apk/release
signingKeyBase64: ${{ secrets.SIGNING_KEY }}
alias: ${{ secrets.ALIAS }}
keyStorePassword: ${{ secrets.KEY_STORE_PASSWORD }}
keyPassword: ${{ secrets.KEY_PASSWORD }}
这一步就是要给apk签名了。这里有一个陌生的id
标签,主要用来标识这一步,后续的step可以使用这一步产生的一些变量。
这一步的with
中也有一些新东西,就是以 ${{ xxx }}
标识的变量。这里都是secrets
[6],后续我们还会遇到一些环境变量和GitHub定义的变量。secrets
的设置在仓库的Settings - Security - Secrets - Actions
找到,例如${{ secrets.SIGNING_KEY }}
我们需要对应创建一个SIGING_KEY
的secret。
这一步的alias
等就不赘述了,就是前面提到的签名的时候使用的几个参数。重点讲一下signingKeyBase64
,前面提到,签名需要用到这个keystore,但是这属于不能公开的内容(否则谁都可以用你的key来给应用签名),因此这个key不能存在公开的仓库中。这个workflow的作者使用base64对key进行编码(base64[7]是一种可以将任意二进制数据转化成文字的编码方法),通过secrets
传进workflow中,避免key的泄露。这个base64的生成方法如下:
> openssl base64 < some_signing_key.jks | tr -d '\n' | tee some_signing_key.jks.base64.txt
发布
- run: mv ${{steps.sign_app.outputs.signedReleaseFile}} LittleGooseOffice_$GITHUB_REF_NAME.apk
- uses: ncipollo/release-action@v1
with:
artifacts: "*.apk"
token: ${{ github.token }}
generateReleaseNotes: true
这里还是走了两步,第一步是将上一步生成的文件改一个名,第二部才是创建一个Release
。
第一步的run
是一个新鲜东西,表示直接在工作区运行一个命令,这里使用一个mv
命令来给生成的apk改个名,顺便移到最外面去。还记得上面提到的step的id吗,这里就用到了。${{steps.sign_app.outputs.signedReleaseFile}}
表示sign_app
这一步的outputs.signedReleaseFile
这个变量,是生成的签名apk的目录。这里还有一个新玩意,就是$GITHUB_REF_NAME
。这个是Github定义的变量,其值为“触发workflow的分支或tag名称”[8]。我们希望生成的apk文件命名为LittleGooseOffice_v1.0.2.apk
,所以做这么一个改名的操作。
第二步也比较简单,创建一个Release
,这里artifact
表示要包含到Release Assets
中的文件,*.apk
就能搞定;token
这里填${{ github.token }}
,这也是一个内置的变量,会使用一个临时的token来创建Release
;最后的generateReleaseNotes
能够自动生成一些变化列表之类的内容。
到这里,这个action就已经大功告成了。接下来我们体验一下如何用gitflow实现自动构建发布。
扬帆起航!
在每次完成一个版本的开发后,我们执行以下操作:
> git tag v1.0.0
> git push --tags
可以看到在Actions
页签出现了一个workflow run,如图。(这里由于workflow已经完成,所以是绿色,如果是刚提交的,应该是黄色。)
等待workflow完成后,前往Release,应该就能看到如下。
这时候,用户点击Assets
中的apk就可以下载到我们最新的包啦!
- 0
- 0
-
赞助
微信赞赏码 -
分享