preface

Gradle is a flexible and efficient automated build tool. Its build scripts are written in Groovy or Kotlin languages. Groovy and Kotlin are both JVM-based languages. At the same time, Gradle is also the official building tool of Android. Learning Gradle can help us better understand the building process of Android projects. When there are problems in project construction, we can better identify the problems. So Gradle learning can help us better manage Android projects, Gradle official address is as follows:

Gradle website

Making the address

The characteristics of Gradle

Gradle build script is written in Groovy or Kotlin language. If written in Groovy, the build script suffix is. Gradle, which can use Groovy syntax. If written in Kotlin, the build script suffix is. You can use Kotlin syntax inside;

2. Since Both Groovy and Kotlin are object-oriented languages, Gradle is filled with objects. Gradle’s.gradle or.gradle.kts script is essentially a Project object. The named configuration items such as BuildScript, allProjects, etc. in the script are essentially methods in the object, and the closure {} after the configuration item is a parameter, so we are essentially calling a method in the object when we use this configuration item.

In Groovy or Kotlin, functions are first-class citizens like classes, and they both provide good closure {} support, so it’s easy to write DSL-style code. Gradle, which uses DSL to write build scripts, is more readable, dynamic and concise than other build tools such as Maven and Ant that use XML to write build scripts.

4. Gradle mainly contains Project and Task objects. Project is the representation of a build script in Gradle, and a build script corresponds to a Project object. Project provides a context for Task execution.

Groovy Basics

All of the examples in this article are written in the Groovy language, so a quick primer on Groovy before reading this article:

Groovy uses full parsing

Here are the main differences between Groovy and Java:

1. You can ignore semicolons after Groovy statements

int num1 = 1
int num2 = 2
int result = add(num1, num2)

int add(int a, int b){
    return a + b
}
Copy the code

2. Groovy supports dynamic typing inference. You can use def to define variables and methods without specifying the type of the variable or the return value of the method, and you can define methods without specifying the type of the parameter

def num1 = 1
def num2 = 2
def result = add(num1, num2)

def add(a, b){
    return a + b
}
Copy the code

3. Groovy method calls can be passed without parentheses. When a method does not specify a return statement, the last line defaults to a return value

def result = add 1.2

def add(a, b){
    a + b
}
Copy the code

4, Groovy can be represented with a single, double, three quotes strings, common string of single quotes, double quotes string can use value ${} operator, and $in single quotation marks are only just said a common character, three quotes string is also known as the template, it can keep text wrap and indentation, Triple quotes also do not support $

def world = 'world'

def str1 = 'hello ${world}'
def str2 = "hello ${world}"
def str3 = '''hello
&{world}'''

// Print output:
//hello ${world}
//hello world
//hello
//&{world}
Copy the code

5. Groovy generates a get/set method for every field in the class that does not have a visibility modifier. We access the field by calling its get/set method, and we can access the method as if it were a normal field if the class method started with get/set

class Person{
    def name
    private def country
  
    def setNation(nation){
        this.country = nation
    }
    def getNation(){
        return country
    }
}

def person = new Person()
// Access the field
person.name = 'rain9155'
println person.name
// Access the method starting with GET /set as a field
person.nation = "china"
println person.nation
Copy the code

Closure in Groovy is a block of code represented by {argument list -> body}. If there are only one argument <= 1, the -> arrow can be omitted. If there is only one argument, the default argument name is it. So the closure can be passed as a parameter, and the closure has a delegate field that can delegate the execution code in the closure to any object for execution

class Person{
    def name
    def age
}

def person = new Person()
// Define a closure
def closure = {
    name = 'rain9155'
    age = 21
}
// Delegate the closure to the Person object
closure.delegate = person
// Execute closures, or you can execute closures by calling closure.call()
closure()

// The above code delegates the closure to the Person object, so the closure is executed in the context of the Person object
// So the closure can access the name and age fields in the Person object and complete the assignment
println person.name// Output: Rain9155
println person.age// Output: 21
Copy the code

In Gradle, if you do not specify a closure delegate, the delegate will be the Project object of the current Project.

The above Groovy knowledge is considered sufficient for those who have Java foundation to learn Gradle. Of course, for some collection operations and file operations, you can check the Groovy official website to fill in the gaps when you use it in the future.

Gradle installation configuration

Official tutorial: Installing Gradle

