Android Studio:Gradle 产品风味:定义自定义属性

Android Studio: Gradle Product Flavors: Define custom properties

我正在 Gradle(Android Studio)中构建 Android 应用程序的不同产品风格。

因此我定义了以下产品口味:

android {

    project.ext.set("customer", "")
    project.ext.set("server", "")

    //Configuration happens here - code removed for readability

    buildTypes {

        debug {
            server = "test"
        }

        release {
            server = "release"
        }
    }

    //Available product flavors
    productFlavors {
        customerA{
            customer = "a"
        }
        customerB{
            customer = "b"
        }
        customerC{
            customer = "c"
        }
    }
}

但是,稍后,当我在我的一个构建任务中访问定义的项目 属性 "customer"(其值在我当前正在构建的产品风格中设置)时,它总是有值 "c" 即使我正在构建客户 A(在这种情况下 属性 客户应该是 "a" 而不是 "c")。例如我稍后执行以下任务:

preBuild << {
    println "Building customer: " + customer
}

它总是打印:

Building customer: c

所以我猜发生了一些覆盖?可能与配置 VS 执行阶段有关?虽然不确定 how/why,因此非常感谢您的帮助。

更新:或者,它已经让我进一步确定产品风味的名称(没有附加构建类型名称)和构建类型(再次:没有在 gradle 构建的执行阶段期间添加到它前面的产品风味名称。

考虑到上述配置,预期的产品口味名称将是:customerA、customerB 和 customerC。

在评估阶段,Gradle 执行您的 android 块中的所有代码;它不只是执行与您要编译的风格相关的代码。事实上,在评估阶段,它甚至不知道你的口味是什么;它必须对其进行评估才能找出答案。

所以你的所有三行 customer = "a"customer = "b"customer = "c" 都将被执行。

这是 Gradle 的微妙之处之一,这使得学习起来有点困难。

所以我已经解释了为什么你的代码没有按照你期望的方式工作,但是这个答案是不完整的,因为我没有说很多关于如何做才能让它正常工作,但很难说该怎么做,因为我不确定您要完成什么。一般来说,我可以说你应该考虑尝试使用用户定义的任务来完成你想要的,并设置任务内依赖关系以确保事情以正确的顺序执行。 Android Gradle 构建的一个陷阱是,即使这些任务在评估阶段之前也不会被定义(在评估构建文件并知道之前,它无法知道构建所有风格所需的任务这些风格是什么),所以做一些 SO 调查,看看如何将事情挂钩到 Android Gradle 构建任务上——你必须在 [=22] 之后的评估阶段结束时设置你的任务=] 插件已经完成了它的工作。

非常感谢 Scott Barta,感谢他的建议和解释,为什么我的解决方案不起作用(这也让我重新考虑了一些事情)。我基本上想出了不同的方法来完成我需要的。

除非你需要做的事情不能通过简单地根据构建类型和风格(即通过约定)组织你的 Android 资源树来实现,否则我会推荐选项 2。虽然我确实保留了选项 1 仅供参考,因为它涵盖了 productFlavor 属性 扩展的有趣主题。

  1. 基于自定义 属性 的选项:Product Flavors 允许您定义自定义属性,从而扩展 productFlavor。 Xavier Ducrohet 在这里提供了一个示例:

我将提供一个与上面提供的非常简单且相似的示例,但在我的例子中我需要一个字符串 属性,而不是布尔值。

    // This class will be used to create our custom property
    class StringExtension {
      String value

      StringExtension (String value) {
        this.value = value
      }

      public void setValue(String value) {
        this.value = value
      }

      public String getValue() {
        return value
      }
    }

    android {
      // Add our new property to each product flavor upon creation
      productFlavors.whenObjectAdded { flavor ->
        //I am suspecting the last argument is the default value
        flavor.extensions.create("myProperty", StringExtension , '')
      }

      // then we can set the value on the extension of any flavor object
      productFlavors {
        customerA{
          myProperty.value 'customerA'
        }
        customerB{
          myProperty.value 'customerB'
        }
      }
    }

    //Adds a custom action to the preBuild task
    preBuild << {
    //Iterate over all application variants. We name our application variant object "variant" as indicated by "variant ->"
        android.applicationVariants.all { variant ->
    //Here we can iterate over the flavors of our variant, well call the flavor "flavor" as indicated by "flavor ->"
            variant.productFlavors.each { flavor ->
    //Access our custom property "customerName"
                println "Building customer" + flavor.customerName.value

            }
        }
    }

