“This article has participated in the good article call order activity, click to see: back end, big front end double track submission, 20,000 yuan prize pool for you to challenge!”

preface

Nice to meet you

As for Gradle learning, I understand the process as follows:

In the last article in this series, we covered customizing Gradle plug-ins, completing the third step. If you haven’t read the previous article, I suggest you read Gradle series (iii), Gradle plugin development.

Today we will introduce the third link: Gradle plug-in practical application

Github Demo address, you can combine with the Demo to see the effect of the bar drop 🍺

A review,

APT on Android (part 4) APT practical application, we made a layout optimization, a small amount of system control in Android is created by means of the new, and most of the controls such as androidx. Appcompat. Under the widget controls, custom controls, the third party controls, etc., are all created by reflection. A lot of reflection creation can cause performance problems, so we need to solve the problem of reflection creation. My solution is:

1. Get all the controls in the Xml layout by writing an Android plug-in

2. After getting the control, create a View class using new method through APT generation

Finally, get the current class by reflection and complete the replacement in the base class

The specific process of 1 is: through the Android plug-in to get all the control names in the Xml layout, and write to a. TXT file. Now that we know how to customize the Gradle plugin, let’s implement it.

Before that, we need to learn about Extension and growth, which will be used later

2. What is the Extension

1) What is Extension?

Extension in Chinese means to expand. Gradle can add a space configuration named like Android to the Gradle script file by implementing a custom Extension. Gradle can recognize this configuration and read the configuration contents. Take a familiar Android configuration as an example:

android {
    compileSdkVersion 30

    defaultConfig {
        applicationId 'com.dream.gradledemo'
        minSdkVersion 19
        targetSdkVersion 30
        versionCode 1
        versionName '1.0'

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

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

The above code can be configured this way because the Android Gradle Plugin defines these extensions

So how do I customize Extension?

Answer: Through ExtensionContainer

2) Customize Extension using ExtensionContainer

ExtensionContainer is similar to TaskContainer. In the previous article, we mentioned that TaskContainer is a container that manages tasks. Similarly, ExtensionContainer is a container that manages an Extension. You can use ExtensionContainer to manipulate an Extension. ExtensionContainer can also be obtained from the Project object:

// Currently in the app build.gradle file

// The following four methods are the same example
1 / / way
extensions
2 / / way
project.extensions
3 / / way
getExtensions()
4 / / way
project.getExtensions()
Copy the code

There are two ways to create an extension using ExtensionContainer:

1. Create an Extension using the Create series method of ExtensionContainer

Create an Extension using the Add series method of ExtensionContainer

3) Create an Extension using the Create series method of ExtensionContainer

First take a look at the create series of methods provided by ExtensionContainer:

As you can see from the screenshot above, it has three reload methods, which we will cover one by one

1, the first overload method

Parameter description:

S: Indicates the name of the Extension to be created. The name can be any string that conforms to the naming rules. It cannot be the same as the existing one, or an exception will be thrown

AClass: An object of type Class for this Extension

Objects: The value of the constructor argument for the current class. This parameter is optional. If left unfilled, it takes the default value

2, the second overload method

Parameter description:

AClass: The Class object exposed by the Extension instance created. Normally we would specify the Class object of the parent Class

S: Indicates the name of the Extension to be created. The name can be any string that conforms to the naming rules. It cannot be the same as the existing one, or an exception will be thrown

AClass1: This Extension implements an object of type Class

Objects: The constructor parameter value of the implementation class. This parameter is optional. If left unfilled, it takes the default value

3, the third overload method

Parameter description:

TypeOf: The typeOf object exposed by the Extension instance created. Normally, we specify the parent typeOf object

S: Indicates the name of the Extension to be created. The name can be any string that conforms to the naming rules. It cannot be the same as the existing one, or an exception will be thrown

AClass: This Extension implements an object of type Class

Objects: The constructor parameter value of the implementation class. This parameter is optional. If left unfilled, it takes the default value

4, specific use

// Currently in the app build.gradle file

// Step 1: Add entity class configuration
class Animal{

    String animalName
    int legs

    Animal(){

    }

    Animal(String animalName) {
        this.animalName = animalName
    }