Before you install Gradle, make sure your COMPUTER has configured the JDK. The JDK version is required to be 8 or higher. You can install Gradle automatically through the package manager or manually download Gradle. On the Mac I recommend using Homebrew package Manager for automatic installation:

1. Window platform

  • 1. Select a Gradle version from the Gradle installation page and download its binary only or complete version. The binary only version means that the downloaded Gradle package contains only Gradle source code. The complete version of Gradle contains the source code of Gradle and the source code documentation. Here I download Gradle 6.5-bin version;
  • 2, download good Gradle, decompress it to a specific directory, such as I here decompress to: D:/ Gradle, and then like configuration Java environment D:/ Gradle/Gradle -6.5/bin PATH added to the system PATH variable;
  • 3. Open CMD and entergradle -vCheck whether the configuration is successful. If information similar to the following is displayed, the configuration is successful.
C:\Users\HY>gradle -v

------------------------------------------------------------
Gradle 6.5
------------------------------------------------------------

Build time:   2020- 06. 20:46:21 UTC
Revision:     a27f41e4ae5e8a41ab9b19f8dd6d86d7b384dad4

Kotlin:       1.372.
Groovy:       2.511.
Ant:          Apache Ant(TM) version 1.107. compiled on September 1 2019
JVM:          10.02. ("Oracle Corporation" 10.02.+13)
OS:           Windows 10 10.0 amd64
Copy the code

2. Mac platform

  • 1. Install Homebrew;
  • 2. Open the terminal and enterbrew install gradleBy default, it will download and install the binary-only version;
  • 3. When Gradle is installed on Homebrew, enter it in the terminalgradle -vCheck whether the installation is successful.

Gradle project structure

Gradle project can use Android Studio, IntelliJ IDEA and other IDE tools or text editor to write, here I take the Mac platform as an example using text editor with command line to write, Window platform similar.

Create a new directory like this: ~/GradleDemo, open the command line, type CD ~/GradleDemo to switch to this directory, then type Gradle init, then Gradle will execute the init task, which will let you choose the template to generate the project, the language to write the script, the project name, etc. I chose the basic template (i.e., the original Gradle project), the Groovy language, and the project name GradleDemo, as follows:

The init task will automatically generate the project template for you, as follows:

Ignoring hidden files or directories that start with., Gradle Init automatically generates the following files or directories for us:

1, build. Gradle

It says Gradle project build scripts, we can write the script by Groovy inside, a build in Gradle Gradle is corresponding to a project, build. Gradle on Gradle root directory of the project, said it is corresponding to the root project, Gradle is placed in another subdirectory of the Gradle Project, which means that it corresponds to a subproject. Gradle will parse build.gradle into a Project object when it is built. The DSL you write in it is actually the method in the Project interface.

2, Settings. Gradle

Gradle’s multi-project configuration script is stored in the root directory of the Gradle project. Include is used to determine which subprojects will participate in the build. Gradle builds Settings. Include is also just a method in the Settings interface.

3, Gradle Wrapper

When gradle init is executed, it also executes the Wrapper task, which creates the gradle/ Wrapper directory, Create gradle-wrapper.jar and gradle-wrapper.properties files in gradle/wrapper directory, and create gradlew and gradlew. Collectively, they are called Gradle Wrapper, which is a layer of wrapping for Gradle.

The Wrapper uses the Gradle Wrapper to enable your computer to run Gradle projects without having to install or configure the Gradle environment. For example, if your Gradle project is cloned by user A, but the Gradle environment is not installed or configured on user A’s computer. When user A builds A project through Gradle, the Gradle Wrapper will download Gradle from the specified download location and unzip it to the specified location on the computer. User A can then run gradlew or the gradlew. Bat script on the Gradle project command line without setting the Gradle system variable, assuming user A wants to run Gradle -v. Run./gradle -v on Linux, run Gradlew -v on Windows, and replace Gradle with GradLew.

Each file in the Gradle Wrapper has the following meanings:

Gradlew: a script for executing gradle commands on Linux.

2. Gradlew. Bat: script for executing gradle commands on window platform;

Gradle -wrapper.jar: Contains the logic code of gradle Wrapper runtime;

4, gradle-wrapper.properties: used to specify gradle download location and decompress location;

The fields in gradle-wrapper.properties are explained as follows:

