文章

Android 打包与签名深度解析——跨平台开发者必知必会

面向跨平台开发者的 Android 打包与签名全指南,涵盖 APK vs AAB、签名机制演进、ProGuard vs R8 混淆配置、多渠道打包策略及 Google Play 签名管理,帮助 Flutter/RN/鸿蒙开发者避开签名打包的那些坑。

Android 打包与签名深度解析——跨平台开发者必知必会

一句话概括

Android 打包与签名是应用从开发到发布的最后一道工序,它决定了你的 App 能否被用户设备信任、能否顺利上架商店、以及如何在多个渠道间高效分发;对于跨平台开发者来说,理解 APK/AAB 的区别、签名机制的演进和混淆配置,是避免上线事故和兼容性问题的必修课。

背景与意义

你可能有过这样的经历:在 Flutter 或 React Native 项目中运行 flutter build apkreact-native run-android 一切正常,但当你准备提审应用商店时,突然遇到签名错误、包格式不对、或者 Google Play 要求上传 AAB 而你只有 APK。这时候再回头去补打包知识,往往会耽误大量时间。

从 Flutter 1.x 到现在的版本,跨平台项目生成的产物始终是 Android 平台的标准打包格式——APK 或 AAB。无论你使用的是什么前端框架,最终交付给用户和商店的是一个 Android 原生打包产物。这意味着,跨平台开发者必须理解 Android 的打包流程,才能正确地配置构建脚本、管理签名密钥以及处理发布版本的各种细节。

事实上,签名和打包对于跨平台开发者来说往往是一个”黑盒”。许多团队在项目初期不重视签名管理,导致后期换电脑、重装系统后找不到 keystore 文件,或者混淆规则配置不当导致第三方 SDK 在 Release 版本中神秘失效。更严重的是,签名密钥遗失可能导致已经上架的应用永远无法更新——这是一个不可逆的后果。

本文将系统性地解析 Android 打包与签名的方方面面,让跨平台开发者不再把签名打包当作”魔法”,而是能够从原理层面理解它,并在实际项目中从容应对。

核心知识点拆解

APK vs AAB:两种打包格式的全面对比

APK(Android Package Kit)

APK 是 Android 平台最传统的打包格式,自 Android 诞生之初就一直使用。它是一个 ZIP 格式的压缩包,包含应用的所有资源、代码和清单文件。当用户从官网、应用商店或第三方渠道下载 APK 文件后,直接安装到设备上。

APK 的核心特点:

  • 包含所有资源:一个 APK 包需要适配所有设备密度、语言和架构的资源和代码,因此体积较大
  • 即时安装:下载即安装,不需要额外的分发流程
  • 灵活性高:可通过任何渠道分发,不受商店限制

对于跨平台开发者来说,flutter build apkcd android && ./gradlew assembleRelease 生成的就是 APK 文件。在开发调试阶段,你主要通过 APK 来安装应用。

AAB(Android App Bundle)

AAB 是 Google 在 2018 年推出的新一代发布格式,自 2021 年 8 月起,Google Play 要求所有新应用必须使用 AAB 格式上传。AAB 不是直接安装包,而是一个”元打包”格式,包含应用的模块化资源和代码。

AAB 的核心特点:

  • 分模块打包:AAB 将应用的代码、资源、原生库按模块组织,不包含最终 APK 的所有组合
  • 动态分发:Google Play 根据用户设备的屏幕密度、CPU 架构、语言版本等信息,为用户生成优化的 APK(称为 APK Split),用户只会下载所需的资源
  • 体积更小:平均可以减少 15%~20% 的下载体积

AAB 的工作原理可以这样理解:你上传 AAB 到 Google Play,Google 的服务器会根据你的 AAB 和用户的设备信息,生成多个 APK Split(基础 APK + 配置 APK + 模块 APK),用户只下载其中需要的部分。

对于跨平台开发者的实际影响

在 Flutter 项目中,如果你想生成 AAB,需要运行 flutter build appbundle。生成的产物位于 build/app/outputs/bundle/release/app-release.aab

需要注意的一点:AAB 不能直接在模拟器或设备上安装。如果你要在本地测试 AAB,需要使用 bundletool 工具将它转换成 APK。这也是很多跨平台开发者在首次接触 AAB 时容易踩的坑——上传到 Google Play 之前想本地测一下,发现 AAB 无法安装。