    String toString() {
        return "This animal is $animalName, it has $legs legs."}}class Dog extends Animal{
    int age = 5

    Dog(){
      
    }

    Dog(int age) {
        this.age = age
    }

    String toString() {
        return super.toString() + " Its age is $age."}}// Step 2: Create Extension
/ / = = = = = = = = = = = = = = = = = = = ExtensionContainer create first overloads = = = = = = = = = = = = = = = = = = = = = = = = =
project.extensions.create('animal1',Dog)

/ / = = = = = = = = = = = = = = = = = = = ExtensionContainer create a second overloads = = = = = = = = = = = = = = = = = = = = = = = = =
project.extensions.create(Animal,'animal2',Dog,10)

/ / = = = = = = = = = = = = = = = = = = = ExtensionContainer create third overloads = = = = = = = = = = = = = = = = = = = = = = = = =
project.extensions.create(TypeOf.typeOf(Animal),'animal3',Dog,15)

// Step 3: Configure statement blocks
animal1{
    animalName 'rhubarb'
    legs 4
}

animal2{
    animalName "Erhuang"
    legs 4
}

animal3{
    animalName 'three yellow'
    legs 4
}

// Step 4: Write a Task to test
project.task('testTask'){
    doLast {
        println project.animal1
        println project.animal2
        println project.animal3
    }
}

/ / testTask execution
./gradlew testTask

// Print the result
> Task :app:TestTask This animal is Rhubarb, it has4 legs. Its age is 5.This animal is Erhuang, it has4 legs. Its age is 10.This animal is three yellow, it has4 legs. Its age is 15.
Copy the code

Note: Groovy syntax specifies that the.class suffix can be omitted when passing a Class object as an argument. For example, “Animal. Class” can be written as “Animal”

4) Create an Extension using the Add series method of ExtensionContainer

First, take a look at the Add family of methods provided by ExtensionContainer:

As you can see, it also has three overloading methods, which we’ll cover one by one

1, the first overload method

Parameter description:

S: Indicates the name of the Extension to be created. The name can be any string that conforms to the naming rules. It cannot be the same as the existing one, or an exception will be thrown

O: Type Object, which can be an instance Object or a Class Object

2, the second overload method

Parameter description:

AClass: The Class object exposed by the Extension instance created. Normally we would specify the Class object of the parent Class

S: Indicates the name of the Extension to be created. The name can be any string that conforms to the naming rules. It cannot be the same as the existing one, or an exception will be thrown

T: Type Object, a concrete Class Object or instance Object

3, the third overload method

Parameter description:

TypeOf: The typeOf object exposed by the Extension instance created. Normally, we specify the parent typeOf object

S: Indicates the name of the Extension to be created. The name can be any string that conforms to the naming rules. It cannot be the same as the existing one, or an exception will be thrown

T: Type Object, a concrete Class Object or instance Object

4, specific use

We modify the second and third steps of the above code to print the same as before

// Currently in the app build.gradle file

/ / = = = = = = = = = = = = = = = = = = = ExtensionContainer add first overloads = = = = = = = = = = = = = = = = = = = = = = = = =
project.extensions.add('animal1',Dog)

/ / = = = = = = = = = = = = = = = = = = = ExtensionContainer add a second overloads = = = = = = = = = = = = = = = = = = = = = = = = =
project.extensions.add(Animal,'animal2'.new Dog(10))

/ / = = = = = = = = = = = = = = = = = = = ExtensionContainer add a third overloads = = = = = = = = = = = = = = = = = = = = = = = = =
project.extensions.add(TypeOf.typeOf(Animal),'animal3'.new Dog(15))

animal1{
    animalName 'rhubarb'
    legs 4
}

// Note that the = sign is used
animal2{
    animalName = "Erhuang"
    legs = 4
}

// Note that the = sign is used
animal3{
    animalName = 'three yellow'
    legs = 4
}
Copy the code

Note: For the second and third overloads in the Add series above, when we show that class instances are created, we need to add the = sign when configuring Extension, otherwise an error will be reported

5) Remove the = sign from methods that define attributes with the same name

If we want to remove the = sign from the add statement above, which uses the second and third overloaded methods of the add series, we can define a method with the same name as the property, as follows:

class Animal{

    String animalName
    int legs

    void animalName(String animalName){
        this.animalName = animalName
    }

