When customizing Gradle plugins, always expect the plugin to pass some specific parameters to the plugin so that they can be dynamically configured as needed. This makes the plug-in more general, flexible, and applicable, so it is important to understand the creation of extended attributes.

To achieve dynamic configuration of parameters, the plug-in needs to provide Extension properties externally, that is, to create an Extension for the Project. Extensions are created using the Api of the ExtensionContainer class. Let’s take a closer look at the creation of extended attributes.

1. Common ways to create extended attributes


The steps to create an extended property are as follows:

  1. Create an extension class: to define configuration items, that is, fields that users can configure and methods that can be invoked
  2. Use ExtensionContainer’s Api to create extensions for target objects (i.e., Project objects) : When using the plug-in, you can configure extension properties in the build.gradle file.

With the steps above, you can create your own extension properties for your plug-in.

Here is a simple case to illustrate:

1. Simple extended attributes

  • (1) Create an extension class:

    open class BigImageConfig {
    
        /** * Whether to allow detection */
        var checkEnabled: Boolean = true
    
        /** * Large image size */
        var maxSize: Long = 1024 * 1024
    }
    Copy the code

    The important thing to note here is that an extension class cannot be final, that is, it must be inheritable, otherwise it cannot create an extension property.

  • (2) Create extensions using ExtensionContainer

    class CheckBigImagePlugin : Plugin<Project> {
    
        override fun apply(project: Project) {
            // Create configuration items
            project.extensions.create("bigImageConfig".1 / / parameter
                                      BigImageConfig::class.java)2 / / parameter}}Copy the code

    Use the create method of ExtensionContainer to create an extension property.

    • Parameter 1: the name of the extended attribute, which can be specified at will. It is recommended that the root extension class be consistent to facilitate identification
    • Parameter 2: The class type of the extension class

    If the default constructor of the BigImageConfig extension class takes parameters, you need to pass the required parameters to the constructor. You can use the create overload of ExtensionContainer to pass the parameters, such as:

    open class BigImageConfig(
    		/** * Whether to allow detection */
        var checkEnabled: Boolean = true) {/** * Large image size */
        var maxSize: Long = 1024 * 1024
    }
    / / the plugin
    class CheckBigImagePlugin : Plugin<Project> {
    
        override fun apply(project: Project) {
            // Create configuration items
            project.extensions.create("bigImageConfig".1 / / parameter
                                      BigImageConfig::class.java,2 / / parameter
                                     true)// The argument passed to the BigImageConfig constructor}}Copy the code

    This allows you to pass arguments to the extension class.

  • (3) Use of extended attributes

    By following the steps above, you can create an extension named bi gImageConfig. When using this plugin, you can add this configuration to the build.gradle file, such as:

    bigImageConfig {
        checkEnabled = true
        maxSize = 1024
    }
    Copy the code

    The configuration information above is the field defined in the extension class, so to add additional configuration information, you can add more fields in the extension class.

    In addition to the equal sign assignment, you can also directly use key-value pairs to remove the equal sign, as in:

    bigImageConfig {
        checkEnabled true
        maxSize 1024
    }
    Copy the code

    So whether you’re using a key-value pair or you’re using an equal sign, you’re going to end up calling the setter for the property.

  • (4) The acquisition of extended attributes

    After the third step, we configure our extension properties. How do we get these extension properties in the plug-in? This can be obtained from the Project property() method, such as:

    class CheckBigImagePlugin : Plugin<Project> {
        // Configuration information
        private lateinit var imageConfig: BigImageConfig
      
        override fun apply(project: Project) {
            // Create configuration items
            project.extensions.create("bigImageConfig", BigImageConfig::class.java)
            // Get the configuration information
            imageConfig = project.property("bigImageConfig") as BigImageConfig
        }
    }
    Copy the code

    As shown in the above code, you get the specified extension property by passing in the name of the extension property(that is, the name passed in when the extension property was created) through the Project property() method.

    Of course, if the plug-in is developed in Groovy, you can also use Project directly. Method that extends the attribute name to get an extended attribute, such as:

    // Get the configuration information
    def imageConfig = project.bigImageConfig
    Copy the code

After the above steps, we know three things:

  • Creating an extended attribute
  • Using extended attributes
  • Get extended attributes

Points to note:

  • Extension classes must be inheritable, not inheritablefinaltype

2. Other ways to create extended attributes

We know from the example above that extensions can be created using the create method of ExtensionContainer. What other methods can be used to create extension properties for ExtensionContainer? Sure, we can take a look at ExtensionContainer’s main Api:

public interface ExtensionContainer {

    /** * Add an extended attribute *@paramName Name of the extended attribute *@paramExtension Class object *@throws IllegalArgumentException When an extension with the given name already exists
     */
    void add(String name, Object extension);

    /** * create an extended property *@paramName Name of the extended attribute *@paramType Extension class type *@paramConstructionArguments * required arguments to the constructor of an extension class@returnReturns the extension object */ created
    <T> T create(String name, Class<T> type, Object... constructionArguments);

    /** * Look for the specified extended attribute by name. If not found, throw an exception */
    Object getByName(String name) throws UnknownDomainObjectException;

    /** * Look for the specified extended property by name. If not found, return null without throwing an exception */
    @Nullable
    Object findByName(String name);
}
Copy the code

Part of the Api for the ExtensionContainer class is captured above, and as you can see from the class declaration above, in addition to creating extensions through the create method, you can also create extension properties through the add method.

Such as:

class CheckBigImagePlugin : Plugin<Project> {
  
    override fun apply(project: Project) {
        // Create configuration items
        project.extensions.add("bigImageConfig", BigImageConfig::class.java)
    }
}
Copy the code

In DefaultConvention, the ExtensionContainer implementation class, you can see the implementation of the add method:

@Override
public void add(String name, Object extension) {
    if (extension instanceofClass) { create(name, (Class<? >) extension); }else{ addWithDefaultPublicType(name, extension); }}Copy the code

As you can see, if you are passing an extension object, you add it directly, if you are passing a Class Class type, you create it through the create method

3, summarize

Summary of how to create extended attributes:

  1. This is done through the create method of ExtensionContainer
  2. Create it through the add method of ExtensionContainer

You can do it either way, but the difference is:

  • Similarities:

    • You can configure it by key-value pairs, you can configure it by equal sign, and all you end up doing is calling the setter method for the property.

    • Throw an exception: An exception is thrown when the extended property to be created already exists, i.e. the extended property is repeated.

  • Difference:

    • The create method returns the creation of the extension object, while the add method does not;

2. Extended attribute nesting


The nested extension property means that within the extended property, there are extended properties, as if within the entity class, there are references to other entity classes, such as:

android {
    defaultConfig {
        vectorDrawables.useSupportLibrary = true
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }

    viewBinding {
        enabled = true
    }
    dataBinding {
        enabled = true}}Copy the code

Android is the outermost configuration, defaultConfig, buildTypes, viewBinding, etc. It’s an extended property that’s nested in Android, so there’s one level of nesting, there’s multiple levels of nesting, so release is a two-level nesting.

There are a number of ways to nest extended attributes like the one above.

  1. In the constructor of the outer object, use the ExtensionContainer API to create the extension properties
  2. In the outer class, define a method with the name of the configuration item, arguments for the Closure, and generic arguments for the nested extension class
  3. In the outer class, define a method with the name of the configuration item, the parameter Action, and the generic parameter nested extension class

Detailed use is as follows

1. Use the ExtensionContainer API to create extension properties

When the outer extension property is created, the nested extension property is created synchronously using the ExtensionContainer API. That is, in the constructor of the outer extension class, the extension property is created, such as:

Extension classes:

// The outer extension class
class User {

    String name = ""

    int age = 10

    User() {
      // The nested extension properties are created when the User object is created
        extensions.create("friendConfig".// The name of the nested extension attribute
                          Friend.class.// The class type that nested the extended attribute
                          "Friends are Zhang SAN.")// Nest the construction parameters of the extended class}}// Nested extension classes
class Friend {

    String friendName = "ss"

    Friend(String friendName) {
        this.friendName = friendName
    }
}
Copy the code

In the plug-in, create an extension property:

project.extensions.create("userConfig", User.class)
Copy the code

Configuration code:

userConfig {
    name  "Zhang"
    age 20
    friendConfig {
        friendName = "Tom"}}Copy the code

Test code:

task test2 {
    doLast {
        println "${project.userConfig.name}" // The configured user name can be obtained normally
        println "${project.userConfig.friendConfig.friendName}" // You can also get the name of your friend}}Copy the code

Printed information:

Zhang SAN Tom

Note: Extension properties created through ExtensionContainer’s API can omit the equal sign of the property, such as:

userConfig {
    ...
    friendConfig {
        friendName  "Tom" // Omit the equals sign. This is more like configuration. You omit the equals sign, but you still call the setter method for the property}}Copy the code

2. In the outer class, define a method with the name of the configuration item, arguments for the Closure, and generic arguments for the nested extension class

The steps for creating an extended property in this way are:

  1. Creates an object with nested extension classes
  2. Defines a configuration method named: the name of the configuration item, optionally specified, taking closures as arguments and nested extension classes as generic arguments

2-1. Create an object

  • When the property is defined directly, the new object is added

    class User {
    
        String name = ""
    
        int age = 10
      
      // Create a nested extension class object
        Friend friend = new Friend()
    }
    Copy the code
  • Gradle’s ObjectFactory class creates an extension class object, such as:

    class User {
    
        String name = "jj"
    
        int age = 10
      
        Friend friend
    
        User(Project project) {
          // Create an extension class object with the ObjectFactory
            friend = project.objects.newInstance(Friend)
        }
    }
    
    class Friend {
    
        String friendName = "ss"
    
      // The constructor
        @Inject Friend() {
        }
    }
    Copy the code

    It is important to note that when you create an extension class using ObjectFactory, you need to add @inject to the constructor of the extension class. Otherwise, an exception will be raised.

2-1. Create a configuration method

Create a configuration method. The method name is the name of the extension property configured in the configuration file. The method name can be defined as your preference.

class User {

    String name = ""

    int age = 10
  
  // Create a nested extension class object
    Friend friend = new Friend()

  // 2. Define a configuration method with a preferred method name, a parameter as a closure object, and a generic type as an extension class type
    void friendConfig(Closure<Friend> closure) {
      // Create a configuration for an object with the ConfigureUtil class
        org.gradle.util.ConfigureUtil.configure(closure, 
                                                friend)// Extend the object}}Copy the code

The method name is friendConfig, which is the same name used during configuration.

Parameter: Closure Closure

Implementation: Execute the closure argument directly by calling gradle’s configure method ConfigureUtil, passing the created extension object.

The configuration code is as follows:

userConfig {
    name  "Zhang"
    friendConfig {
        friendName = "Tom"}}Copy the code

Note:

  • For ease of use, it is best to create an extension object with the same name as the configuration method, so that configuration and fetching are consistent. For example:

    class User {
      // Extend the object
        Friend friendConfig = new Friend()
    
      // Configuration method
        void friendConfig(Closure<Friend> closure) {
            org.gradle.util.ConfigureUtil.configure(closure, friendConfig)
        }
    }
    Copy the code
  • When configuring an extended property created in this way, do not omit the equal sign, that is,

    userConfig {
        friendConfig {
            friendName "Tom" // This mode cannot be used. An error is reported}}Copy the code

3. In the outer class, define a method with the name of the configuration item, the parameter Action, and the generic parameter nested extension class

This method is similar to the second method, but there are some differences in the implementation of the configuration method, as follows:

class User {

    String name = "jj"
    int age = 10
    User(Project project) {
      // Create an extension object
        friend = project.objects.newInstance(Friend)
    }
  
    Friend friend 
  
  // Configuration method
    void friendConfig(Action<Friend> action) {
        action.execute(friend) // Execute the execute method of the action argument directly, passing in the extension object}}Copy the code

Configuration code:

userConfig {
    name  "Zhang"
    friendConfig {
        friendName = "Tom"}}Copy the code

By replacing the parameter of the configuration method with an Action object and executing the Action’s exectute method, everything else remains the same. When you configure, you can’t omit the equal sign

4, summarize

  1. When configuring a nested extension attribute created using the API of ExtensionContainer, you can omit the equal sign. When configuring an extension attribute created by defining the configuration method, you cannot omit the equal sign
  2. When an extension object is created using the ObjectFactory method, the constructor of the extension class must be annotated with the @inject annotation
  3. The extended attribute name and the configuration method name should be the same for easy use

Extended attributes of the container type


We must have used a buildTypes configuration for Android, like this:

buildTypes {
    release {
        minifyEnabled true
        proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
    }
    debug {
        minifyEnabled false
        proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'}}Copy the code

This type can be used to create new objects of the specified type in a code block.

First look at the source code:

public void buildTypes(Action<? super NamedDomainObjectContainer<BuildType>> action) {
    this.checkWritability();
    action.execute(this.buildTypes);
}
Copy the code

It passes in the closure code for a BuildType list of types.

NamedDomainObjectContainer is a container, traced back it is inherited from the Collection < T >. We call it the named object container, which can be used to create objects in buildScript that must have a name attribute to identify the elements in the container.

How do you get a container object like this? Let’s look at the Project container method:

<T> NamedDomainObjectContainer<T> container(Class<T> var1);
<T> NamedDomainObjectContainer<T> container(Class<T> var1, NamedDomainObjectFactory<T> var2);
<T> NamedDomainObjectContainer<T> container(Class<T> var1, Closure var2);
Copy the code

The following examples will be introduced.

1. Example introduction

Creating an extension property of a container type is similar to creating a normal extension property. There are two steps:

  1. Define the container extension class
  2. Creating an extension Object
  3. Add an extension object to an Extension

The details are as follows:

1-1. Define the container extension class

First create a container-type extension class PhoneConfig. This class must have a name attribute:

class PhoneConfig {

    String name

    String color

    float price

    PhoneConfig(String name) {
        this.name = name
    }
}
Copy the code

Note that the name field must be present.

1-2. Create container extension objects

Create a container extension object using the Project container method, such as:

// Create a container object using the project container method
NamedDomainObjectContainer<PhoneConfig> phoneConfig = project.container(PhoneConfig.class)
Copy the code

1-3. Create container extension properties

Add the container extension object you created to extension. For example:

project.extensions.add("phoneConfig", phoneConfig)
Copy the code

1-4. Complete code

The complete code is as follows:

class User {

    String name = ""
    int age = 10
    
    User(Project project) {
        friend = project.objects.newInstance(Friend)
      // Create a container object
        NamedDomainObjectContainer<PhoneConfig> phoneConfig = project.container(PhoneConfig.class)
      // Add the container object to Extensions
        project.extensions.add("phoneConfig", phoneConfig)
    }

    Friend friend

    void friendConfig(Action<Friend> action) {
        action.execute(friend)
    }
}
Copy the code

Configuration code:

userConfig {
    name "Zhang"
    
    phoneConfig {
        iphone {/ / name
            color = "Red" // The equal sign cannot be omitted
            price = 10000
        }
        xiaomi {
            color = "Yellow"
            price = 5000}}}Copy the code

Obtain configuration information:

task test2 {
    doLast {
        println "${project.userConfig.name}"
      // Get the phoneConfig directly from the project object
        project.phoneConfig.forEach { phone ->
            println "name=${phone.name},color=${phone.color},price=${phone.price}"}}}Copy the code

The following information is displayed:

Name =xiaomi,color= yellow,price=5000.0

We noticed that all of the container configurations above have to be equal, otherwise it will report an error, so is there any way to do it without equal? Of course there is. Just define an assignment method for the extension class with the same name as the attribute. For example:

class PhoneConfig {

    String name

    String color

    float price

    PhoneConfig(String name) {
        this.name = name
    }

  // Define a configuration method with the same name as the property
    def color(String color) {
        this.color = color
    }
}
Copy the code

Configuration code:

userConfig {
    name "Zhang"
    phoneConfig {
        iphone {// This is the name of the item in the container.
            color  "Red"
            price = 10000
        }
        xiaomi {
            color  "Yellow"
            price = 5000}}}Copy the code

If you define an assignment method with the same name as the attribute, you can omit the equals sign, such as the color attribute. If the price attribute does not define an assignment method with the same name, you cannot omit the equals sign.

The properties of the container created here are not stored in the outer extension class. If you want to access the container class in the outer class, you need to store the container object in the outer class. For example:

class User {
		NamedDomainObjectContainer<PhoneConfig> phoneConfig 
  
    User(Project project) {
      // Create a container object
        phoneConfig = project.container(PhoneConfig.class)
      // Add the container object to Extensions
        project.extensions.add("phoneConfig", phoneConfig)
    }
}
Copy the code

Access:

task test2 {
    doLast {
        println "${project.userConfig.name}"
      // Get his property phoneConfig object through userConfig
        project.userConfig.phoneConfig.forEach { phone ->
            println "name=${phone.name},color=${phone.color},price=${phone.price}"}}}Copy the code

That’s it for extended properties.

Iv. Summary:

1. How to create extended attributes

  • Use the create or add methods of ExtensionContainer to create extension properties

2. How to create extended attribute nesting

  • Created using ExtensionContainer
  • Create nested properties by defining configuration methods

3. How container extensions are created

  • Using the Project container method, create the container object and add the created extension object to the ExtensionContainer.