preface

In this article, we will explain the Gradle core model and the Gradle plugin.

Gradle core model

1.1 Gradle hook functions

Hook functions. Gradle execution flowcharts

As is shown in

  • Gradle sets up hook function calls in all three phases of its lifecycle.
  • Handle custom builds using hook functions:
    • Initialization phase: gradle.settingsEvaluated and gradle.projectsLoaded. (effect in settings.gradle)
    • Configuration phases: project.beforeEvaluate and project.afterEvaluate; Gradle beforeProject, gradle. AfterProject and gradle. TaskGraph. TaskGraph. WhenReady.
    • Execution phase: gradle. TaskGraph. BeforeTask and gradle taskGraph. AfterTask.

Gradle can also listen for callback processing at various stages:

  • gradle.addProjectEvaluationListener
  • gradle.addBuildListener
  • Gradle. AddListener: TaskExecutionGraphListener (task execution monitor), TaskExecutionListener (task execution monitor), TaskExecutionListener, TaskActionListener, StandardOutputListener…

The concept said a lot of, lu code verification!

  1. Open AS and create a normal project.
  2. Into the projectbuild.gradle(Outer) file
  3. Add the following code at the end:
/ / = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
// Gradle provides hook functions
// Configuration phase:
gradle.beforeProject {
    println "gradle.beforeProject"
}
gradle.afterProject {
    println "gradle.afterProject"
}
gradle.taskGraph.whenReady {
    println "gradle.taskGraph.whenReady"
}
beforeEvaluate {

    println "beforeEvaluate"
}
afterEvaluate {
    println "afterEvaluate"
}


/ / = = = = = = = = = = = = = = = = = =
// Set the listener for Gradle
gradle.addProjectEvaluationListener(new ProjectEvaluationListener() {
    @Override
    void beforeEvaluate(Project project) {
        println "Configure listener beforeEvaluate"
    }

    @Override
    void afterEvaluate(Project project, ProjectState state) {
        println "Configure listener afterEvaluate"
    }
})


gradle.addBuildListener(new BuildListener() {
    @Override
    void buildStarted(Gradle gradle) {
        println "Build listener buildStarted"
    }

    @Override
    void settingsEvaluated(Settings settings) {
        println "Build listener settingsEvaluated"
    }

    @Override
    void projectsLoaded(Gradle gradle) {
        println "Build listener projectsLoaded"
    }

    @Override
    void projectsEvaluated(Gradle gradle) {
        println "Build listener projectsEvaluated"
    }

    @Override
    void buildFinished(BuildResult result) {
        println "Build listener buildFinished"
    }
})

task runGradle{
    println "configure runGradle AAAAAA"
    doFirst {
        println "doFirst runGradle AAAAAA"}}Copy the code

Code parsing

The top piece of code is the same as in the previous article, and then sets up configuration listening and running listening for Gradle. Then let’s run the runGradle task to see what it looks like:

Starting Gradle Daemon...
Connected to the target VM, address: '127.0.0.1:65159', transport: 'socket'
Gradle Daemon started in 2 s 697 ms

> Configure project :
configure runGradle AAAAAA
Configure listener afterEvaluate
gradle.afterProject
afterEvaluate

> Configure project :app
Configure listener beforeEvaluate
gradle.beforeProject
Configure listener afterEvaluate
gradle.afterProject
Build listener projectsEvaluated
gradle.taskGraph.whenReady

> Task :runGradle
doFirst runGradle AAAAAA
Build listener buildFinished

BUILD SUCCESSFUL in 8s
1 actionable task: 1 executed
14:46:28: Task execution finished 'runGradle'.
Disconnected from the target VM, address: '127.0.0.1:65159', transport: 'socket'
Copy the code

BeforeEvaluate and beforeProject are not implemented when you configure project. Gradle. BeforeEvaluate and beforeProject are not implemented when you configure Project. These methods are executed when app.gradle is configured.

So here is the final explanation for the glitch left in the previous article (why the two methods were not run during the configuration phase), because they are not run when you configure project.gradle.

Now back to the effects, this time focusing on the first three sentences and the last few.

When we compile projects using AndroidStudio, the first time is usually very slow, but once we compile it, the next time we compile it the same day is very fast. A compiled project will also compile slowly if it is not compiled for a long time. Why?

Starting Gradle Daemon… This code.

1.2 Gradle Daemons