    void legs(int legs){
        this.legs = legs
    }
    / /...
}

// In this case, you can write it like this
animal2{
    animalName "Erhuang"
    legs 4
}

animal3{
    animalName = 'three yellow'
    legs = 4
}
Copy the code

6), create series method and add series method comparison

Similarities:

1. Both can be configured with a key-value pair, or with =, and all that is called is the setter method for the property

2. Throw an exception: If the Extension to be created already exists, an exception will be thrown

Difference:

1. The create series of methods return the generic type T passed in. The Add series of methods do not

The second and third methods of the Add series are overloaded. When we show that we create an instance of the class, we need to add = to the Extension configuration

7), find Extension through the ExtensionContainer getByName and findByName series of methods

//1, find series of methods
Object findByName(String name)
<T> T findByType(Class<T> type)

//2, get series method
Object getByName(String name)
<T> T getByType(Class<T> type)

//3, find series method and get series method difference
// The get method does not throw an exception. The find method does not

//4
println project.extensions.getByName("animal1")
println project.extensions.getByName("animal2")
println project.extensions.getByName("animal3")

println project.extensions.findByName("animal1")
println project.extensions.findByName("animal2")
println project.extensions.findByName("animal3")

// The printout results are bothThis animal is Rhubarb, it has4 legs. Its age is 5.This animal is Erhuang, it has4 legs. Its age is 10.This animal is three yellow, it has4 legs. Its age is 15.
Copy the code

8) Configure nested Extension

1. Configure nested Extensions by defining methods

We often see this nested Extension in the Android configuration block, as follows:

android {
    compileSdkVersion 30
    defaultConfig {
        applicationId 'com.dream.gradledemo'
        minSdkVersion 19
        targetSdkVersion 30
        versionCode 1
        versionName '1.0'
    }
    / /...
}
Copy the code

We implement a similar:

// Currently in the app build.gradle file

// Step 1: Add entity class configuration
class AndroidExt{
    int compileSdkVersionExt

    DefaultConfigExt defaultConfigExt = new DefaultConfigExt()

