25、构建产物签名与分发:Google Play签名、内部分发平台、构建产物版本管理

说到构建产物的最后一步,其实就是两件事:签名分发。签名是为了证明这个APK是你写的,没被人动过手脚;分发则是让用户能装到手机上。这两件事看起来简单,但坑特别多。我见过不少团队,开发阶段跑得好好的,一到上架就崩,十有八九是签名配置出了问题。

25.1 签名的基础逻辑

Android要求所有APK必须经过数字签名才能安装。这个签名用的是证书,说白了就是一个公私钥对。你用私钥给APK打上标记,Google Play或者手机系统用公钥来验证这个标记是否有效。

这里有个关键点:签名证书的指纹(SHA1、SHA256)会作为应用的唯一标识。同一个包名,如果签名变了,系统会认为这是两个不同的应用。我之前接手过一个项目,前任把签名文件丢了,结果所有用户都得卸载重装——那叫一个惨。

⚠️ 警告:签名文件(.jks / .keystore)一定要备份!最好存到密码管理器或者公司内部的密钥管理服务里。丢了就等于丢了应用的身份。

25.2 Google Play签名(App Signing by Google Play)

Google Play在2017年推出了这个功能。它的核心思路是:你把上传用的密钥交给Google,Google再用另一个密钥去签最终分发给用户的APK

为什么要这么做?说白了就是安全。你想想看,如果你的上传密钥泄露了,攻击者最多能上传一个恶意版本到Google Play,但拿不到最终分发的签名密钥。Google那边会帮你保管好最终签名密钥,你只需要管好上传密钥就行。

配置方式很简单,在Google Play Console里开启就行。但要注意一点:一旦开启,就不能关闭。而且你必须在首次上传APK之前就决定好,因为Google需要你提供上传证书的指纹。

💡 我的建议:所有新项目都开启Google Play签名。它最大的好处是——如果你不小心丢了上传密钥,可以联系Google重置,而不用换包名。我有个朋友就遇到过这事,他当时差点崩溃,还好用了Google Play签名,最后只花了两周就恢复了。

Gradle配置示例:

android {
    signingConfigs {
        release {
            storeFile file("upload-keystore.jks")
            storePassword "your-store-password"
            keyAlias "upload"
            keyPassword "your-key-password"
        }
    }
    buildTypes {
        release {
            signingConfig signingConfigs.release
        }
    }
}

这里有个细节:不要把密码硬编码在build.gradle里。我习惯用gradle.properties或者环境变量来传。比如:

// gradle.properties
RELEASE_STORE_PASSWORD=xxx
RELEASE_KEY_PASSWORD=xxx

// build.gradle
storePassword System.getenv("RELEASE_STORE_PASSWORD") ?: project.findProperty("RELEASE_STORE_PASSWORD")

25.3 内部分发平台

不是所有APK都要上Google Play。测试版本、内部试用版、企业版,这些都需要一个内部分发渠道。

25.3.1 Firebase App Distribution

Firebase App Distribution是Google官方的内测分发工具。它跟Firebase生态深度集成,你可以在Firebase Console里管理测试人员名单,还能看到谁安装了哪个版本。

集成方式很简单,在build.gradle里加一个插件:

plugins {
    id 'com.google.firebase.appdistribution'
}

android {
    // ...
}

firebaseAppDistribution {
    appId = "1:123456789:android:abc123"
    releaseNotes = "修复了登录闪退的问题"
    groups = "qa-team"
}

然后执行:

./gradlew assembleRelease appDistributionUploadRelease

嗯,这里要注意:Firebase App Distribution要求你登录Google账号。在CI/CD环境里,你需要用服务账号来认证。我踩过这个坑,当时CI一直报401,查了半天才发现是服务账号权限没配好。

25.3.2 蒲公英(国内常用)

如果你在国内做分发,蒲公英是个不错的选择。它不需要测试人员注册Google账号,直接扫码或者点链接就能安装。蒲公英支持API上传,你可以把它集成到CI流程里。

蒲公英的上传API很简单:

curl -F "file=@app-release.apk" \
     -F "uKey=your_ukey" \
     -F "_api_key=your_api_key" \
     https://upload.pgyer.com/apk/v1/upload

我个人习惯在Gradle里写一个自定义task,把上传逻辑封装起来:

task uploadToPgyer {
    doLast {
        def apk = file("build/outputs/apk/release/app-release.apk")
        def uKey = project.findProperty("PGYER_UKEY")
        def apiKey = project.findProperty("PGYER_API_KEY")
        
        exec {
            commandLine "curl", "-F", "file=@${apk.absolutePath}",
                    "-F", "uKey=${uKey}",
                    "-F", "_api_key=${apiKey}",
                    "https://upload.pgyer.com/apk/v1/upload"
        }
    }
}
📌 提示:蒲公英免费版有文件大小限制(一般是100MB)。如果你的APK超过这个大小,要么用分体APK(Android App Bundle),要么升级付费版。

25.4 构建产物版本管理

版本管理这件事,看起来就是改个versionCode和versionName,但实际做起来很容易乱。我见过一个项目,versionCode直接从1写到了999,中间跳了好几百个数字,完全看不出哪个版本对应哪个发布。

我的做法是:用CI的构建号作为versionCode。比如Jenkins的BUILD_NUMBER,或者GitHub Actions的run_number。这样每个构建都有一个唯一的递增ID,不会重复。

Gradle配置示例:

android {
    defaultConfig {
        versionCode System.getenv("CI_BUILD_NUMBER")?.toInteger() ?: 1
        versionName "2.3.${versionCode}"
    }
}

versionName的格式我推荐用语义化版本:主版本.次版本.修订号。比如2.3.1表示主版本2,次版本3,第1次修订。每次发布新版本时,手动更新主版本或次版本,修订号自动用构建号填充。

还有一个容易被忽略的点:版本信息要能追溯到代码。我习惯在APK的meta-data里写入Git commit hash:

android {
    defaultConfig {
        buildConfigField "String", "GIT_COMMIT_HASH", "\"${getGitHash()}\""
    }
}

def getGitHash() {
    def process = "git rev-parse --short HEAD".execute()
    return process.text.trim()
}

这样出问题的时候,看一眼APK就能知道它对应哪个代码版本,不用去猜。

25.5 知识体系总览

下面这张图把签名、分发、版本管理的关系串起来了:

构建产物签名与分发流程 源代码 + 版本号 Gradle 构建 签名(APK/AAB) Google Play 签名 上传密钥 → Google 签名密钥 内部分发平台 Firebase / 蒲公英 企业签名 MDM / 内部部署 版本管理:versionCode(构建号) + versionName(语义化版本) 最终用户安装 构建流程 Google Play 内部分发

25.6 一些实战经验

最后分享几个我踩过的坑:

  • 签名文件不要放在Git仓库里。就算仓库是私有的,也不安全。我习惯把签名文件放到CI的密钥存储里,或者用公司的密钥管理服务。
  • versionCode不要手动改。手动改一定会出错。要么用CI构建号,要么用时间戳(比如20250315)。
  • 测试版和正式版的签名要分开。我见过有人把debug签名当成release签名上传到Google Play,结果安装失败。debug签名是Android Studio自动生成的,每台机器都不一样。
  • Firebase App Distribution的测试人员要提前加好。别等到要发版了才去加人,Google的权限同步有延迟,有时候要等十几分钟。

嗯,签名和分发这件事,说白了就是把正确的文件,用正确的方式,送到正确的人手里。流程搭好了,后面就省心了。


公众号:蓝海资料掘金营,微信 deep3321