The field name explain
distributionBase The home directory of the downloaded Gradle package after decompression is GRADLE_USER_HOME, which is represented in windowC:/ user/your computer login user name /.gradle/On the MAC it means~ /. Gradle /
distributionPath The path to the decompressed Gradle relative to the distributionBase is wrapper/dists
distributionUrl Gradle 6.5-all.zip is the complete version of Gradle 6.5. Gradle-6.5-bin. zip indicates the binary version of Gradle 6.5
zipStoreBase The same as distributionBase, except that it represents the home directory where the downloaded Gradle zip is stored
zipStorePath The same as distributionPath, except that it represents the path where the downloaded Gradle zip is stored

Using the Gradle Wrapper, you can unify the Gradle version of the project on different users’ computers. At the same time, you do not need to let the person running the Gradle project to install and configure the Gradle environment, which improves development efficiency.

Gradle multi-project configuration

The Gradle project has a root project by default, and its build. Gradle file is located in the root directory of the Gradle project. If we want to add more than one subproject, we need to configure it through settings. Gradle.

First we create multiple folders in GradleDemo, here I create 4 folders, respectively: Subproject_1, subproject_2, subproject_3, subproject_4, then create a build.gradle file in each new folder, as follows:

Next open settings.gradle and add the following:

include ':subproject_1'.':subproject_2'.':subproject_3'.':subproject_4'
Copy the code

Open the command line, switch to the GradleDemo directory, type Gradle Projects, and execute the projects task to show the dependencies between all projects, as follows:

# chenjianyu @ FVFCG2HJL414 in ~/GradleDemo 
$ gradle projects     

> Task :projects
Root project 'GradleDemo'
+--- Project ':subproject_1'
+--- Project ':subproject_2'
+--- Project ':subproject_3'
\--- Project ':subproject_4'

BUILD SUCCESSFUL in 540ms
1 actionable task: 1 executed
Copy the code

As you can see, the 4 sub-projects depend on the root project. Next, we will configure the project. The configuration of the project is usually done in the build.gradle of the current project. It would be a waste of time to do the same configuration in each Project’s build.gradle. Gradle’s Project interface provides allprojects and subprojects methods. Allprojects = ‘root’; subprojects = ‘root’; subprojects = ‘root’;

// Build. Gradle for the root project

// Add maven repo addresses for all projects
allprojects {
    repositories {
        mavenCentral()
    }
}
// Add groovy plugins to all subprojects
subprojects {
    apply plugin: 'groovy'
}
Copy the code

The lifecycle of Gradle builds

When you type gradle build to build the entire project or gradle task name on the command line, the building process of Gradle is divided into three stages:

Init -> configure -> execute

  • Init: The initialization phase mainly parses Settings. gradle, generates Settings objects, determines which projects need to participate in building, and creates Project objects for the projects that need to be built;
  • Configure: In the configuration phase, we mainly parse build.gradle, configure the Project objects generated in init phase, build the root Project and all subprojects, generate and configure the Task objects defined in build.gradle, and construct the relationship dependency graph of tasks, which is a directed acyclic graph.
  • Execute: Execute the Task according to the dependency diagram of the configure phase.

Gradle hooks some listeners at the beginning and end of each of the three phases, exposing them to developers and making it easier for them to do something during Gradle’s different phases.

Gradle and build.gradle represent the Settings object and the Project object respectively, each of which has a Gradle object, Gradle objects can be obtained in the root directory of the Gradle project, settings. Gradle or build. Gradle, and then the life cycle listening, as follows:

/ / build. Gradle or Settings. Gradle

this.gradle.buildStarted {
    println "Gradle build starts"
}

/ / -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- the init start -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
this.gradle.settingsEvaluated {
    println "Settings. gradle parsing complete"
}
this.gradle.projectsLoaded {
    println "All projects are loaded from Settings."
}
/ / -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- the init end -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --

/ / -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- the configure start -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
this.gradle.beforeProject {project ->
    // Is called before each project is built
    println "${project.name} Project start build"
}
this.gradle.afterProject {project ->
    // Each project is called upon completion of the build
    println ${project.name} Project build completed
}
this.gradle.projectsEvaluated {
    println "All projects built."
}
this.gradle.taskGraph.whenReady {
    println("Task Diagram build complete")}/ / -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- the configure end -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --

/ / -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- the execute start -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
this.gradle.taskGraph.beforeTask {task ->
    // This method is called at the start of each task
    println(${task.name}task)}this.gradle.taskGraph.afterTask {task ->
    // This method is called at the end of each task
    println("${task.name}task completed")}/ / -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- the execute end -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --

this.gradle.buildFinished {
    println "Gradle build finished"
}
Copy the code