    /** * Method 1: * Create an internal Extension with an Action named defaultConfig ** @param Action
    void defaultConfigExt(Action<DefaultConfigExt> action) {
        action.execute(defaultConfigExt)
    }

    / * * * 2: * create internal Extension, through ConfigureUtil name for the method name defaultConfig * * @ param closure closure, is essentially a block of code * /
    void defaultConfigExt(Closure<DefaultConfigExt> closure) {
        org.gradle.util.ConfigureUtil.configure(closure, defaultConfigExt)
    }

}

class DefaultConfigExt{
    String applicationIdExt
    int minSdkVersionExt
    int targetSdkVersionExt
    int versionCodeExt
    String versionNameExt
}

// Step 2: Create Extension
project.extensions.create('androidExt',AndroidExt)

// Step 3: Configure statement blocks
androidExt {
    compileSdkVersionExt 30
    defaultConfigExt {
        applicationIdExt = 'com.dream.gradledemo'
        minSdkVersionExt = 19
        targetSdkVersionExt = 30
        versionCodeExt = 1
        versionNameExt = '1.0'}}// Step 4: Write a Task to test
project.tasks.create('extensionNested'){ doLast { println project.androidExt.compileSdkVersionExt println project.androidExt.defaultConfigExt.applicationIdExt  println project.androidExt.defaultConfigExt.minSdkVersionExt println project.androidExt.defaultConfigExt.targetSdkVersionExt println project.androidExt.defaultConfigExt.versionCodeExt println project.androidExt.defaultConfigExt.versionNameExt } }/ / extensionNested execution
./gradlew extensionNested

// Print the result
> Task :app:extensionNested
30
com.dream.gradledemo
19
30
1
1.0
Copy the code

In the above code we implement a configuration similar to the Android configuration block. The key code is:

DefaultConfigExt defaultConfigExt = new DefaultConfigExt()

/** * Method 1: * Create an internal Extension with an Action named defaultConfig ** @param Action
void defaultConfigExt(Action<DefaultConfigExt> action) {
    action.execute(defaultConfigExt)
}

/ * * * 2: * create internal Extension, through ConfigureUtil name for the method name defaultConfig * * @ param closure closure, is essentially a block of code * /
void defaultConfigExt(Closure<DefaultConfigExt> closure) {
    org.gradle.util.ConfigureUtil.configure(closure, defaultConfigExt)
}
Copy the code

The two methods above are used to create an internal Extension. In practice, only one of the methods is required. The method name should be consistent with the name of the property

I don’t know if you have noticed that my defaultConfigExt configuration block has added the = sign, which is a little different from our actual Android configuration block. You may ask, can I remove the = sign?

A: No. If you want to remove:

Create nested Extensions using the ExtensionContainer series API

Create a method with the same name as the property

The method to create the same name as the property has already been demonstrated. We will mainly demonstrate the use of the ExtensionContainer family API to create nested extensions

Create an Extension API using the ExtensionContainer series to configure nested extensions

Creating an Extension in ExtensionContainer

class AndroidExt{
    int compileSdkVersionExt
  
    AndroidExt(){
      	// Note: The Extensions here belong to AndroidExt, not the Project object
        extensions.create('defaultConfigExt',DefaultConfigExt)
    }
}


extensions.create('defaultConfigExt',DefaultConfigExt)
// This is the same as the following
project.extensions.create('androidExt',AndroidExt)
project.androidExt.extensions.create('defaultConfigExt',DefaultConfigExt)
Copy the code

This code creates an extension to DefaultConfigExt in the Constructor of AndroidExt so that the = is removed from the DefaultConfigExt configuration block

9) Configure a variable number of extensions

We often see this variable number of extensions in the Android configuration block, as follows:

buildTypes {
    release {
        // Turn on confusion
        minifyEnabled true
        // Resource alignment
        zipAlignEnabled true
        // Whether to enable the debug mode
        debuggable false
        / /...
    }
    debug {
        minifyEnabled false
        zipAlignEnabled false
        debuggable true
        / /...}}Copy the code

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

Let’s look at the source code for buildTypes:

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

It is a list of BuildType type of the incoming Action, one can see NamedDomainObjectContainer, this thing is very important, we introduce it

1, NamedDomainObjectContainer is introduced

Chinese translation NamedDomainObjectContainer named domain object container, traced back it inherited from the Collection < T >. Its role is to create objects in the script file, and create the object must have the name of this attribute as a container element identification, we can be obtained by using the approach of Project object container series NamedDomainObjectContainer objects:

Let’s implement a similar configuration for a buildTypes configuration block

2, similar to buildTypes configuration block multi-extension implementation
// Currently in app build.gradle

// Step 1: Add entity class configuration
class BuildTypesConfigExt{
    // Note: there must be a name attribute for identification
    String name
    boolean minifyEnabledExt
    boolean zipAlignEnabled
    boolean debuggableExt

    BuildTypesConfigExt(String name) {
        this.name = name
    }
		
    //===================== Configure the method ================ with the same name as the property
    void minifyEnabledExt(boolean minifyEnabledExt) {
        this.minifyEnabledExt = minifyEnabledExt
    }

    void zipAlignEnabled(boolean zipAlignEnabled) {
        this.zipAlignEnabled = zipAlignEnabled
    }

    void debuggableExt(boolean debuggableExt) {
        this.debuggableExt = debuggableExt
    }
}

// Step 2: Build a named domain object container and add it to Extension
NamedDomainObjectContainer<BuildTypesConfigExt> container = project.container(BuildTypesConfigExt)
project.extensions.add('buildTypesExt',container)

// Step 3: Configure statement blocks
buildTypesExt {
    release {
        minifyEnabledExt true
        zipAlignEnabled true
        debuggableExt false
    }

    debug {
        minifyEnabledExt false
        zipAlignEnabled false
        debuggableExt true}}// Step 4: Write a Task to test
project.tasks.create("buildTypesTask"){
    doLast {
        project.buildTypesExt.each{
            println "$it.name: $it.minifyEnabledExt $it.zipAlignEnabled $it.debuggableExt"}}}/ / buildTypesTask execution
./gradlew buildTypesTask

// Print the result
> Task :app:buildTypesTask
debug: false false true
release: true true false
Copy the code

That’s all for Extension. Next, let’s introduce Variants.

Three, Variants (Variants) introduction

The variant belongs to the Android Gradle Plugin (hereafter referred to as AGP) which needs to be introduced in the knowledge point, and will be introduced in detail later when we talk about AGP. So here’s a brief introduction to what we’re going to use

AGP provides three types of Variants for Android objects:

ApplicationVariants: App Plugins only

LibraryVariants: Only works with the Library Plugin

TestVariants: This one works for both the App Plugin and the Libarary Plugin, but is rarely used

One of our most commonly used is applicationgrowth, we will introduce it

1) applicationVariants

We can get the Android property from the Project object, and then get the variant from the Android object as follows:

// Currently in the app build.gradle file
1 / / way
android.applicationVariants
2 / / way
project.android.applicationVariants
3 / / way
project.property('android').applicationVariants
Copy the code

All three methods are the same variant

For a better demonstration, we added the following to the app’s build.gradle:

android {
    / /...

    buildTypes {
        debug{

        }

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



    productFlavors{

        flavorDimensions 'isFree'

        baidu{
            dimension 'isFree'
        }

        google{
            dimension 'isFree'
        }

        winxin{
            dimension 'isFree'}}}Copy the code

This configuration produces six variations, which are actually created by permutation and combination of buildTypes and productFlavors. Let’s go through and print the name and baseName of each variant

Note:

Starting with AGP 3.0, at least one Flavor Dimension must be specified explicitly

ApplicationVariants or libraryVariants obtained from An Android object are all variants, and we can iterate through each variant to retrieve it

3. For the properties and methods that the variant can operate, you can refer to the official AGP documentation, which provides a Chinese version of portal

// Currently in the app build.gradle file
afterEvaluate {
    project.android.applicationVariants.all{ variant ->
        println "$variant.name $variant.baseName"}}// Print the result
> Configure project :app
baiduDebug baidu-debug
googleDebug google-debug
winxinDebug winxin-debug
baiduRelease baidu-release
googleRelease google-release
winxinRelease winxin-release
Copy the code

From above we can see a difference between name and baseName

2) Hook tasks in applicationVariants

Typically, we use a variant to hook tasks in the build process, as follows:

// Currently in the app build.gradle file
afterEvaluate {
    project.android.applicationVariants.all{ variant ->
        def task = variant.mergeResources
        println "$task.name"}}// Print the result
> Configure project :app
mergeBaiduDebugResources
mergeGoogleDebugResources
mergeWinxinDebugResources
mergeBaiduReleaseResources
mergeGoogleReleaseResources
mergeWinxinReleaseResources
Copy the code

We took the mergeResources Task for all the variants and printed its name

3) Rename THE APK using applicationVariants

The output file for each variant in applicationVariants is an APK, so we can rename APK with applicationVariants as follows:

// Currently in the app build.gradle file

project.android.applicationVariants.all{ variant ->
    variant.outputs.all{
        outputFileName = "${variant.baseName}" + ".apk"
        println outputFileName
    }
}

// Print the result
> Configure project :app
baidu-debug.apk
google-debug.apk
winxin-debug.apk
baidu-release.apk
google-release.apk
winxin-release.apk
Copy the code

So that’s all we have to say about variants

Four, get all THE Xml controls in the App practical application

Gradle plugin will also be used to create a Gradle plugin. If you want to customize Gradle plugin, refer to my previous post portal

1) Thinking analysis

In the Android packaging build process, merge… The Resources Task merges all resource files and merges… Resources middle… Will vary depending on the variant and will have an effect on the output directory, for example:

If you are currently running a debug environment, then the variant, debug, in the Android packaging-build process, merges all resources via the mergeDebugResources Task and outputs the merged files to: /build/intermediates/incremental/mergeDebugResources/merger.xml

If you are currently running a release environment, then the variant is release. In the Android packaging build process, the mergeReleaseResources Task mergeReleaseResources merges all resources and outputs the merged files to: /build/intermediates/incremental/mergeReleaseResources/merger.xml

Gradle plugin to merge tasks… After the Resources, then iterate through the merger. XML file and output all the VIEWS in the XML into a.txt file

Yeah, it feels right. We’re done

2) Practical application

Let’s take a look at the initial state of our project structure:

1, the first step: customize the plug-in, the custom Task suspends to merge… Resources

package com.dream.xmlviewscanplugin

import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.Task

/**
 * 扫描 Xml Plugin
 */
class XmlViewScanPlugin implements Plugin<Project> {

