Android 使用 Gradle 命令行签名 APK

本文主要探讨如何使用 Android 项目包含的 gradlew 命令行程序签名 APK 文件. 这同时适用于 Debug 和 Release 等多个 variants. 以下以 Linux/OS X 环境为例.

笔者编译环境为 Gradle 8.0+ 和 Kotlin Gradle Settings, 对于 Groovy Gradle Settings 可能无等效方案.

原理与主要思路

在 Android 项目根目录下执行如下命令行

1
./gradlew assemble

即可以生成所有 variants 对应的 APK 文件, 并根据 variant 名称分别存放于 app/build/outputs/apk/<variant> 中. 一般默认生成的 Android 项目包含 Debug 和 Release 两个 variants, 也可以分别使用

1
./gradlew assembleDebug

1
./gradlew assembleRelease

生成对应 variant 的 APK.

但是在默认的 Gradle 配置中, 如此生成的 APK 文件是未签名的. 例如 Release variant 生成的 APK 文件默认会包含后缀名 “unsigned”. 若需要对其签名, 则需要传入对应的选项 (option) 实现.

本文选择在应用build.gradle.kts 中添加签名所需的选项, 并利用 Kotlin 语言 (相较于 Groovy) 的优势分离出敏感信息. 这样无需修改命令行指令即可编译出签名文件, 同时在不包含签名密钥的情况下也可以编译出未签名文件, 不影响编译命令原本的输出结果.

build.gradle.kts 中控制签名的选项主要是 android.signingConfigsandroid.buildTypes.<variant>.signingConfig. 前者用于指定密钥数据库位置, 密码等敏感信息作为一个 signing config, 后者用于指定该 variant 使用何种 signing config.

实现

设置密钥信息

假定存储敏感信息的文件名为 credentials.properties. 我们选择利用 java.util.Properties 读取该文件. 在该文件中写入类似如下的内容:

1
2
3
4
STORE_FILE=/path/to/keystore.jks    # Key store 路径
STORE_PASSWORD=password # Key store 密码
KEY_ALIAS=MyApp # Key 别名 (alias)
KEY_PASSWORD=password # Key 密码

以上内容需要符合 Java Properties 的语法规则, 特别注意等号 (=) 等只要出现在键名之后则不需要特别转义, 但是反斜杠 (\) 需要特别转义为 \\.

build.gradle.kts 中指定密钥

打开应用build.gradle.kts, 在开头添加[^1]

1
2
3
4
5
6
7
8
9
import java.util.Properties

val f = rootProject.file("credentials.properties")
var credentials: Properties? = null
if (f.exists() && f.isFile) {
credentials = Properties().apply {
load(f.reader())
}
}

这段代码表示仅在 credentials.properties 文件存在时才读取并将其存入 credentials 变量中. 其他类似的写法皆可.

再在 android {} 语句块中添加如下内容[^2]:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
android {
// ...

if (credentials != null) {
signingConfigs {
create("release") {
storeFile = credentials!!["STORE_FILE"]?.let { file(it) }
storePassword = credentials!!["STORE_PASSWORD"].toString()
keyAlias = credentials!!["KEY_ALIAS"].toString()
keyPassword = credentials!!["KEY_PASSWORD"].toString()
enableV1Signing = true
enableV2Signing = true
enableV3Signing = true
enableV4Signing = true
}
}
}

buildTypes {
release {
// ...

if (credentials != null) {
signingConfig = signingConfigs.getByName("release")
}
}
}

// ...
}

这段代码在 credentials 正确读入密钥信息后设置相应的 signing config, 并开启 v1 至 v4 的应用签名方案, 随后设置仅对 Release 的 variant 签名.

如此, 使用 ./gradlew assembleRelease 命令即可生成签名后的 APK 了.

验证 APK 签名

使用 jarsigner 程序即可检查签名的有效性[^3]:

1
jarsigner -verify -verbose -certs /path/to/my/signed/apk.apk

如果输出中 CN= 后为自己的密钥的签发者, 则说明 APK 签名成功.

特别提醒

若使用 Git 等版本控制工具, 请务必将 credentials.properties 忽略掉!

参考

[^1]: https://stackoverflow.com/a/71934561/12002560.
[^2]: https://gist.github.com/mileskrell/7074c10cb3298a2c9d75e733be7061c2.
[^3]: https://stackoverflow.com/a/7104680/12002560.

Android 使用 Gradle 命令行签名 APK

https://blog.tamako.work/techdev/android/sign-apk-gradlew/

Posted on

2023-10-27

Updated on

2023-10-27

Licensed under

Comments