Gradle’s buildStarted method is never called back because we register it too late. When parsing settings. Gradle or build. Gradle, Gradle has already started building, so this method is also marked as obsolete by Gradle, because we have no chance to listen to the start of the Gradle build, and if you add all the above listens to build. Gradle, Gradle’s settingsEvaluated and projectsLoaded methods will not be called back, because settings. Gradle is parsed before build.gradle, and it is too late to listen for them in build.gradle.

You can also listen for Gradle lifecycle callbacks by adding a BuildListener via the addBuildListener method of the Gradle object

How do I listen for the start and end of my current single project? Just add to your current project’s build.gradle:

//build.gradle

this.beforeEvaluate {
    println 'Project starts building'
}
this.afterEvaluate {
    println 'End of project build'
}
Copy the code

Note that adding the above method to the root project’s build.gradle does not allow the beforeEvaluate method to be called because it is registered too late and the root project is already building when the root project’s build.gradle is parsed. Gradle can listen to the start and end of a project build, because the root project will not be built until the root project is finished.

Task

1. Task creation

Task is the smallest execution unit in Gradle. It is an interface. The default implementation class is DefaultTask. Here I create Task in subproject_1/build.gradle as follows:

//subproject_1/build.gradle

// Create a task using the task method of Project
task task1{
    doFirst{
	    println 'one'
    }
    doLast{
 	    println 'two'}}Copy the code

This code creates a task named task1 using the task method. When creating the task, you can configure its doFirst and doLast actions using the closure. DoFirst and doLast are methods in the task. The doFirst method is executed before the action of the Task, and the doLast method is executed after the action of the Task. The action is the execution unit of the Task. You can also specify the doFirst and doLast actions after creating a Task, as follows:

//subproject_1/build.gradle

// Create a task using the task method of Project
def t = task task2
t.doFirst {
    println 'one'
}
t.doLast{
    println 'two'
}
Copy the code

The above task created by the task method of Project is placed in a container of type TaskContainer of Project by default. We can get this container from the getTasks method of Project. TaskContainer provides the create method to create tasks as follows:

//subproject_1/build.gradle

// Create a Task using the TaskContainer create method
tasks.create(name: 'task3'){
    doFirst{
	    println 'one'
    }
    doLast{
 	    println 'two'}}Copy the code

To create a Task, run the following command: gradle task1 gradle task1 gradle task1

# chenjianyu @ FVFCG2HJL414 in ~/GradleDemo 
$ gradle task1   

> Task :subproject_1:task1
one
two

BUILD SUCCESSFUL in 463ms
1 actionable task: 1 executed
Copy the code

If you want to simplify the output, just add the -q parameter, such as gradle -q task1, so that the output only contains the output of task1:

# chenjianyu @ FVFCG2HJL414 in ~/GradleDemo 
$ gradle -q task1
one
two
Copy the code

If you want to execute multiple tasks, multiple Task names are separated by a space after the gradle command, such as gradle task1 task2 task3.

2. Configure Task attributes

Gradle defines default properties for each Task, such as Description, group, dependsOn, inputs, outputs, etc. We can configure these properties as follows:

//subproject_2/build.gradle

// We can assign these properties when we define Task
task task1{
 group = 'MyGroup'
 description = 'Hello World'

 doLast{
 	println "Task: ${group}"
 	println "${description}"}}Copy the code

Before executing a Task, Gradle configures the Property of the Task and then executes the execution block of the Task, so it doesn’t matter where the configuration block is placed, as follows:

//subproject_2/build.gradle

// Task is configured after the Task is defined
task task2{
    doLast{
	    println "Task: ${group}"
 	    println "${description}" 
    }
}
task2{
    group = 'MyGroup'
    description = 'Hello World'
}

// is equivalent to task2
task task3{
    doLast{
	    println "Task: ${group}"
 	    println "${description}" 
    }
}
task3.description = 'Hello World! '
task3.group = "MyGroup"

// is equivalent to task3
task task4{
    doLast{
	    println "Task: ${group}"
 	    println "${description}" 
    }
}
task4.configure{
    group = 'MyGroup'
    description = 'Hello World'
}
Copy the code

We can specify the dependency between tasks using the dependsOn attribute of Task as follows:

//subproject_2/build.gradle

DependsOn is declared when a Task is created
task task5(dependsOn: task4){
    doLast{
	    println 'Hello World'}}// Or declare dependencies between tasks after the Task is created