    @Override
    void apply(Project project) {
        println 'Hello XmlViewScanPlugin'
        // Add the blacklist extension configuration
        project.extensions.create('ignore',IgnoreViewExtension)

        project.afterEvaluate {
            // Whether it is an Android plugin
            def isAppPlugin = project.plugins.hasPlugin('com.android.application')

            // Get the variant
            def variants
            if(isAppPlugin){
                variants = project.android.applicationVariants
            }else {
                variants = project.android.libraryVariants
            }

            variants.each{ variant ->
                // Get the corresponding merge... Resources
                Task mergeResourcesTask = variant.mergeResources

                // Define a custom Task extension prefix
                def prefix = variant.name
                // Get our custom Task
                Task xmlViewScanTask = project.tasks.create("${prefix}XmlViewScanTask", XmlViewScanTask,variant)

                // Hook our custom Task to the mergeResourcesTask
                mergeResourcesTask.finalizedBy(xmlViewScanTask)
            }
        }
    }
}
Copy the code

2. Step 2: Write a custom Task to write the scanned control into the file

package com.dream.xmlviewscanplugin

import com.android.build.gradle.api.BaseVariant
import groovy.util.slurpersupport.GPathResult
import groovy.util.slurpersupport.Node
import org.gradle.api.DefaultTask
import org.gradle.api.Task
import org.gradle.api.tasks.TaskAction
import javax.inject.Inject
import java.util.function.Consumer
import java.util.function.Predicate
import java.util.stream.Stream

/** * Scan the Xml Task */
class XmlViewScanTask extends DefaultTask {

