详解Android variants(变体)

6 阅读7分钟

想象你是在一个汽车工厂工作:

  1. 你的 App 就是一辆汽车。

  2. buildTypes (构建类型):  这指的是你生产汽车的 目的 或 阶段

    • debug (调试):  这是你工厂内部测试用的样车。它装满了诊断工具(Logcat 日志、调试器连接)、没有做任何优化(方便快速修改和测试)、甚至可能有些特殊标识(比如应用名后面加 _debug)。生产速度,但不适合给客户。
    • release (发布):  这就是真正要卖给客户的量产车。它去掉了所有测试工具、经过了严格优化(代码混淆 minifyEnabled、资源压缩 shrinkResources)、性能更好、体积更小、安全性更高(可能做了代码签名)。生产速度,但质量高。
    • 你还可以自定义其他类型,比如 staging (预发布),用于给测试团队做最后测试,配置介于 debug 和 release 之间。
  3. productFlavors (产品风味):  这指的是你生产汽车的 不同版本或型号。它们共享同一个核心底盘和引擎(你的核心 App 代码和资源 src/main),但有不同的配置、功能或外观

    • 例子 1:免费版 vs 付费版 (free vs paid)

      • free:可能包含广告 SDK、某些高级功能被锁定或移除、应用图标上有个 “Free” 角标。
      • paid:没有广告、解锁所有功能、可能有专属主题或图标。
    • 例子 2:不同客户定制 (customerA vs customerB)

      • customerA:使用 A 公司的品牌颜色、Logo、后端 API 地址。
      • customerB:使用 B 公司的品牌颜色、Logo、不同的后端 API 地址。
    • 例子 3:不同分发渠道 (googleplay vs amazon)

      • 可能需要集成不同的应用内支付 SDK 或处理不同的商店政策。
    • 核心思想:  productFlavors 让你能用同一套核心代码,生成多个不同版本的 App。

  4. 变体 (Variants):buildTypes × productFlavors

    • 这才是真正神奇的地方!变体就是 buildTypes 和 productFlavors 组合 的结果。

    • 每个可能的组合都会生成一个独特的 App 版本 (APK/AAB)。

    • 命名规则:  [flavor][BuildType] (例如:freeDebugfreeReleasepaidDebugpaidRelease)。在 Android Studio 的 Build Variants 窗口里你会看到这些名字。

    • 如何组合架构?

      • 想象一个表格:

        构建类型 \ 产品风味free (免费版)paid (付费版)
        debug (调试)freeDebugpaidDebug
        release (发布)freeReleasepaidRelease
      • 你的构建系统 (Gradle) 会自动为表格中的 每一个格子 创建一个构建任务和最终的输出包。

  5. 具体如何编译(组合源代码和资源)?
    Gradle 在编译一个特定的变体(比如 paidRelease) 时,会按照一个优先级顺序合并来自不同地方的代码、资源和配置 (AndroidManifest.xml)。理解这个“覆盖”机制是关键:

    1. 主源集 (main):  这是基础。它包含所有变体共享的代码、资源、AndroidManifest.xml。就像一个公共的零件仓库。

    2. 构建类型源集 (buildTypes):  例如 src/debug 或 src/release。这里的文件会覆盖或补充 main 中的同名文件。比如:

      • debug/AndroidManifest.xml 里可以添加 android:debuggable="true"
      • release/java/ 下可以放一些只在发布版本需要的工具类(虽然更常见做法是用 build.gradle 条件判断)。
    3. 产品风味源集 (productFlavors):  例如 src/free 或 src/paid。这里的文件同样会覆盖或补充 main 中的文件,并且覆盖 buildTypes 源集中的文件(如果存在同名冲突)。

      • free/res/values/strings.xml 可以定义 app_name 为 “MyApp (Free)”。
      • paid/res/values/colors.xml 可以定义品牌主色调为金色。
      • paid/java/ 下可以放付费版独有的功能类。
    4. 变体源集 (variants):  这是最具体的层级。例如 src/freeDebug 或 src/paidRelease。这里的文件优先级最高,会覆盖前面所有层级(mainbuildTypeflavor) 的同名文件。

      • 通常用得不多,但在极少数需要为某个特定组合(如 freeDebug) 做非常特殊调整时使用。
    5. 依赖项 (dependencies):  在 build.gradle 文件中,你可以声明某些依赖库只对特定的构建类型或产品风味生效

      dependencies {
          // 所有变体都依赖
          implementation 'com.android.support:appcompat-v7:28.0.0'
      
          // 仅 debug 变体依赖 (例如 LeakCanary)
          debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.7'
      
          // 仅 free 风味依赖 (例如某个广告 SDK)
          freeImplementation 'com.google.android.gms:play-services-ads:20.4.0'
      
          // 仅 paid 风味依赖 (例如某个支付 SDK)
          paidImplementation 'com.example.premium:payment-sdk:1.0.0'
      }
      

    编译流程简述(以 paidRelease 为例):

    1. Gradle 决定要构建 paidRelease 变体。

    2. 收集所有源文件:

      • 合并 main 的所有代码和资源。
      • 用 release 源集中的文件覆盖/补充 main 中同路径同名的文件。
      • 用 paid 源集中的文件覆盖/补充前面合并结果中同路径同名的文件(包括 release 覆盖过来的)。
      • 最后用 paidRelease 源集中的文件(如果有)覆盖/补充前面所有结果。
    3. 合并 AndroidManifest.xml 文件:同样遵循 main + release + paid + paidRelease 的覆盖顺序。

    4. 应用 build.gradle 中 release 构建类型的配置(混淆、压缩、签名配置等)。

    5. 应用 build.gradle 中 paid 产品风味的配置(如果有,比如 applicationIdSuffix)。

    6. 根据依赖声明,只包含 implementationreleaseImplementationpaidImplementation 的库(不包含 debugImplementation 或 freeImplementation)。

    7. 执行编译(Java/Kotlin -> DEX)、资源处理(AAPT2)、打包(APK/AAB)、签名(对于 release)等标准 Android 构建步骤。

    8. 输出最终的 paidRelease APK 或 AAB 文件。

  6. Flavor Dimensions (风味维度) - 更复杂的组合

    • 有时一个维度(比如 version)不够。你可能同时需要区分 version (免费/付费) 和 target (手机/平板) 或者 region (国内/国际)。

    • 风味维度 (flavorDimensions)  让你可以定义多个独立的分类标准

    • 定义维度:  在 build.gradle 的 android 块中声明维度名:

      flavorDimensions "version", "target"
      

      flavorDimensions 列表中位置靠后的维度,会覆盖靠前的维度!

    • 在维度下定义风味:

      productFlavors {
          free {
              dimension "version" // 属于 "version" 维度
              ...
          }
          paid {
              dimension "version"
              ...
          }
          phone {
              dimension "target" // 属于 "target" 维度
              ...
          }
          tablet {
              dimension "target"
              ...
          }
      }
      
    • 变体组合:  Gradle 会做笛卡尔积,组合所有维度下的每一个风味。上面的例子会产生:

      • freePhoneDebugfreePhoneRelease
      • freeTabletDebugfreeTabletRelease
      • paidPhoneDebugpaidPhoneRelease
      • paidTabletDebugpaidTabletRelease
    • 源集目录:  源集目录可以更细化,例如:

      • src/freePhone/ (覆盖 free/ 和 phone/)
      • src/paidTabletRelease/ (覆盖所有)
  7. 为什么需要这个机制?

    • 高效管理多版本:  避免为每个小变体维护一个完全独立的项目。
    • 代码复用最大化:  共享核心逻辑 (main),差异化部分隔离 (flavorsbuildTypes)。
    • 自动化构建:  Gradle 自动处理所有组合的构建任务。
    • 隔离配置:  不同环境(开发/生产)、不同客户/版本(免费/付费)的 API 密钥、服务器地址、功能开关、资源等严格分开,避免配置错误。
    • 定制化发布:  轻松为不同应用商店、不同客户群体打包不同的 APK/AAB。