task4.dependsOn task3
Copy the code

Task3 -> task4 -> task5 -> task5 -> task3 -> task4 -> task5

# chenjianyu @ FVFCG2HJL414 in ~/GradleDemo 
$ gradle task5  

> Task :subproject_2:Task3 Task group: MyGroup Task Description: Hello World! >Task :subproject_2:Task4 Task group: MyGroup Task Description: Hello World >Task :subproject_2:Task5 Hello World task5Task BUILD SUCCESSFUL is completein 2s
3 actionable tasks: 3 executed
Copy the code

Execute task3, task4, and task5 in sequence.

3. Customize tasks

We can define tasks by inheriting DefaultTask. Gradle also has many built-in tasks with specific functions that inherit from DefaultTask indirectly. For example, Copy(Copy file), Delete(Delete file), etc., we can directly build. Gradle custom Task, as follows:

//subproject_3/build.gradle

class MyTask extends DefaultTask{

    def message = 'hello world from myCustomTask'

    @TaskAction
    def println1(){
        println "println1: $message"
    }

    @TaskAction
    def println2(){
        println "println2: $message"}}Copy the code

In MyTask, the @taskAction annotation method is the action of the Task. An action is the main component of a Task. It represents an action to execute a Task. The actions are executed in the same order as the methods in the @taskAction annotation, so the process of executing a Task is: MyTask defines two actions in MyTask. Next we use this Task, as follows:

//subproject_3/build.gradle

// When defining a Task, specify the type of the Task
task myTask(type: MyTask){
  message = 'custom message'
}
Copy the code

When defining a Task, you can specify the type of the Task by using type. You can also configure the message parameter in MyTask by using closures. To execute this Task, type Gradle MyTask as follows:

# chenjianyu @ FVFCG2HJL414 in ~
$ gradle myTask        

> Task :subproject_3:myTask
println2: custom message
println1: custom message

BUILD SUCCESSFUL in 611ms
1 actionable task: 1 executed
Copy the code

Our custom Task is essentially a class. In addition to writing our custom Task directly in the build.gradle file, we can create a new buildSrc directory in the root of the Gradle project. In the buildSrc/ SRC /main/[Java /kotlin/groovy] directory, define to write the custom Task, either in Java, kotlin, groovy statements, or in a separate project. We’ll look at these later in customizing plugins.

A custom Plugin

Plugin can be understood as a set of tasks. A Plugin can be customized by implementing the Apply method of the Plugin interface. A custom Plugin is also a class in nature.

  • 1. Write directly in build.gradle: You can write custom Plugin in any build.gradle file. In this way, custom Plugin is only visible to the project corresponding to the build.gradle;
  • 2, write in the buildSrc directory: You can write a custom Plugin in the buildSrc/ SRC /main/[Java /kotlin/groovy] directory at the root of the Gradle project. You can use one of the Java, kotlin, and Groovy statements. All class files in the buildSrc/ SRC /main/[Java /kotlin/groovy] directory are class files for all build. Gradle references in this project. So this custom Plugin is only visible to the Gradle project;
  • 3. Write in a separate project: You can create a new Gradle project, write a custom Plugin in the Gradle project, and then package the Plugin source code into a JAR and publish it to maven, LVY and other hosting platforms, so that other projects can reference the Plugin, so that the custom Plugin is visible to all Gradle projects.

Since the above introduction of custom Task has already explained how to write directly in build.gradle, and custom Plugin is similar, so the following two methods are mainly introduced, and these two methods are the most commonly used ways of custom Plugin in daily development.

1. Write in the buildSrc directory

Create a new buildSrc directory in GradleDemo. Then create a new SRC /main/groovy directory in buildSrc. If you want to use Java or kotlin, create a new SRC /main/ Java or SRC /main/kotlin. In SRC /main/groovy you can also create a new package. In this case, my package is com.example.plugin. Then create a new class myplugin.groovy under the package, which inherits from the plugin interface.

class MyPlugin implements Plugin<Project> {
    @Override
    void apply(Project project){}
}
Copy the code

Now there is no logic in MyPlugin, we usually use the apply plugin in build.gradle: ‘Plugin name ‘refers to a Plugin, where apply refers to the logic in the apply method, and the project parameter to the apply method refers to the project object that references the Plugin’s build.gradle. Next let’s write the logic in the apply method as follows:

package com.example.plugin

import org.gradle.api.*