    /** * Xml layout to be added to the View */
    private Set<String> mXmlScanViewSet = new HashSet<>()
    /** * Current variant */
    private BaseVariant variant

    @Inject
    XmlViewScanTask(BaseVariant variant) {
        this.variant = variant
    }


    /** * Execute XML scan Task */
    @TaskAction
    void performXmlScanTask() {
        try {
            println 'performXmlScanTask start... '

            // Create the file path where you want to output the View
            File outputFile = new File(project.buildDir.path + "/${variant.name}_xml_scan_view/xml_scan_view.txt")
            if(! outputFile.parentFile.exists()) { outputFile.parentFile.mkdirs() }if (outputFile.exists()) {
                outputFile.delete()
            }
            outputFile.createNewFile()
            println 'file create success... '
            mXmlScanViewSet.clear()

            // Get the merger. XML file
            Task mergeResourcesTask = variant.mergeResources
            String mergerPath = "${project.buildDir.path}/intermediates/incremental/${mergeResourcesTask.name}/merger.xml"
            File mergerFile = new File(mergerPath)

            // Start parsing merger. XML
            XmlSlurper xmlSlurper = new XmlSlurper()
            GPathResult result = xmlSlurper.parse(mergerFile)
            if (result.children()) {
                result.childNodes().forEachRemaining(new Consumer() {
                    @Override
                    void accept(Object o) {
                        parseNode(o)
                    }
                })
            }
            println 'merger.xml parsing success... '


            // At this point, all XML controls are added to mXmScanViewSet
            // Next we need to read the View in the blacklist and filter it out
            Stream<String> viewNameStream
            // Whether to enable the blacklist filtering function
            if(project.ignore.isEnable){
                println 'blacklist enable... '
                viewNameStream = filterXmlScanViewSet()

                ViewNameStream is null if the blacklist is not configured
                if(viewNameStream == null){
                    viewNameStream = mXmlScanViewSet.stream()
                }
            }else {
                println 'blacklist disable... '
                viewNameStream = mXmlScanViewSet.stream()
            }

            // Write the viewName to the file
            PrintWriter printWriter = new PrintWriter(new FileWriter(outputFile))
            viewNameStream.forEach(new Consumer<String>() {
                @Override
                void accept(String viewName) {
                    printWriter.println(viewName)
                }
            })
            printWriter.flush()
            printWriter.close()
            println 'write all viewName to file success... '
        } catch (Exception e) {
            e.printStackTrace()
        }
    }

    ViewName * @return Stream
      
        */
      
