利用重签机制实现多渠道分发

背景是公司的产品需要为私有部署客户提供私有API版本的移动客户端。在 Android 客户端中,需要根据不同的客户配置不同的 server host。现有的方案是在 Jenkins 上搭建了一个自动化打包流程,给实施工程师的同事们提供了一个可视化的配置页面,填写对应客户的自定义 host,然后写入环境变量。在 Android 的构建打包过程中,读取存好的值写入 manifest 文件以实现动态的 host 配置。

这的确是一个可行方案,但缺点是,每次都要跑完一次完整的 build 过程,当前项目的规模构建平均时间大概在12分钟左右。然而遇到版本升级,需要给私有部署客户升级客户端时,其实都是同一个版本,不同的仅仅是 manifest 中的配置,重新构建打包耗费了大量不必要的时间,影响了实施同事的工作效率。

如果仅仅是更改 manifest 文件,就可以利用反编译和重签机制了。脚本使用 Python 编写,使用到的工具有 apktoolapksinger和 Python 的 xml 库xml.etree.ElementTree

修改原来的 Android build 脚本,首先从阿里云查询是否已经存在当前要构建的版本,如果没有就走原来的重新打包流程,结束后上传至阿里云归档。如果有就走新的重签机制。

Step 1 反编译 APK

从阿里云下载已有 apk 到 Jenkins 服务器。

apktool d -s your_app.apk

使用 apktool 反编译 APK,-s是保留src,因为只是修改 manifest 文件,跳过代码部分可以节省一些反编译的时间。

Step 2 修改 AndroidManifest.xml

import xml.etree.ElementTree as ET

ET.register_namespace('android', "http://schemas.android.com/apk/res/android")
tree = ET.parse("your_app/AndroidManifest.xml")
root = tree.getroot()
application = root.find('application')
for meta_data in application.findall('meta-data'):
if meta_data.get('{http://schemas.android.com/apk/res/android}name') == 'field.want.to.replace':
meta_data.set('{http://schemas.android.com/apk/res/android}value', newValue)
tree.write('your_app/AndroidManifest.xml', 'utf-8', True)

值得注意的是需要给 ElementTree 注册 Android 的命名空间,否则最后写出来的文件android命名空间都会变成namespace1

Step 3 重新打包签名

apktool b -o unaligned_app.apk your_app
zipalign -f 4 unaligned_app.apk unsigned_app.apk
apksigner sign --ks keystore.jks --ks-key-alias your_alias --ks-pass pass:you_key_store_password --key-pass pass:your_key_password signed_app.apk

替换了新的方案以后,整个打包流程 Android 构建的部分从原先的12分钟缩减到了平均不到1分钟的水平,极大的提升了打包效率。