class MyPlugin implements Plugin<Project> {

    @Override
    void apply(Project project){
        // Create an extension named outerExt using the project ExtensionContainer create method. The extension class is outerExt
        // The create method returns the OuterExt instance, which we can use in the apply method
        def outerExt = project.extensions.create('outerExt', OuterExt.class)
        	
        // Create a task named showExt using the task method of project
        project.task('showExt'){
            doLast{
                        // Use the OuterExt instance
        		println "outerExt = $outerExt"}}}/** * Custom plug-in extension corresponding class */
    static class OuterExt{
    	def name
    	def message
    
        @Override
    	String toString(){
    	    return "[name = $name, message = $message]"}}}Copy the code

I created an extension and a Task in the apply method, where the Task is easy to understand, so what is the extension? When we refer to Android plugins, we must have seen some namespaces similar to Android, as follows:

apply plugin: 'com.android.application'

android {
compileSdkVersion 29
buildToolsVersion "29.0.3"
  / /...
}
Copy the code

It’s not a method called Android, it’s an extension of an Android plugin called Android that corresponds to a bean class that has methods like compileSdkVersion, buildToolsVersion, and so on. So to configure Android is to configure the bean class corresponding to Andorid. Now go back to our MyPlugin. MyPlugin also defines a bean class: OuterExt, which has two fields: name and Messag, Groovy will automatically generate get/set methods for us. The apply method creates an extension named outerExt using the project instance’s ExtensionContainer create method. The extension’s corresponding bean class is outerExt. ExtensionContainer is similar to TaskContainer. It is a container that holds all the extensions in a Project. You can create an extension using the create method of ExtensionContainer. The create method returns an instance of the extended corresponding class, which we can use with MyPlugin as follows:

//subproject_4/build.gradle

apply plugin: com.example.plugin.MyPlugin

outerExt {
    name 'rain9155'
    message 'hello'
}

// Run gradle showExt, output:
//outerExt = [name = rain9155, message = hello]
Copy the code

The main thing about extensions is that you can use closures to configure the classes for extensions, so you can use outerExt to configure your Plugin. Many custom plugins use extensions to configure their own plugins. How to implement a nested DSL similar to Android is as follows:

apply plugin: 'com.android.application'

android {
compileSdkVersion 29
buildToolsVersion "29.0.3"

defaultConfig {
    applicationId "com.example.myapplication"
    minSdkVersion 16
    targetSdkVersion 29
    / /...
}
/ /...
}
Copy the code

Android {} has a nested defaultConfig{}, but defaultConfig is not an extension. It is a method named defaultConfig that takes type Action and is an interface with only one execute method. Here I refer to the internal implementation of the Android plug-in to implement the nested DSL. The principle of the nested DSL can be simply understood as extending the corresponding class to define another class. The implementation of MyPlugin is as follows:

package com.example.plugin

import org.gradle.api.*
import org.gradle.api.model.*/ / new introduction

class MyPlugin implements Plugin<Project> {

    @Override
    void apply(Project project){
        // Create an extension named outerExt with the class outerExt. The create method returns an instance of outerExt
        def outerExt = project.extensions.create('outerExt', OuterExt.class)
        
        project.task('showExt'){
            doLast{
                    // Use the OuterExt instance and InnerExt instance
        	    println "outerExt = $outerExt, innerExt = ${outerExt.innerExt}"}}}static class OuterExt{
        def name
        def message
        // Define another class
        InnerExt innerExt
    
        // Annotate the construction with the ObjectFactory parameter using @inject
        @javax.inject.Inject
        public OuterExt(ObjectFactory objectFactory){
            // Create an instance of the InnerExt class using the newInstance method of the objectFactory
            innerExt = objectFactory.newInstance(InnerExt.class)}// Define a method whose argument type is Action and the generic type is InnerExt
        // The method name can be arbitrarily named, it will be used in build.gradle
        void inner(Action<InnerExt> action){
            // Call the Action's execute method, passing in the InnerExt instance
            action.execute(innerExt)
        }
    
        @Override
        String toString(){
            return "[name = $name, message = $message]"
        }
    
        static class InnerExt{
            def name
            def message
            
            @Override
            String toString(){
                return "[name = $name, message = $message]"}}}}Copy the code

This can be done using MyPlugin as follows:

//subproject_4/build.gradle

apply plugin: com.example.plugin.MyPlugin