When a project is started, a client is started, and then a Daemon is started. Requests are sent to and from the client. The project is shut down, the client is shut down, and the Daemon is kept running. So it is very fast. By default, daemons are not used for 3 hours and then shut down. For compatibility of different projects, you can also use no-daemon to start projects, so there is no speed advantage.

So in this run effect you can see: Connected to the target VM, address Disconnected from the target VM

When we use Gradle, when we have multiple Library projects, we tend to unify the versions, so this requires the extension of Gradle properties.

1.3 Gradle Property Extensions

  • Use Ext to extend any object property:

    • Attribute extension to project using Ext, visible to all child projects.
    • Ext attributes are typically extended in root projects to provide reusable attributes for sub-projects, which are accessed directly through rootProject
    • You can add attributes to any object using Ext: use closures, where you define extended attributes. Add extended attributes directly using = assignment.
    • The extension property belongs to whoever makes the ext call.
    • In build.gradle, the default is the project object of the current project, so using “ext=” or “ext{}” in build.gradle directly defines the extended properties for the project
  • Use gradle.properties to define properties as key-value pairs that all projects can use directly

1.3.1 Use Ext to extend arbitrary object properties

Add the following code to project. Gradle

ext {// Project attribute extension, can be seen in other projects
    prop1 = "prop1"
    prop3 = "prop3"
}

ext.prop2 = "prop2"

println prop1
println prop2

task runProExtPro{
    println "runProExtPro\t"+project.ext.prop3
    println "runProExtPro\t"+project.prop2
}
Copy the code

Effect after running the task runProExtPro

. Prop1 prop2 runProExtPro prop3 runProExtPro prop2 slightlyCopy the code

The ext attribute opens a closure that can be extended with multiple attributes and, after being extended, can be extended with a single attribute externally. Since this access is running in the current project. Gradle environment, now try accessing it in app. Gradle.

task runAppExtPro{
    println "runAppExtPro\t"+project.prop3
    println "runAppExtPro\t"+project.prop2
}
Copy the code

Note that ext has already been removed from the list, as this will indicate that the attribute does not exist, so it is recommended to access the ext extension directly through project.xx. Now look at the effect of running runAppExtPro:

. RunAppExtPro prop3 runAppExtPro Prop2... slightlyCopy the code

From here you can see that the project is extended using Ext, which is visible to all the child projects.

When we configure the version information, we do not want to configure the extension properties in the root project. Gradle. Here’s another way to extend.

1.3.2 Use gradle.properties to define properties

Open gradle.properties and add the following properties:

MIN_SDK_VERSION=21
TARGET_SDK_VERSION=30
COMPILE_SDK_VERSION=30
BUILD_TOOL_VERSION=30.03.
Copy the code

Open the corresponding child project.gradle or the library we depend on to use the properties we just extended.

