Android 自动化编译之 Github Actions
Actions 是 Github 2019年推出的自动化集成编译工具,在漫长的 Beta 等待后我也在去年获得了优先体验,当时也写了 actions-android-ci 的第一个版本,用来编译 CallerInfo 项目。
最近在写一个私有项目,自动化 CI 自然是必不可少的,为了简单方便,托管在了 Github。使用去年编写的 [email protected] 很快完成了编译,同时增加了对每次编译 apk
和 mapping.txt
上传到 artifacts
的支持。
但是每次编译都需要将近 8 分钟,而每个月只有 2000 分钟的免费编译时长,因此减少编译时间很重要。查看编译的日志,发现有 30 秒在编译 docker 镜像,还有 6 分钟都在编译,其中大部分时间都在下载 gradle 依赖。
所以通过预编译 docker 镜像和增加 sdk 及 gradle 依赖缓存可以大幅减少编译运行时间。
集成编译优化过程
1. 使用预编译 docker 镜像
在 v1.2.1 版本, Github Actions 在每次运行时都会从 actions-android-ci 仓库拉取最新的代码,然后根据 action.yml
文件中的配置,编译新的 docker 镜像。由于这个镜像包括 openjdk 等依赖,每次编译都会去拉取大量数据并重新编译 docker image:
runs:
using: 'docker'
image: 'Dockerfile'
直接从 docker hub 拉起最新的镜像则可以减少每次制作新镜像的耗时:
runs:
using: 'docker'
image: 'docker://xdtianyu/actions-android-ci'
2. 增加 sdk 及 gradle 缓存
这个 docker 复用了 Gitlab CI,而当时为了优化在 Gitlab runner 中编译,已经增加了缓存的支持,只是存储在了 /opt/cache/gradle
/opt/sdk
/opt/ndk
几个目录下,在 Gitlab Runner 运行时是预先挂载了宿主机的这几个目录来实现 runner 缓存的。
而 Github 目前是不支持使用 docker volume
挂载参数的,所以需要将这几个目录设定在可以访问的目录下,最后缓存这几个目录。所以重新编写了下载 sdk 的脚本和环境变量:
echo $GITHUB_WORKSPACE
SDK=/opt/sdk
NDK=/opt/ndk
GRADLE=/opt/cache/gradle
if [ ! -z "$GITHUB_WORKSPACE" ]; then
SDK="$GITHUB_WORKSPACE/.opt/sdk"
NDK="$GITHUB_WORKSPACE/.opt/ndk"
GRADLE="$GITHUB_WORKSPACE/.opt/cache/gradle"
fi
# ... setup sdk ...
export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:$SDK/emulator/:$SDK/tools/bin:$SDK/tools:$SDK/platform-tools:$NDK:$SDK/cmake/3.10.2.4988404/bin
export ANDROID_HOME=$SDK
export ANDROID_SDK=$SDK
export ANDROID_SDK_ROOT=$SDK
export GRADLE_USER_HOME=$GRADLE
当有 GITHUB_WORKSPACE
环境变量时,说明当前运行环境为 Github Actions 环境,需要将 sdk 目录设定在 $GITHUB_WORKSPACE/.opt/sdk
目录下。完整内容请阅读代码 setup-android-sdk.sh
编译新的镜像并上传到 docker hub: xdtianyu/actions-android-ci (这里使用了 docker hub 的自动编译)。
然后在 .github/workflows/android.yml
编译前增加缓存配置:
- name: Cache gradle and sdk
uses: actions/cache@v2
env:
cache-name: cache-gradle-and-sdk
with:
path: |
${{ github.workspace }}/.opt/cache/gradle/wrapper
${{ github.workspace }}/.opt/cache/gradle/caches
${{ github.workspace }}/.opt/sdk
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/wrapper/gradle-wrapper.properties', '**/build.gradle') }}
restore-keys: |
${{ runner.os }}-build-${{ env.cache-name }}-
${{ runner.os }}-build-
${{ runner.os }}-
这样,每次编译时都会下载 .opt/sdk
等目录下的缓存文件,并在编译完成后检查缓存是否需要更新,如需更新则上传新的目录。
这里的缓存校验使用了 build.gradle
和 gradle-wrapper.properties
,如果这些文件被修改则说明需要更新缓存,否则跳过新建缓存的过程。可以根据自己需要调整这里的校验配置。
3. 加密的环境变量 Secrets
可以在项目 Settings -> Secrets
中配置加密的环境变量,这里配置的环境变量很安全,有特殊处理,不会在日志中显示。同时也不会传递由 Fork
发起的 Pull Request
。
我习惯性的使用了上图中的5个环境变量,用来将发布签名加密保存在代码仓库中。其中 ENCRYPTED_IV
和 ENCRYPTED_KEY
是使用 openssl 加密的 secrets.tar.enc
文件相关密钥。通过如下命令解密:
openssl aes-256-cbc -K $ENCRYPTED_KEY -iv $ENCRYPTED_IV -in secrets.tar.enc -out secrets.tar -d
如果不需要,可以在参考代码脚本中移除这部分内容。
在这个脚本中有详细的说明可以参考学习: .travis/env.sh
4. 增加 artifacts 上传
每次自动编译都会生成 apk 文件,可以将其和混淆文件一起发布出来提供下载。只需要增加如下 artifacts 配置即可完成对每次编译 apk
和 mapping.txt
文件上传。类似于 Gitlab job
,在每个 Action
编译成功后都会有一个 Artifacts
链接能用来下载:
- name: Upload artifacts
uses: actions/upload-artifact@v2
with:
name: artifacts
path: |
app/**/apk/release/*
app/**/mapping/release/mapping.txt
完整的配置文件请阅读 xdtianyu/actions-android-ci README 文件。
总结
我从事开发过程的一大乐趣就是写各种各样的自动化脚本,在工作中也承担了内部 Gitlab 及 CI 的搭建维护工作。在 CallerInfo 项目中,已经集成了各式各样的 CI 服务如:Travis-ci
、 Gitlab runner
、Jenkins
、AppVeyor
、Github Actions
,也集成过 CircleCI
等服务,但是担心过多的权限要求导致我的其他私有仓库泄漏,所以没有集成。
本文主要是对 Gtihub Actions
Android
应用编译缓存方面的实现,附带的介绍了加密环境变量及上报 artifacts
,没有涉及版本发布、自动化发布报告(sha1/commits/changes)和其他更多好玩的内容。这些内容已经在 Travis ci
上实现过了,就不再赘述,感兴趣的读者可以参考学习我的开源项目 CallerInfo 中的 .travis 目录和 .travis.yml 文件。
CI 平台大同小异,常见的几种平台中,AppVeyor
是在 Windows 宿主机上编译的,如果你也喜欢写自动化CI,建议也不要错过 AppVeyor。