然后我意识到,以上是完全没有必要的,因为我想要的只是我的口味的名称(其中没有构建类型),一旦我找到 属性 给我的名称我的口味,我能够按如下方式更改以上所有代码:

  1. 通过访问名为 "name".

    的已经存在的产品风味 属性,只需将您的风味名称用作客户名称
    android {
    
      productFlavors {
        customerA{
        }
        customerB{
        }
      }
    }
    
    //Adds a custom action to the preBuild task
    preBuild << {
    //Iterate over all application variants. We name our application variant object "variant" as indicated by "variant ->"
        android.applicationVariants.all { variant ->
    //Here we can iterate over the flavors of our variant, well call the flavor "flavor" as indicated by "flavor ->"
            variant.productFlavors.each { flavor ->
    //Access our product flavor name
                println "Building customer" + flavor.name
    
            }
        }
    }
    

以上内容也更有意义,因为我的 Android 资源目录结构是根据实际风格命名的。

后者也让我找到了对原始问题的最终解决方案:

  1. 基于资源目录的方法

目的是根据每个客户的 xml 文件夹是发布版本还是调试版本修改文件。这可以通过相应的文件夹结构来实现。根据最初的问题,我们有 3 个客户,每个客户都有一个调试版本和一个发布版本。上述 xml 文件对于每个客户和构建类型都是不同的。因此,以下目录结构:

src/
  - customerA
    //Contains all relevant resource files specific to customer A
  - customerB
    //Contains all relevant resource files specific to customer B
  - customerC
    //Contains all relevant resource files specific to customer C

  - customerADebug
    //Contains debug server-settings file for customer A
  - customerBDebug
    //Contains debug server-settings file for customer B
  - customerCDebug
    //Contains debug server-settings file for customer C
  - customerARelease
    //Contains release server-settings file for customer A
  - customerBRelease
    //Contains release server-settings file for customer B
  - customerCRelease
    //Contains release server-settings file for customer C

所以每个产品风味的主要内容都在与风味同名的文件夹中(客户 A、客户 B 等。请参阅上面代码段的第一部分)。现在,根据每个客户是调试版本还是发布版本,将这个文件放入适当的文件夹中,例如 customerADebug --> 包含带有调试模式等服务器设置的文件。

例如,当您构建 customerA 时,如果您构建调试或发布版本,则会选择正确的文件。

回答我 post 的更新部分:

产品风格名称(不含 buildType):

flavor.name (where flavor is a productFlavor)

以下方法对我有用,可以将自定义属性添加到产品口味中:

android {
    // ...defaultConfig...

    productFlavors.whenObjectAdded { flavor ->
        // Add the property 'myCustomProperty' to each product flavor and set the default value to 'customPropertyValue'
        flavor.ext.set('myCustomProperty', 'customPropertyValue')
    }

    productFlavors {
        flavor1 {
        }
        flavor2 {
            myCustomProperty = 'alternateValue'
        }
    }
}

flavor1 具有自定义 属性 的默认值,而 flavor2 具有覆盖值。

这是一个如何访问自定义 属性:

的示例
applicationVariants.all { variant ->
    // Get the 'myCustomProperty' property from the variant's productFlavor (it's a list, but there should only be one)
    def customProp = variant.productFlavors*.myCustomProperty[0]
}

我假设同样可以将自定义属性添加到构建类型,但我还没有对此进行测试。