提问者:小点点

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


我正在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"
        }
    }
}

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

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

它总是打印:

建筑客户:c

所以我猜有一些覆盖发生?可能与配置VS执行阶段有关?虽然不知道如何/为什么,所以非常感谢任何帮助。

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

考虑到上述配置,预期的产品风格名称将是:客户A、客户B和客户C。


共3个答案

匿名用户

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

因此,您的所有三行客户="a"客户="b"客户="c"都将被执行。

这是关于Gradle的微妙之处之一,这使得它有点难以学习。

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

匿名用户

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

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

  1. 基于自定义属性的选项:Product Flavors允许您定义自定义属性,从而扩展productFlavor。Xavier Ducrohet在此处提供了一个示例:https://stackoverflow.com/a/17708357/1041533

我将提供一个非常简单且类似的示例,如上所述,尽管在我的例子中我需要一个String属性,而不是布尔值。

    // 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

            }
        }
    }

然后我意识到,上面是完全不必要的,因为我想要的只是我的味道的名字(没有构建类型),一旦我找到了给我的味道的名字的属性,我就可以改变上面所有的代码如下:

只需通过访问名为“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资源目录结构是以实际风格命名的。

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

目的是根据每个客户的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等,参见上面片段的第一部分)。现在,这个文件,根据它是每个客户的调试还是发布版本而有所不同,被放入适当的文件夹中,例如CusterADebug-

例如,当您构建客户A时,如果您构建调试或发布构建,将选择正确的文件。

回答我帖子的更新部分:

产品风味名称(不带buildType):

flavor.name(其中风味是一种产品Flavor)

匿名用户

以下工作为我添加自定义属性的产品口味:

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'
        }
    }
}

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

以下是如何访问自定义属性的示例:

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]
}

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