    private Stream<String> filterXmlScanViewSet() {
        List<String> ignoreViewList = project.ignore.ignoreViewList
        Stream<String> viewNameStream = null
        if (ignoreViewList) {
            println "ignoreViewList: $ignoreViewList"
            viewNameStream = mXmlScanViewSet.stream().filter(new Predicate<String>() {
                @Override
                boolean test(String viewName) {
                    for (String ignoreViewName : ignoreViewList) {
                        if (viewName == ignoreViewName) {
                            return false}}return true}})}else {
            println 'ignoreViewList is null, no filter... '
        }
        return viewNameStream
    }


    /** * Recurse the Node in the merger. XML file ** merger.  * 
       */
    private void parseNode(Object obj) {
        if (obj instanceof Node) {
            Node node = obj

            if (node) {
                if ("file" == node.name() && "layout" == node.attributes().get("type")) {
                    // Get the layout file
                    String layoutPath = node.attributes().get("path")
                    File layoutFile = new File(layoutPath)

                    // Start parsing the layout file
                    XmlSlurper xmlSlurper = new XmlSlurper()
                    GPathResult result = xmlSlurper.parse(layoutFile)
                    String viewName = result.name()
                    mXmlScanViewSet.add(viewName)

                    if (result.children()) {
                        result.childNodes().forEachRemaining(new Consumer() {
                            @Override
                            void accept(Object o) {
                              	// Resolve the child nodes recursively
                                parseLayoutNode(o)
                            }
                        })
                    }
                } else {
                    // If it is not a layout file, call recursively
                    node.childNodes().forEachRemaining(new Consumer() {
                        @Override
                        void accept(Object o) {
                            parseNode(o)
                        }
                    })

                }
            }
        }
    }


    /** * recurse to resolve the layout child node */
    private void parseLayoutNode(Object obj) {
        if (obj instanceof Node) {
            Node node = obj
            if (node) {
                mXmlScanViewSet.add(node.name())
                node.childNodes().findAll {
                    parseLayoutNode(it)
                }
            }
        }
    }

}
Copy the code

Note:

The constructor must use the @javax.inject.Inject annotation. If the property is not modified with the private modifier, the constructor must also use the @javax.inject. Otherwise, Gradle will report an error

2. Create a custom method with a random name and an @taskAction annotation. This method will be executed in Gradle’s execution phase

3. When using some classes, make sure that the package name is incorrectly derivable

Step 3: Publish the plug-in to the local repository for reference

//1. Execute the Task to publish the plugin or publish it through Gradle visual interface

//2
/ / root build. Gradle
buildscript {
    repositories {
      	/ /...
        // Local Maven repository
        maven{
            url uri('XmlViewScanPlugin')
        }
    }
    dependencies {
      	/ /...
        // Introduce plug-in dependencies
        classpath 'com. Dream: xmlviewscanplugin: 1.0.2'}}/ / app build. Gradle
apply plugin: 'XmlViewScanPlugin'
Copy the code

After the above three steps, we are ready to perform an effect verification

4. Effect verification

1. Take a look at our layout file activity_main.xml:

2. Next, run the project to see if our view is output to the.txt file

As you can see from the screenshot above, all the views are exported to the.txt file. Now let’s verify the blacklist function

3. Add blacklist configuration in app build.gradle

ignore {
    ignoreViewList = [
            'TextView']}Copy the code

We blacklisted the TextView, ran the project, and you can see that the.txt file we generated has no TextView

This is the end of the Gradle plugin’s practical application

Five, the summary

Some of the highlights of this article:

1. What is the Extension?

1, define Extension methods, parameter differences

How do I define Extension to remove the = sign

3. How to define nested extensions and multiple non-fixed number of extensions

2. Hook tasks in the build process through variants

Gradle plugin to output all views in Xml to a. TXT file

Well, that’s the end of this article, hope to help you 🤝

Thanks for reading this article

The next trailer

Stay tuned for the next post on customizing Gradle Transform 😄

References and Recommendations

Create Gradle plugin for Gradle

Android Gradle learning (5) : Extension details

Gradle creates extension properties in detail

Here is the full text, the original is not easy, welcome to like, collect, comment and forward, your recognition is the motivation of my creation

Follow my public account, search for sweereferees on wechat and updates will be received as soon as possible