/ / nested DSL
outerExt {
    name 'rain'
    message 'hello'

    // Use the inner method
    inner{
        name '9155'
        message 'word'}}// Run gradle showExt, output:
//outerExt = [name = rain, message = hello], innerExt = [name = 9155, message = word]
Copy the code

Inner {} is a method that takes an Action type. Gradle internally configs the closure behind the inner method to the InnerExt class. In general, the steps to define the nested DSL are as follows:

  • Define the bean class for the nested DSL. In this case, InnerExt.
  • Define a constructor with type ObjectFactory and annotate @inject (@inject is a class in the Javax package and ObjectFactory is a class in the Model package of Gradle). The construction of @inject annotation is called by Gradle to instantiate the class and Inject an ObjectFactory instance.
  • Create an instance of the bean class in construction using the newInstance method of the ObjectFactory object. (Objects instantiated by ObjectFactory can be configured by closures.)
  • 4. Define a method whose argument type is Action and whose generic type is the bean class of the nested DSL. In this case, call the execute method of the Action and pass in the bean class instance.

When the above four steps is nested DSL need to do, in the custom Plugin Gradle also provides us with a more flexible named nested DSL, through NamedDomainObjectContainer implementation, it is similar to the android buildType {}, as follows:

android {
    / /...
    buildTypes {
        release {
            / /..
        }
        debug {
             / /...}}}Copy the code

BuildTypes defines two namespaces: Release and Debug. Each namespace generates a BuildType configuration that can be used in different scenarios, and I can define more namespaces depending on the scenario: Test and testDebug buildTypes {} variable number of namespace, the namespace name, this is because the buildTypes internal are implemented through NamedDomainObjectContainer containers, we are interested in can consult our implementation, I won’t expand it out here.

It is also important to note that the extension bean class must be static if you define it in a custom Plugin class file. For example, the OuterExt and InnerExt classes are static, or they should be defined in a separate class file.

2. Write in a separate project

Writing it in a separate project is the same as writing it in the buildSrc directory, only there is an extra publishing process. Instead of creating a separate project for convenience, I will create a new subproject called gradle_plugin in GradleDemo. Then create a new directory under gradle_plugin: SRC /main/groovy and SRC /main/resources. Then just in the written buildSrc com. The example. The plugin. MyPlugin is copied to the SRC/main/groovy, finally in a repo GradleDemo new directory, as to the warehouse when will release the plugin, GradleDemo structure is as follows:

Gradle_plugin /build. Gradle: gradle_plugin/build. Gradle: gradle_plugin/build. Gradle Open gradle_plugin/build.gradle and add the following:

//gradle_plugin/build.gradle

// Apply the Groovy plugin
apply plugin: 'groovy'

dependencies {
    Gradle_plugin/SRC /main/groovy/ uses Gradle and Groovy syntax
    implementation gradleApi()
}
Copy the code

MyPlugin now have, we need a name to the plug-in, in gradle_plugin/SRC/main/resources directory meta-inf/gradle – new plugins directory, and then in that directory to create a new XX. The properties file, XX is the name you want for the plugin, which is the name after apply Plugin. For example, the andorid plugin is called com.android.application. So its properties file for the com. Android. Application. The properties, here I give my MyPlugin plug-in called MyPlugin, so I MyPlugin. New properties, as follows:

Open the myplugin.properties file and add the following:

# Define the implementation class of the custom plugin and the name of the plugin is the name of the properties file.'myplugin'
implementation-class=com.example.plugin.MyPlugin
Copy the code

Through the implementation – the class if you want to release the plug-in implementation classes, such as for com. Example. The plugin. MyPlugin, next we’ll release the plugin.

You can choose which repository you want to publish to, such as Maven or LVY. The most common one is Maven, so I choose Maven here. Gradle provides maven plugins to help us publish to Maven, and Maven has two types of repositories: local and remote. So the Maven plugin provides install and uploadArchives to help us publish the plugin to maven’s local repository and maven’s remote repository. Add the following to your gradle/build.gradle:

//gradle_plugin/build.gradle

// Apply the Maven plugin
apply plugin: 'maven'

// Upload the jar file and pom file to the local Maven repo. The local Maven repo path is ~ /. M /repository/ on the MAC and C:/ user/username /.m/repository/ on the window
install {
    repositories.mavenInstaller {
        / / by pom configuration custom plug-in group, an artifact and version, and referenced by the classpath when custom plug-in for: groupId: artifactId: version
        pom{
            groupId = 'com.example.customplugin'
            artifactId = 'myplugin'
            version = '1.0'}}}// Upload the JAR file and POM file of the custom plug-in through the uploadArchives task. You can upload the jar file and POM file to the specified local directory or remote repo address