1
2
3
4
# 使用 bundletool 从 AAB 生成测试 APK
java -jar bundletool build-apks --bundle=app-release.aab --output=app.apks
# 安装到设备
java -jar bundletool install-apks --apks=app.apks

签名机制深度解析

为什么需要签名?

Android 签名机制的目的有两个:

  1. 身份认证:证明应用的开发者身份,避免应用被篡改后冒名发布
  2. 应用升级验证:只有签名相同的应用才能互相覆盖安装,防止恶意应用伪装成已有应用的更新版本

Android 的签名不是一次性的,而是对应用的所有内容进行摘要计算后加密,并将签名信息嵌入到 APK/AAB 中。用户在安装应用时,系统会验证签名是否有效。

签名方案的发展历程

Android 的签名方案经历了四个版本的演进,每一次升级都解决了上一版本的痛点并增强了安全性。

v1 签名方案(JAR 签名)

v1 签名是从 Java JAR 签名方案继承而来的,也是 Android 最早支持的签名方式。它的工作方式是:

  • 对 APK 中的每个文件分别计算 SHA1 或 SHA256 摘要
  • 将这些摘要信息写入 META-INF/MANIFEST.MF 文件中
  • 生成 META-INF/CERT.SF 文件,对 MF 文件进行签名
  • 生成 META-INF/CERT.RSA 文件,包含签名证书和签名数据

v1 签名的最大问题是安全性不足。因为它是基于 ZIP 条目级别的签名,APK 文件在签名后仍然可以修改——比如移除 META-INF/ 目录下的签名文件、修改文件内容后重新打包。这意味着攻击者可以对已签名的 APK 进行篡改而不影响安装过程。

此外,v1 签名的一个实际体验问题是:如果在 Android 7.0 及以上设备上仅使用 v1 签名安装应用,速度会非常慢,因为系统需要解压并验证每一个文件的摘要。

v2 签名方案(APK 签名方案 v2)

v2 签名方案在 Android 7.0(API 24)中引入,它采用全文件签名的方式:

  • 在 ZIP 中央目录(Central Directory)之前插入签名块(APK Signing Block)
  • 对 APK 文件的全部内容(除签名块和 ZIP 中央目录外的字节范围)计算摘要
  • 签名覆盖整个文件内容的完整性

v2 签名的优势非常明显:

  • 安全性更高:任何对 APK 的修改都会导致签名验证失败
  • 安装速度更快:使用 v2 签名的 APK 在 Android 7.0+ 设备上安装时不需要解压文件,系统可以直接读取签名块并行验证
  • 更高效:支持多线程并行验证

v2 签名的一个关键点是:它对 APK 文件内容的”保护字节范围”做了精确定义。签名覆盖的是从 APK 文件起始到 APK 签名块之前的所有内容,以及 ZIP 中央目录和 ZIP 尾部。这意味着签名块本身可以包含额外的数据(如 v3 签名的信息或签名者序列),而不会影响签名验证。

对跨平台开发者来说,需要特别注意:如果你的应用最低支持版本低于 Android 7.0(API 24),则必须同时使用 v1 + v2 签名,因为旧版本设备不支持 v2 签名验证。

v3 签名方案

v3 签名方案在 Android 9.0(API 28)中引入,它的核心改进是引入了密钥轮换(Key Rotation)机制:

  • 允许开发者在不改变应用包名的情况下更换签名密钥
  • 在签名块中记录密钥轮换的链式结构,包含新旧密钥的信息
  • 保留旧密钥对新密钥的签名证明

密钥轮换的意义在于:如果签名密钥泄露,或者企业并购后需要统一签名密钥,不需要重新上架一个新应用——用户可以通过 Google Play 的更新获得签名密钥更换后的更新包。

v3 签名之前,如果签名密钥遗失或泄露,唯一的解决方案就是使用新密钥签名并以新的包名上架——这意味着你将失去所有现有用户和评分。

v4 签名方案

v4 签名方案是 Android 11(API 30)中引入的最新签名方案,它主要为 增量安装(Incremental Installation,即 ADB 安装优化)服务:

  • 使用独立的 .idsig 文件存储签名信息,不嵌入 APK 中
  • 支持流式验证——设备可以在下载 APK 的同时进行签名验证,无需等待下载完成
  • 专为 Android 11 及以上设备的增量文件系统(Incremental File System)设计

v4 签名目前主要用于 Google Play 的动态交付功能和开发者的 ADB 调试场景,普通应用开发中很少直接接触。

跨平台开发者需要如何选择签名方案

