Android productFlavors多版本、多环境打包

1,389 阅读8分钟

序言

项目开发过程中,经常会打不同debug、release版本的测试包。不同的logo、app名字,给不同的公司配置不同的环境变量甚至不同的实现。这时候可以利用在gradle中配置productFlavors实现。

一、productFlavors介绍

产品风味是 Android 开发中的 Gradle 功能,允许您创建应用程序的不同变体,使您的开发过程更易于管理。此功能使您能够在使用相同的核心代码库的同时针对不同目的自定义应用程序。以下是产品口味的详细信息:

为什么使用它们?

对于不同版本:产品风格允许您针对不同版本自定义应用程序。例如,您可以创建免费和高级版本或不同的变体,例如不同的语言或主题。

单一代码库:产品风格使您能够创建不同的变体,同时有效地使用相同的代码库,从而更轻松地管理和维护代码。

简化分发:您可以使用产品风格来简化分发,而不是为不同版本创建单独的项目。这使得更新和维护更加简单。

您可以定制什么?

资源文件:您可以为每个产品风格定义自定义资源文件和布局。例如,您可以包含不同语言或不同布局的文本。

依赖关系:您可以为不同的产品风格指定不同的依赖关系。例如,您可以在一个版本中使用一个库,而在另一版本中使用不同的库。

清单文件:为每个产品风格自定义 AndroidManifest.xml 文件。这对于为不同版本添加不同的权限、功能或活动非常有用。

构建配置:您可以为不同的产品风格定义自定义构建配置。例如,您可以使用不同的服务器 URL 或密钥。

如何使用它们?

。如下:

// app/build.gradle

android {
    namespace 'xxx'
    compileSdk libs.versions.compileSdk.get().toInteger()
    defaultConfig {
        applicationId "com.xxx.xxx.xx"
        minSdk libs.versions.minSdk.get().toInteger()
        targetSdk libs.versions.targetSdk.get().toInteger()
        versionCode 1
        versionName "1.0.0"

        vectorDrawables {
            useSupportLibrary true
        }
    }
    ...

    buildTypes {
        release {
            minifyEnabled true
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
            //Zipalign优化
            zipAlignEnabled true
            signingConfig signingConfigs.config
            ndk {
                abiFilters 'armeabi-v7a', 'arm64-v8a'
            }
        }

        debug {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            zipAlignEnabled false
            signingConfig signingConfigs.config
            ndk {
                // You can customize the NDK configurations for each
                // productFlavors and buildTypes.
//                abiFilters 'armeabi', 'x86'
                abiFilters 'arm64-v8a'
            }
            matchingFallbacks = ['release']
        }

    }

    productFlavors {
        flavorDimensions = ["company"]
        companyA {
            dimension "company"
            applicationIdSuffix ".companyA"
            resValue "string", "app_name", "xxx-companyA"
            buildConfigField("String", "COMPANY", "\"companyA\"")
        }
        companyB {
            dimension "company"
            applicationIdSuffix ".companyB"
            resValue "string", "app_name", "xxx-companyB"
            buildConfigField("String", "COMPANY", "\"companyA\"")
        }
    }

    // 打包路径配置
    applicationVariants.all { variant ->
        println("===buildConfig: [${variant.flavorName}_${variant.buildType.name}_${variant.versionName}]")
        variant.outputs.all { output ->
            def outputFile = output.outputFile
            if (outputFile != null && outputFile.name.endsWith('.apk')) {
                outputFileName = "xxx_app_${variant.flavorName}_${variant.buildType.name}_${variant.versionName}_${new Date().format("yyyy-MM-dd")}.apk"
                println("===newOutputFile: ${outputFileName}")
            }
        }
    }
}

dependencies {
    ...
    // 向自定义逻辑发布变体依赖项
    companyAImplementation project(':companyAModule')
    companyBImplementation project(':companyBModule')
}

注意事项:需要删除 res/values/strings.xmlapp_name的资源配置,否则gradle生成的app_name String资源和默认配置的app_name资源冲突会报错。

另外还可给不同的flavor配置 manifestPlaceholders

上面基于不同的buildTypeproductFlavor可以排列组合出多个风格的Build variant。包括如下:

  • companyADebug
  • companyARelease
  • companyBDebug
  • companyBRelease
自定义依赖项解析策略

一个项目可能会依赖于同一个库的两个不同版本,这样会导致依赖项冲突。例如,如果您的项目依赖于模块 A 的版本 1 和模块 B 的版本 2,而模块 A 以传递方式依赖于模块 B 的版本 3,则会出现依赖项版本冲突。

为了解决此冲突,Android Gradle 插件使用以下依赖项解析策略:当插件检测到依赖项关系图中存在同一模块的不同版本时,默认情况下,它会选择版本号最高的一个。

不过,此策略可能并不总是如您所愿。如需自定义依赖项解析策略,请使用以下配置解析任务所需的特定变体依赖项:

  • variant_nameCompileClasspath:此配置包含适用于给定变体编译类路径的解析策略。
  • variant_nameRuntimeClasspath:此配置包含适用于给定变体运行时类路径的解析策略。