uploadArchives{
    repositories.mavenDeployer{
        pom{
            groupId = 'com.example.customplugin'
            artifactId = 'myplugin'
            version = '2.0'
        }

        // Upload to local
        repository(url: uri('.. /repo'))

        // Upload to the remote end
        //repository(url: uri('http://remote/repo')){
        // authentication(userName: "name", password: "***")
        / /}}}Copy the code

Where pom{} configes the classpath path of the plug-in through groupId, artifactId, and version, that is, the path of the referenced plug-in. We will be in the dependencies use {} * * classpath ‘com. Example. Customplugin: myplugin: 1.0’ * * to refer to our release of the plugin.

If you want to install your maven project, run the following command: Gradle install = ‘/. M /repository/’; C:/ user/username /.m/repository/)

To execute the uploadArchives task, type Gradle uploadArchives on the command line in the directory where the Gradle project is located. Here I specify uploadArchives to be uploaded to the local GradleDemo/repo directory. You can also replace the uploadArchives with your remote Maven address. After the uploadArchives task is executed successfully, you will see the uploaded plugin JAR and POM files in GradleDemo/repo/ as follows:

Now that MyPlugin has been released, I have released versions 1.0 and 2.0 of MyPlugin to two different local directories. Let’s use the plugin.

Add the following to subproject_4/build.gradle:

//subproject_4/build.gradle

buildscript {
    repositories { 
        // Add maven local repo
        mavenLocal()
    
        // Add maven remote repo
        //mavenCentral()
    
        // Add the local path specified when uploadArchives was uploaded
        maven {
            url uri('.. /repo')
        }
    }

    dependencies {
        // Define the classpath path of the plug-in jar. Gradle will scan all jar files in this classpath when compiling
        / / the classpath 'com. Example. Customplugin: myplugin: 1.0'
        classpath 'com. Example. Customplugin: myplugin: 2.0'}}Copy the code

This uses the Project’s buildScript method, which uses the Repositories method and dependencies method to specify the repository and classpath that the current Project will depend on when it is built. When the project is built, it will go to the repository address defined by Repositories to find all jar files in the classpath directory. /repo’)}, where mavenLocal() corresponds to maven’s local repository (/.m/repository). If you upload to uploadArchives, you can add mavenCentral(), which represents maven’s remote address. In the dependencies method, you specify the classpath of the plug-in in the repository. Here, I specify the classpath of MyPlugin 2.0: Com. Example. Customplugin: myplugin: 2.0, it’s in the maven {url uri (‘.. /repo’)} repository.

Now we can use MyPlugin with the Apply Plugin, add the following to subproject_4/build.gradle:

// Reference the plug-in
apply plugin: 'myplugin'

// Use DSL to configure the properties of the plug-in
outerExt{
    name 'rain'
    message 'hello'

    inner{
        name '9155'
        message 'word'}}// Run gradle showExt, output:
//outerExt = [name = rain, message = hello], innerExt = [name = 9155, message = word]
Copy the code

The apply of the plugin at the back of the plugin name is our gradle_plugin/SRC/main/resources/meta-inf/gradle – the name of the plugins directory to write the properties file.

In the latest version of Gradle, the Maven plugin used in this article has been deprecated. Gradle provides the Maven-Publish plugin instead, but the overall publishing process is similar.

conclusion

In this article, Gradle features, project structure, life cycle, Task, custom Plugin and so on to comprehensively learn Gradle, master the above knowledge is enough to let you start Gradle, but if you want to further learn Gradle, master the knowledge of this article is completely insufficient. You may also need to be familiar with the use of various APIS in the Project, be able to customize a fully functional Plugin independently, and be able to write Gradle scripts to manage the Project. Here is a Gradle DSL Reference. You can find the meaning and usage of Gradle related configuration items when needed:

Gradle DSL Reference

For Android developers, there are also a number of configuration items in the Android Plugin we introduced. The Following Android Plugin DSL Reference allows you to find the meaning and usage of the configuration items in the Android Plugin:

Android Plugin DSL Reference

Source location of this article:

GradleDemo

The above is all the content of this article, I hope you have a harvest!

Reference content:

Gradle website

Gradle Learning series

Gradle plugin from the beginning to the advanced