在实际的 Flutter/RN 项目中,你通常不需要手动选择签名方案——build.gradle 中的默认配置已经合理。Android Gradle Plugin 默认会同时使用 v1 和 v2 签名。如果需要配置,可以在 build.gradle 中设置:

1
2
3
4
5
6
7
8
android {
    signingConfigs {
        release {
            v1SigningEnabled true
            v2SigningEnabled true
        }
    }
}

keystore 与签名配置实战

什么是 keystore?

keystore 是一个加密的密钥存储文件,它使用 Java 的 KeyStore 技术来保存密钥对(私钥 + 公钥证书)。通常以 .jks(Java KeyStore)或 .keystore 扩展名保存。

一个 keystore 文件可以包含多个别名(alias),每个别名对应一个独立的密钥对。当你的团队有多个应用或多个开发环境时,可以把它们放在同一个 keystore 文件中。

生成 keystore

在跨平台项目中,生成 keystore 的标准方式是使用 keytool 命令(JDK 自带):

1
2
3
4
5
6
7
keytool -genkey -v -keystore upload-keystore.jks \
        -alias upload-key \
        -keyalg RSA \
        -keysize 2048 \
        -validity 10000 \
        -storepass 密码 \
        -keypass 密码

参数说明:

  • -genkey:生成密钥对
  • -keystore:指定输出文件名
  • -alias:密钥别名,后续签名时使用
  • -keyalg RSA:加密算法,RSA 是 Android 的标准选择
  • -keysize 2048:密钥长度,2048 位是当前推荐的最小安全长度
  • -validity 10000:证书有效期(天数),约 27 年

Flutter 项目中的签名配置

在 Flutter 项目中,签名配置位于 android/app/build.gradle。一个标准的配置模板如下:

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
// 在 android/app/build.gradle 中
// 1. 将 keystore 文件放在 android/app/ 目录下
// 2. 在 android 块前创建一个 keystore 属性文件

// 从 keystore.properties 文件中加载密钥信息
def keystoreProperties = new Properties()
def keystorePropertiesFile = rootProject.file('key.properties')
if (keystorePropertiesFile.exists()) {
    keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
}

android {
    // ... 
    signingConfigs {
        release {
            keyAlias keystoreProperties['keyAlias']
            keyPassword keystoreProperties['keyPassword']
            storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null
            storePassword keystoreProperties['storePassword']
        }
    }
    buildTypes {
        release {
            signingConfig signingConfigs.release
        }
    }
}

同时创建一个 key.properties 文件(不要提交到 Git!):

1
2
3
4
storeFile=../android/app/upload-keystore.jks
keyAlias=upload-key
storePassword=你的存储密码
keyPassword=你的密钥密码

RN 项目中的签名配置

React Native 项目的签名配置类似,在 android/app/build.gradle 中配置 signingConfigs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
android {
    signingConfigs {
        release {
            storeFile file('release.keystore')
            storePassword '密码'
            keyAlias 'release-key'
            keyPassword '密码'
        }
    }
    buildTypes {
        release {
            signingConfig signingConfigs.release
        }
    }
}

混淆:ProGuard vs R8

混淆的目的

混淆(Obfuscation)主要做三件事:

  1. 缩小体积(Shrink):移除未使用的代码和资源
  2. 混淆代码(Obfuscate):将类名、方法名、属性名重命名为简短的无意义名称,增加逆向难度
  3. 优化字节码(Optimize):对字节码进行优化,如移除未使用的参数、合并冗余指令

对于跨平台开发者来说,混淆最闹心的事情莫过于:Release 版本应用崩溃了,但堆栈信息中的类名和方法名全是 a.a.b() 这种格式,完全无法定位问题。

ProGuard 与 R8 的区别

特性ProGuardR8
推出时间较早(2011年)Android Gradle Plugin 3.4+(2019年)
与 Gradle 集成作为可选项默认启用
性能较慢,需要单独运行更快,与编译过程深度集成
功能Shrink + Obfuscate + Optimize集成了 ProGuard 的全部功能 + 编译优化
配置语法兼容 ProGuard 规则兼容 ProGuard 规则,有少量扩展

从 Android Gradle Plugin 3.4 开始,R8 已成为默认的混淆和优化工具。你不需要额外的配置就能使用 R8。

混淆规则配置

混淆规则文件通常是 proguard-rules.pro(在 app/ 目录下)。对于跨平台开发者,以下是最常见的混淆配置场景:

保留原生方法(JNI)

-keepclasseswithmembernames class * {
    native <methods>;
}

保留通过反射调用的类

-keep class com.yourpackage.** { *; }

保留 Gson/Serialization 类

-keepclassmembers class * {
    @com.google.gson.annotations.SerializedName <fields>;
}
-keep class com.google.gson.** { *; }
-keep class * implements java.io.Serializable { *; }

保留 Flutter 引擎相关类

# Flutter 的引擎类是自动注册的,不需要额外 keep
# 但如果使用了第三方 Flutter plugin,需要查看其文档

保留日志输出(仅在 Debug 中)

-assumenosideeffects class android.util.Log {
    public static boolean isLoggable(java.lang.String, int);
    public static int v(...);
    public static int d(...);
    public static int i(...);
}

跨平台项目的混淆实战

在 Flutter 项目中默认不需要额外的混淆配置,因为 Flutter 引擎本身是用 Dart 编写的,在 Dart 层有自己的混淆机制。但 Flutter 集成到的 Android 原生代码(如 platform channel 的调用)仍然需要混淆规则。

一个常见的坑是:Flutter 项目中使用 MethodChannel 调用原生端,但原生端的代码被 R8 混淆导致调用失败——解决方案是在 proguard-rules.pro 中保留平台通道相关类。

# 保留 MethodChannel 相关
-keep class io.flutter.plugin.** { *; }
-keep class io.flutter.embedding.** { *; }
-keep class io.flutter.view.** { *; }

多渠道打包

什么是多渠道打包?

多渠道打包是指使用一套代码同时构建出针对不同渠道或环境的应用包。常见的场景包括:

  • 不同应用商店:华为、小米、OPPO、应用宝等,每个渠道需要不同的配置
  • 不同环境:测试环境、预发布环境、生产环境
  • 不同版本类型:免费版 vs 付费版、国内版 vs 海外版

productFlavors 使用

productFlavors 是 Android Gradle Plugin 提供的维度化打包功能。它可以为同一份代码生成多个变体(Variant),每个变体可以有自己的包名、版本号、资源文件、依赖等。

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
android {
    // 口味维度(用于多维度的产品变体组合)
    flavorDimensions "version", "channel"
    
    productFlavors {
        // 第一个维度:版本
        free {
            dimension "version"
            applicationIdSuffix ".free"
            versionNameSuffix "-free"
        }
        paid {
            dimension "version"
            applicationIdSuffix ".paid"
            versionNameSuffix "-paid"
        }
        
        // 第二个维度:渠道
        googleplay {
            dimension "channel"
        }
        huawei {
            dimension "channel"
        }
    }
}

上述配置会生成所有组合:freeGoogleplayReleasefreeGoogleplayDebugpaidGoogleplayRelease 等一共 8 个变体。

在 Flutter 中使用构建变体

在 Flutter 项目中,你可以通过 --flavor 参数选择构建变体:

1
2
3
4
5
# 构建 free + googleplay 的 Release 版本
flutter build apk --flavor freeGoogleplay --release

# 或指定目标变体
flutter build apk --flavor free --target-platform android-arm,android-arm64

需要注意的是,Flutter 项目只有在 Android 原生层配置了 productFlavors 后,才能使用 --flavor 参数。如果没有配置,运行 flutter build --flavor 会报错。

多渠道打包的常见方案

除了 productFlavors,还有一些其他的多渠道打包方案:

  1. AndroidManifest 占位符:在 AndroidManifest.xml 中使用 ${channel} 占位符,在 build.gradle 中为不同渠道设置不同的值

  2. 多渠道打包工具(如 Walle):美团开源的 Walle 方案,通过在 APK 的 APK Signing Block 中插入渠道信息,无需重新打包即可实现多渠道分发

  3. Gradle Build Config Fields:通过 buildConfigField 定义编译时常量,在代码中根据渠道执行不同逻辑

1
2
3
4
5
6
7
8
productFlavors {
    googleplay {
        buildConfigField "String", "CHANNEL", "\"googleplay\""
    }
    huawei {
        buildConfigField "String", "CHANNEL", "\"huawei\""
    }
}

Google Play 签名管理

传统签名模式 vs Play App Signing

在 Google Play 推出应用签名服务之前,开发者用一个签名密钥签署所有版本。如果这个密钥丢失,整个应用将无法更新——你的用户将永远停留在旧版本上。