Android Gradle 插件包含可用于访问每个变体的配置对象的 getter。因此,您可以使用变体 API 查询依赖项解析,示例所示:

android {
    applicationVariants.all { variant ->
        // Return compile configuration objects of a variant.
        variant.getCompileConfiguration().resolutionStrategy {
        // Use Gradle's ResolutionStrategy API
        // to customize how this variant resolves dependencies.
            ...
        }
        // Return runtime configuration objects of a variant.
        variant.getRuntimeConfiguration().resolutionStrategy {
            ...
        }
        // Return annotation processor configuration of a variant.
        variant.getAnnotationProcessorConfiguration().resolutionStrategy {
            ...
        }
    }
}

创建源代码集

除了可以为各个productFlavorbuildType创建源代码集目录之外,您还可以为产品变种的每个组合创建源代码集目录。例如,您可以创建 Java 源代码并将其添加到 src/companyADebug/java/ 目录中,只有在构建将这两个产品变种组合在一起的变体时,Gradle 才会使用这些源代码。

您为产品变种组合创建的源代码集的优先级高于属于各个产品变种的源代码集。如需详细了解源代码集以及 Gradle 如何合并资源,请参阅关于如何创建源代码集的部分。

Android Gradle 插件提供了一项有用的 Gradle 任务,可向您展示如何整理每个 build 类型、产品变种和 build 变体的文件。例如,任务输出中的以下示例描述了 Gradle 希望在何处能够找到“debug”build 类型的某些文件:

------------------------------------------------------------
Project :app
------------------------------------------------------------

...

debug
----
Compile configuration: debugCompile
build.gradle name: android.sourceSets.debug
Java sources: [app/src/debug/java]
Kotlin sources: [app/src/debug/kotlin, app/src/debug/java]
Manifest file: app/src/debug/AndroidManifest.xml
Android resources: [app/src/debug/res]
Assets: [app/src/debug/assets]
AIDL sources: [app/src/debug/aidl]
RenderScript sources: [app/src/debug/rs]
JNI sources: [app/src/debug/jni]
JNI libraries: [app/src/debug/jniLibs]
Java-style resources: [app/src/debug/resources]

如需查看此输出,请按以下步骤操作:

  1. 点击工具窗口栏中的 Gradle。

  2. 依次前往 MyApplication > Tasks > android,然后双击 sourceSets。

    如需查看 Tasks 文件夹,您必须允许 Gradle 在同步期间构建任务列表。为此,请按照以下步骤操作:

    a. 依次点击 File > Settings > Experimental(在 macOS 设备上,依次点击 Android Studio > Settings > Experimental)。

    b. 取消选中 Do not build Gradle task list during Gradle sync。

  3. Gradle 执行完该任务后,系统会打开 Run 窗口以显示输出。

使用源代码集构建

您可以让源代码集目录包含您希望只针对某些配置打包在一起的代码和资源。例如,如果您要构建“companyADebug”build 变体(“companyA”产品变种和“debug”build type的混合产物),Gradle 会查看这些目录,并为它们指定以下优先级:

  1. src/companyADebug/(build 变体源代码集)
  2. src/debug/(build 类型源代码集)
  3. src/companyA/(产品变种源代码集)
  4. src/main/(主源代码集)

为产品变种组合创建的源代码集必须包含所有变种维度。 例如,build 变体源代码集必须是 build 类型及所有变种维度的组合。不支持合并涉及的文件夹涵盖多个(但并非全部)变种维度的代码和资源。

如果您将多个产品变种组合在一起,那么这些产品变种的优先级由它们所属的变种维度决定。使用 android.flavorDimensions 属性列出变种维度时,属于您列出的第一个变种维度的产品变种的优先级高于属于第二个变种维度的产品变种,依此类推。此外,您为产品变种组合创建的源代码集的优先级高于属于各个产品变种的源代码集。

优先级顺序决定了 Gradle 组合代码和资源时哪个源代码集的优先级更高。由于 demoDebug/ 源代码集目录很可能包含该 build 变体特有的文件,因此如果 demoDebug/ 包含的某个文件在 debug/ 中也进行了定义,Gradle 会使用 demoDebug/ 源代码集中的文件。同样,Gradle 会为 build 类型和产品变种源代码集中的文件指定比 main/ 中的相同文件更高的优先级。在应用以下构建规则时,Gradle 会考虑这种优先级顺序:

  • kotlin/java/ 目录中的所有源代码将一起编译以生成单个输出。
  • 所有清单都将合并为一个清单。按照前面示例中列表项的顺序指定优先级。也就是说,build 类型的清单设置会替换产品变种的清单设置,依此类推。如需了解详情,请参阅清单合并
  • values/ 目录中的文件也会合并在一起。如果两个文件同名,例如存在两个 strings.xml 文件,则按照前面示例中列表项的顺序指定优先级。也就是说,在 build 类型源代码集的文件中定义的值会替换在产品变种的同一文件中定义的值,依此类推。
  • res/asset/ 目录中的资源会打包在一起。如果在两个或更多个源代码集中定义了同名的资源,则按照前面示例中列表项的顺序指定优先级。
  • 在构建应用时,Gradle 会为库模块依赖项随附的资源和清单指定最低优先级。