总结:

  • buildTypes:  控制 App 的构建目的 (开发调试 vs 发布上线),影响优化、调试能力和签名。
  • productFlavors:  定义 App 的不同版本或定制 (免费版/付费版/客户A版/客户B版),允许代码、资源和配置的差异化。
  • 变体 (Variants):  是 buildTypes 和 productFlavors (可能跨多个维度 flavorDimensions) 的所有可能组合每个变体都是一个独立的、可构建的 App 版本。
  • 编译组合:  Gradle 按照 main < buildType < productFlavor < variant 的优先级顺序合并代码、资源和清单文件,并应用相应的构建配置和依赖。

通俗比喻终极版:

  • 想象你开了一家 T 恤衫工厂 (buildTypes × productFlavors)

  • buildTypes 是生产线:

    • sample 线:快速打样,布料普通,缝线松散(方便拆改),有内部标签 (debug)。
    • production 线:精工细作,用最好布料,缝线牢固,有精美吊牌 (release)。
  • productFlavors 是 T 恤的 设计款式

    • logoA: 印公司 A 的 Logo (free / 客户 A)。
    • logoB: 印公司 B 的 Logo (paid / 客户 B)。
    • vneck: V 领款式 (tablet 适配)。
    • crewneck: 圆领款式 (phone 适配)。
  • 变体 (Variants)  就是最终生产出来的具体 T 恤

    • logoA sample vneck (freeDebug tablet): 给公司 A 看的 V 领样品衫。
    • logoB production crewneck (paidRelease phone): 卖给公司 B 的圆领量产衫。
  • 组合过程:  工人(Gradle)拿到订单(构建 paidRelease phone):

    1. 拿一件基础白T (main)。
    2. 送到 production 线:按量产标准缝好 (release 配置)。
    3. 送到 logoB 设计组:印上 B 公司 Logo (paid 资源/代码)。
    4. 送到 crewneck 设计组:做成圆领 (phone 适配/代码)。
    5. (如果有 paidReleasePhone 特别设计组,最后再加工一下)。
    6. 打包发货 (输出 APK/AAB)。