Play App Signing 是 Google Play 提供的一项免费服务,它将签名过程分成两个角色:

  1. 上传密钥(Upload Key):开发者用来签署应用并上传到 Google Play 的密钥
  2. 发布密钥(App Signing Key / 发布密钥):Google Play 实际用来签署分发到用户设备的应用的密钥

当你开启 Play App Signing 后,工作流程变为:

  1. 你使用上传密钥签署应用并上传到 Google Play
  2. Google Play 验证上传密钥后,使用发布密钥重新签署应用
  3. 最终用户下载的是由发布密钥签署的应用

为什么跨平台开发者需要关注 Play App Signing

密钥丢失不是玩笑。Google Play 客服不会帮你恢复丢失的密钥。但有了 Play App Signing,即使上传密钥丢失,你仍然可以通过开发者账户验证身份后请求重置上传密钥。

密钥迁移灵活。如果你的团队有多个应用需要统一签名密钥,或者公司并购后需要统一签名签署,Play App Signing 提供了便捷的管理界面。

开启 Play App Signing 的注意事项

  1. 首次开启不可逆:一旦启用了 Play App Signing,就无法关闭
  2. 保留发布密钥:即使在 Play App Signing 模式下,Google 仍建议你妥善保管发布密钥的备份(用于 Google Play 之外的第三方商店分发)
  3. 密钥加密导出:你可以在 Google Play Console 中加密导出发布密钥,用于其他分发渠道

实战案例

案例一:Flutter 项目从 APK 迁移到 AAB

背景:某 Flutter 团队一直在使用 APK 分发应用,应用体积约 50MB。随着用户增长,对应用下载转化率的影响越来越明显。

解决方案

  1. flutter build 命令中增加 AAB 支持:
    1
    2
    
    # 生成 AAB
    flutter build appbundle --release
    
  2. 使用 bundletool 进行本地测试: ```bash

    下载 bundletool

    https://github.com/google/bundletool/releases

    java -jar bundletool.jar build-apks
    –bundle=build/app/outputs/bundle/release/app-release.aab
    –output=app.apks
    –ks=upload-keystore.jks
    –ks-pass=pass:密码
    –ks-key-alias=upload-key
    –key-pass=pass:密码

安装到设备

java -jar bundletool.jar install-apks –apks=app.apks

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
3. 在 Google Play Console 中验证 AAB 生成的对照表格,确认所有支持设备都有对应的 APK Split。

**结果**:实际下载体积从 50MB 降至约 32MB,下载转化率提升了 22%。

**踩坑记录**:
- Flutter 项目的 `res/drawable-v24` 目录中的矢量图在 AAB 模式下可能导致构建失败,需要检查 `build.gradle` 中的 `vectorDrawables` 配置
- 第三方 SDK 的 `.so` 库需要在 AAB 中正确配置 `abiFilters`

### 案例二:React Native 应用混淆后崩溃的排查

**背景**:一个 RN 项目在 Release 版本中频繁崩溃,Debug 版本完全正常。崩溃堆栈中只有 `a.b.c()` 这类混淆后的方法名。

**排查过程**:

1. 在 `android/app/build.gradle` 中启用 R8 的 mapping 文件保存:
```groovy
android {
    buildTypes {
        release {
            minifyEnabled true
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
            // 保存 mapping 文件
            proguardFile 'mapping.txt'
        }
    }
}
  1. 混淆后生成的 mapping 文件位于 build/outputs/mapping/release/mapping.txt

  2. 使用 retrace 工具还原混淆堆栈:
    1
    2
    3
    
    # retrace 工具位于 Android SDK 的 tools/proguard/ 目录下
    # 或者使用 proguard 的 retrace
    java -jar proguard/lib/retrace.jar mapping.txt crash-stacktrace.txt
    
  3. 还原后发现崩溃发生在某个第三方推送 SDK 的回调中。添加保留规则:
    -keep class com.thirdparty.push.** { *; }
    -keep class * extends com.thirdparty.push.PushCallback { *; }
    

教训:每次使用新的第三方 SDK 后,不要等上线发现问题再添加混淆规则,应该在集成阶段就检查其文档中的混淆规则要求。

案例三:Flutter 多渠道打包实战

背景:一个 Flutter 项目需要同时发布到 Google Play 和华为 AppGallery,两个渠道使用不同的推送 SDK 和不同的应用内支付方案。