android {
    compileSdkVersion Integer.parseInt(COMPILE_SDK_VERSION)
    buildToolsVersion BUILD_TOOL_VERSION

    defaultConfig {
        applicationId "com.hqk.gradledemo01"
        minSdkVersion Integer.parseInt(MIN_SDK_VERSION)
        targetSdkVersion Integer.parseInt(TARGET_SDK_VERSION)
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"}... A little... slightlyCopy the code

It still compiles, and all child projects can use the gradle.properties extended property version. In app.gradle, add the following code

task checkVersion{
    println "runAppGradle"
    println "MIN_SDK_VERSION:"+MIN_SDK_VERSION
    println "TARGET_SDK_VERSION:"+TARGET_SDK_VERSION
    println "COMPILE_SDK_VERSION:"+COMPILE_SDK_VERSION
    println "BUILD_TOOL_VERSION:"+BUILD_TOOL_VERSION
}
Copy the code

Running effect

. RunAppGradle MIN_SDK_VERSION:21 TARGET_SDK_VERSION:30 COMPILE_SDK_VERSION:30 BUILD_TOOL_VERSION:30.0.3... slightlyCopy the code

Perfect operation, also play out the desired effect. But so far, most of the tasks written have been printouts and have not yet written complex logic. So how do you define tasks if you want to implement complex logic?

1.4 Gradle Custom Tasks

Customizing tasks in build.gradle:

  • The task defined by task is an object of a concrete implementation class of DefaultTask
  • User-defined DeaflutTask can be used:
    • Use the @TaskAction annotation on the method to indicate the method to be invoked while the task is running.
    • Use @input to indicate Input parameters to the task.
    • Use @outputfile to represent the task OutputFile.
    • Inputs, outputs directly set task inputs/outputs.
    • An output of one task can be an input of another (implicit dependency).

1.4.1 Writing File Data to Demo

class WriteTask extends DefaultTask {
    @Input
// @Optional
    // Indicates optional
    String from
    
    @OutputFile
// @Optional
    // Indicates optional
    File out
    
    WriteTask() {

    }
    @TaskAction
    void fun() {
        println " @TaskAction fun()"
        println from
        println out.toString()
        out.createNewFile()
        out.text=from
    }
}

task myTask(type: WriteTask) {
    from = "a/b/c" / / input
    out = file("test.txt") / / output
}
Copy the code

We define a WriteTask custom task, in which two properties represent the input and output objects with corresponding annotations, and then define myTask, which writes the string to the file and runs it to see the effect.

As is shown in

When Gradle runs successfully, a TXT file is added to the same directory as the string we just wrote. Now this demo to upgrade, currently is a string write file, so can write the contents of one file in another file? Now try it:

class WriteTask extends DefaultTask {
//// @Input
//// @Optional
// // indicates optional
// String from
//// @OutputFile
//// @Optional
// // indicates optional
// File out
    WriteTask() {

    }
    @TaskAction
    void fun() {
        println " @TaskAction fun()"
// println from
// println out.toString()
// out.createNewFile()
// out.text=from
        println inputs.files.singleFile
        def inFile = inputs.files.singleFile

        def file = outputs.files.singleFile
        file.createNewFile()
        file.text = inFile.text

    }
}

task myTask(type: WriteTask) {
// from = "a/b/c" // input
// out = file("test.txt") //
    inputs.file file('build.gradle')
    outputs.file file('test.txt')}Copy the code

Now change the mode of input and output, through inputs. Input and output. Build. Gradle = test.txt = test.txt

Build. gradle is successfully written to test. TXT.

So at this point, we’re done writing data to demo. Now for the new demo: File compression

1.4.2 File Compression Demo

Add the following code to app.gradle:

task zip(type: Zip) {
    archiveName "outputs.zip"// The output file name
    destinationDir file("${buildDir}/custom")// The output file is stored in the folder
    from "${buildDir}/outputs"// Input file
}
Copy the code

The outputs file in the build directory where ${buildDir} runs successfully will be compressed.

Note: The reason for the compression here is to note the type parameter, of typeZip“Indicates that the Zip compression task is enabled. Just write the parameter type to the file we just customizedtype: WriteTask

Now run Task Zip to see the result:

From this illustration, we can see that the compression has been successfully compressed. But there is a problem, because executing task Zip alone does not enable APK compilation because there is no correlation between the two (as explained in the previous article), so what if the target of compression does not exist (APK does not compile the corresponding build folder)? Try deleting the target folder:

Running effect

. Task :app:zip no-source Build Listener buildFinishedCopy the code

Notice that there is NO SOURCE, so compression failed. So can we wait for the compression target to be created before compression? In other words, when performing compression tasks, even if the target task does not exist, it should be compiled in advance and then compressed.

Now continue to modify the code:

//task zip(type: Zip) {
// archiveName "elsions.zip "// output file name
// destinationDir file("${buildDir}/custom"
${buildDir}/outputs"// input file
/ /}

afterEvaluate {
    println tasks.getByName("packageDebug")
    task zip(type: Zip) {
        archiveName "outputs2.zip"// The output file name
        destinationDir file("${buildDir}/custom")// The output file is stored in the folder
        from tasks.getByName("packageDebug").outputs.files// Input file
        tasks.getByName("packageDebug").outputs.files.each {
            println it
        }
    }
}
Copy the code

App. gradle afterEvaluate closure. Apk injects task Zip into the Gradle execution process at the end of compilation and configuration. Since it is in the APK compilation execution flow, it will start the APK compilation and then execute the Task ZIP task to achieve the desired effect. Now try running the Task zip task separately:

Note: Only one task can exist for the type: Zip parameter, so comment it out

The code does not have a run button, so use the right tool to assist the run, note that the left is not compiled folder, click the right to run:

At the end of the run, the left side has the corresponding build folder, which also contains the corresponding zip package, and the name can be matched.

The file compression demo has been perfectly implemented so far, but this feature can only be used for your own project, so what if you want to use it for others or other projects? That’s where the plugins come in.

2. Gradle plug-in

2.1 What are Gradle plug-ins

  • Gradle plug-ins are dependencies that are provided to the Gradle build tool at compile time. The essence of a plug-in is to package the common build business for reuse
  • Gradle plug-ins are divided into script plug-ins and binary plug-ins (the classes that implement the Plugin).
  • The Gradle plug-in is introduced into the project through the Apply method

Gradle plug-ins are divided into script plug-ins and binary plug-ins. What is the difference?

  • The script plug-in implements a list of tasks and is assembled to be used directly according to the provided API
  • Gradle script plug-ins, which provide task encapsulation for implementation, need to be self-assembled. Or the encapsulation of some specific business used.

2.2 Gradle Script Plug-ins

Create the script file script.gradle in the root directory of your project and write the following code:

afterEvaluate {
    println tasks.getByName("packageDebug")
    task zip(type: Zip) {
        archiveName "outputs3.zip"// The output file name
        destinationDir file("${buildDir}/custom")// The output file is stored in the folder
        from tasks.getByName("packageDebug").outputs.files// Input file
        tasks.getByName("packageDebug").outputs.files.each {
            println it
        }
    }
}
Copy the code

If you look closely at this script, you can see that the contents of the script plugin are exactly the same as those in app.gradle.

Go to app. Gradle


apply from: '.. /script.gradle'android { compileSdkVersion Integer.parseInt(COMPILE_SDK_VERSION) buildToolsVersion BUILD_TOOL_VERSION ... Slightly}//task zip(type: Zip) {
// archiveName "elsions.zip "// output file name
// destinationDir file("${buildDir}/custom"
${buildDir}/outputs"// input file
/ /}