实现方案

  1. 配置 productFlavors
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    
    android {
     flavorDimensions "distribution"
        
     productFlavors {
         googleplay {
             dimension "distribution"
             applicationId "com.example.app"
         }
         huawei {
             dimension "distribution"
             applicationId "com.example.app"
         }
     }
    }
    
  2. 创建不同渠道的源代码集:
    1
    2
    3
    4
    
    app/src/
    ├── main/java/...        # 公共代码
    ├── googleplay/java/...  # Google Play 渠道代码
    └── huawei/java/...      # 华为渠道代码
    
  3. 在 Flutter 侧通过 Channel 获取当前渠道: ```dart import ‘package:flutter/services.dart’;

class AppConfig { static const _channel = MethodChannel(‘com.example.app/config’);

static Future get flavor async { return await _channel.invokeMethod('getFlavor'); } }

1
2
3
4
5
4. 构建不同渠道的包:
```bash
flutter build appbundle --flavor googleplay
flutter build apk --flavor huawei

常见问题

Q1:keystore 文件丢失了怎么办?

如果在未开启 Play App Signing 的情况下丢失了签名密钥,你的应用将永久无法更新。你只能使用新的密钥以新的包名发布应用,用户无法从旧版本升级。

解决方案:

  • 提前开启 Play App Signing,这样丢失的只是上传密钥,可以在 Play Console 申请重置
  • 将 keystore 备份到密码管理器或加密云存储中(如 1Password、Bitwarden 的附件功能)
  • 使用 CI/CD 系统(如 GitHub Actions)统一管理签名密钥

Q2:AAB 能不能在本地安装测试?

不能直接安装。AAB 不是 APK,设备无法识别。你需要使用 bundletool 将 AAB 转换成可安装的 APK 文件集:

1
java -jar bundletool build-apks --bundle=app-release.aab --output=app.apks

关于 bundletool 的获取:它托管在 GitHub 的 google/bundletool 仓库下,可以从 Release 页面下载最新的 jar 包。

Q3:为什么 Release 版本应用启动后闪退,而 Debug 正常?

这是混淆导致的最常见问题。排查步骤:

  1. 检查 build.gradle 中是否开启了 minifyEnabled true
  2. 查看 mapping 文件(位于 build/outputs/mapping/release/mapping.txt
  3. 使用 retrace 工具还原崩溃堆栈
  4. 找到被混淆破坏的类,在混淆规则中保留

常见原因:

  • 使用反射调用的类被混淆
  • JSON 反序列化的模型类被混淆
  • JNI 方法对应的 C++ 侧类名混淆
  • 第三方 SDK 的回调接口被混淆

Q4:Flutter 项目的 AAB 构建失败,提示 “Failed to find build tools”

检查 Flutter 项目 android/app/build.gradle 中的 compileSdkVersion 和 Android SDK 的安装。确保:

  • Android SDK Build-Tools 已安装(通过 SDK Manager)
  • compileSdkVersion 对应的 SDK 版本已安装
  • Gradle 版本和 Android Gradle Plugin 版本兼容

Q5:多渠道打包时 productFlavors 名称和代码中常量如何对应?

使用 BuildConfig 类(在 Java/Kotlin 代码中自动生成):

1
2
3
// 在代码中访问渠道信息
String channel = BuildConfig.FLAVOR;
// 如果使用了 flavorDimensions,BuildConfig.FLAVOR 会包含维度组合

在 Flutter 中,可以通过 platform channel 将 BuildConfig.FLAVOR 传递给 Dart 层。

总结

对于跨平台开发者来说,Android 打包与签名不应该是一个黑盒。理解 APK 和 AAB 的区别,掌握签名方案的演进(v1→v2→v3→v4),正确配置混淆规则,以及合理利用多渠道打包和 Play App Signing,能够帮助你:

  1. 减少上线事故:提前发现并解决签名、混淆相关的问题
  2. 提升应用体验:利用 AAB 减少用户下载体积,提升转化率
  3. 保障应用安全:通过正确的签名管理防止应用被篡改
  4. 提高发布效率:合理使用多渠道打包策略,一次构建多端分发

最重要的是管理好你的签名密钥——使用密码管理器备份、CI/CD 系统来管理,而不是把它放在某个人的电脑硬盘里。签名密钥丢失是 Android 开发中最常见也是最不可逆的错误之一。

最后记住一句话:Debug 版本不出问题不代表 Release 版本没问题——在提审之前,务必测试 Release 构建产物,这是跨平台开发者最容易忽视但最不应该跳过的步骤。

本文由作者按照 CC BY 4.0 进行授权