//afterEvaluate {
// println tasks.getByName("packageDebug")
// task zip(type: Zip) {
// archiveName "outputs2.zip"// Name of the output file
// destinationDir file("${buildDir}/custom"
Outputs. Files // From tasks. GetByName ("packageDebug").outputs
// tasks.getByName("packageDebug").outputs.files.each {
// println it
/ /}
/ /}
/ /}
Copy the code

Remember to comment out the compression here. Now click on the right to see what it looks like:

From this point of view, it has worked perfectly successfully! Scripting plug-ins are as simple as that! What about binary plug-ins?

2.3 Gradle binary plug-ins

//apply from: '.. /script.gradle'
apply plugin:MyPlugin android { compileSdkVersion Integer.parseInt(COMPILE_SDK_VERSION) buildToolsVersion BUILD_TOOL_VERSION ... Slightly}//task zip(type: Zip) {
// archiveName "elsions.zip "// output file name
// destinationDir file("${buildDir}/custom"
${buildDir}/outputs"// input file
/ /}
//afterEvaluate {
// println tasks.getByName("packageDebug")
// task zip(type: Zip) {
// archiveName "outputs2.zip"// Name of the output file
// destinationDir file("${buildDir}/custom"
Outputs. Files // From tasks. GetByName ("packageDebug").outputs
// tasks.getByName("packageDebug").outputs.files.each {
// println it
/ /}
/ /}
/ /}


/ / = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
// Plug-ins: 1. Script plug-ins
// 2. Binary plug-in

class MyPlugin implements Plugin<Project> {

    @Override
    void apply(Project target) {
        println "MyPlugin apply"

        target.afterEvaluate {
            println "MyPlugin afterEvaluate "+target.tasks.getByName("packageDebug")
            target.task(type: Zip, "zip") {// The second argument specifies which method
                archiveName "outputs4.zip"// The output file name
                destinationDir target.file("${target.buildDir}/custom")// The output file is stored in the folder
                from target.tasks.getByName("packageDebug").outputs.files// Input file
                target.tasks.getByName("packageDebug").outputs.files.each {
                    println it
                }
            }
        }
    }

Copy the code

The MyPlugin class implements the Plugin interface, and the target.afterevaluate task defines target.task(type: Zip, “Zip “), the first parameter specifies what type, and the second parameter indicates that the current task is called Zip compression.

Now delete the result of the previous run and continue with the task on the right to see the result:

Ha, ha, ha, ha, ha, ha, ha. That’s pretty much the end of the tutorial.

3. The conclusion

I believe you will have a new understanding of Gradle’s core model and Gradle plug-ins. In the next article, we will continue Gradle’s explanation.

It is not easy to be original. If this article is useful to my friends, I hope my friends can give me more thumbs up and support. I can update the tutorial faster and better.