Teach you to write gradle plug-in | data acquisition

Time:2022-5-7

1、 Foreword

In the previous article “Introduction to Shence Android full buried plug-in”, we learned that Shence Android plug-in is actually a custom gradle plug-in. Gradle is an open source automated build tool that focuses on flexibility and performance, and the role of plug-ins is to package modular and reusable build logic. You can implement specific logic through plug-ins and package them to share with others. For example, Shence Android full buried point plug-in processes specific functions during compilation through the plug-in, so as to realize the collection of full buried points for control click and fragment page browsing.

In this article, we will first introduce the basic knowledge of gradle, and then give an example to illustrate how to implement a custom gradle plug-in. It should be noted here that: the text adopts/ Gradlew executes the command of gradle. If you are a Windows user, you need to change it to gradlew bat。

2、 Gradle Foundation

Gradle has two important concepts: project and task. This section will introduce their respective roles and relationships.

2.1 project introduction
Project is the most important API for interacting with gradle. We can understand the meaning of project through the project structure of Android studio, as shown in Figure 2-1:
Teach you to write gradle plug-in | data acquisition

Figure 2-1 Android studio project structure
Figure 2-1 shows a project (named blogdemo) used in the writing process, including app and plugin modules. Here, both “project” and “module” will be abstracted into project objects by gradle during construction. Their main relationships are:

1. A project in the Android studio structure is equivalent to a parent project, and all modules in a project are the child projects of the parent project;

2. Each project will correspond to a build Gradle configuration file, so when using Android studio to create a project, there is a build. Exe in the root directory Gradle file, there is a build in the directory of each module Gradle file;

3. Gradle is through settings Gradle file is used for multi project construction. The relationship between projects can also be seen from Figure 2-1.

The parent project object can get all the child project objects, which can be created in the build corresponding to the parent project Make some unified configurations in the gradle file, for example: manage the dependent Maven Central Library:


allprojects {

repositories {
    google()
    jcenter()
}

}

2.2 task introduction

Project will execute a series of tasks during the construction process. The Chinese translation of task is “task”. In fact, its function is to abstract a series of meaningful tasks. In gradle’s official words, each task performs some basic work. For example, when you click the run button of Android studio, Android studio will compile and run the project. In fact, this process is completed by executing a series of tasks. It may include: task of compiling java source code, task of compiling Android resources, task of compiling JNI, confused task, task of generating APK file, task of running app, etc. You can also see which tasks are actually running in the build output of Android studio, as shown in Figure 2-2:
Teach you to write gradle plug-in | data acquisition

Figure 2-2 Android studio build output log
From the right side of the figure, we can see that a task consists of two parts: the name of the module where the task is located and the name of the task. When running a task, you also need to specify a task in this way.

In addition, you can customize and implement your own task. Let’s create the simplest task:

// add to build.gradle
task hello {

println 'Hello World!'

}
The meaning of this code is to create a task named “hello”. If you want to execute the task alone, you can enter “. / gradlew hello” in the terminal of Android studio. After execution, you can see the console output “Hello world!”.

3、 Gradle plug-in build

3.1 plugin introduction

Plugin and task have little difference in terms of their functions. They both encapsulate some business logic. The applicable scenario of plugin is to package the compilation logic that needs to be reused (that is, modularize part of the compilation logic). You can customize the gradle plug-in, publish it to the remote warehouse or share it as a local jar package after realizing the necessary logic. In this way, when you want to use it again or share it with others, you can directly reference the package of the remote warehouse or the local jar package.

The most common plugin should be the Android gradle plugin officially provided by Android. It can be found in the build. Of the main module of the project The first line of the gradle file reads: “apply plugin: ‘com. Android. Application'”, which is the Android gradle plugin. “Com. Android. Application” refers to the plugin ID, which is used to help you generate a runnable APK file.

The plug-in can also read and write in build Configuration in gradle file. Build. Of main module There will be a block named “Android” in the gradle file, which defines some attributes, such as the minimum system version supported by app, the version number of app, etc. You can compare the “Android” Android block here to a data class or base class, and the defined attributes to the member variables of the class. Android gradle plugin can get the object instantiated by “Android” block at runtime, and then run different compilation logic according to the attribute value of the object.

3.2 build gradle plug-ins for independent projects

Gradle plug-in can be implemented in three ways: build script, buildsrc project and standalone project:

1. Build script will write the logic directly in build In the gradle file, plugin only applies to the current build Gradle file visible;

2. Buildsrc project is to write the logic in rootprojectdir / buildsrc / SRC / main / Java (the last path folder can also be groovy or kotlin, which mainly depends on what language you use to implement the custom plug-in). Plugin only works for the current project;

3. Standalone project is to write logic in an independent project, which can directly compile and publish jar package to remote warehouse or local.

For the purpose of writing this article, we mainly explain the standalone project, that is, the gradle plug-in of the independent project.

3.2.1 directory structure analysis

The general structure of the gradle plug-in of an independent project is shown in Figure 3-1:

Teach you to write gradle plug-in | data acquisition
Figure 3-1 schematic diagram of gradle plug-in project directory
The main folder is divided into groovy folder and Resources folder:

Under the groovy folder is the source file (the gradle plug-in also supports writing in Java and kotlin, where the folder name is determined according to the actual language);
Under the Resources folder is the resource file.
Under the Resources folder is meta-inf / gradle plugins / XXXX in fixed format Properties, XXXX represents the plugin ID that needs to be specified when using the plug-in in the future.

At present, Android studio’s support for gradle plug-in development is not good enough. We need to manually complete many tasks that could have been completed by the IDE, such as:

1. Android studio cannot directly create a module of gradle plug-in, but can only create a module of Java library type first, and then delete the redundant folder;

2. The new class is a new Java class by default, and the new file name suffix is “. Java”. To create a new class with groovy syntax, you need to manually create a new file with “. Groovy” suffix, and then add package and class declarations;

3. The whole resource needs to be created manually, and the folder name needs to be spelled carefully;

4. Delete the build. Of the module The whole content of gradle, plus the gradle plug-ins and dependencies required for the development of gradle plug-ins.

3.2.2 writing plug-ins

Before writing the plug-in code, we need to build Gradle is modified as follows:

apply plugin: ‘groovy’
apply plugin: ‘maven’

dependencies {

implementation gradleApi()
implementation localGroovy()

}

uploadArchives{

repositories.mavenDeployer {
    //Local warehouse path. Take the repo folder under the root directory of the project as an example
    repository(url: uri('../repo'))
    //Groupid, self defined
    pom.groupId = 'com.sensorsdata.myplugin'
    //artifactId
    pom.artifactId = 'MyPlugin'
    //Plug in version number
    pom.version = '1.0.0'
}

}
It is mainly divided into three parts:

1. Apply plug-in: the ‘groovy’ plug-in is applied because our project is developed in groovy language. The ‘Maven’ plug-in will be used when releasing the plug-in later;

2. Dependencies: declare dependencies;

3. Uploadarchive: here are some Maven related configurations, including the location of the publishing warehouse, groupid, artifactid and version number. For debugging convenience, the location is selected in the repo folder under the project root directory.

After making the above preparations, you can start to write the source code. The gradle plug-in requires the entry class to implement org gradle. api. Plugin interface, and then implement its own logic in the implementation method apply:

package com.sensorsdata.plugin
class MyPlugin implements Plugin<Project>{

@Override
void apply(Project project) {
    println 'Hello,World!'
}

}
In the example here, the apply method is the entry method of our whole gradle plug-in, which is similar to the main method of various languages. The input parameter type of the apply method has been explained in Section 2, and will not be repeated here. Since plugin class and project class have many classes with the same name, be sure to choose org. Org when importing gradle. Classes under API package.

Finally, a preparatory work needs to be done: the gradle plug-in does not automatically find the entry class, but requires developers to write the class name of the entry class in resources / meta-inf / gradle plugins / XXXX In properties, the content format is “implementation class = fully qualified name of entry class”. The configuration of the example project here is as follows:

// com.sensorsdata.plugin.properties
implementation-class=com.sensorsdata.plugin.MyPlugin

3.2.3 release plug-ins

After writing all the contents of the plug-in, execute it at the terminal

./gradlew uploadArchive
You can publish the plug-in. In the previous section, write the build. Of the plug-in The configuration related to publishing to Maven warehouse is configured in advance in the gradle file. Therefore, after we execute this command here, the repo folder will appear in the project root directory, which contains the packaged jar file.

3.2.4 using plug-ins

There are two main steps to use plug-ins:

(1) Declaration plug-in

Declare that the plug-in needs to be built at the project level Gradle file, in build There is a block in the gradle file called buildscript, which is divided into repositories block and dependencies block. The repositories block is used to declare the remote warehouse address of the dependency to be referenced, and the dependencies block is used to declare the dependency of the specific reference. Here, take the jar package just published to the local repo folder as an example. The reference code is as follows:

buildscript {

repositories {
    maven{
        //We just published the plug-in to the repo folder under the root directory
        url 'repo'
    }
}
dependencies {
    // classpath '$group_id:$artifactId:$version'
    classpath 'com.sensorsdata.myplugin:MyPlugin:1.0.0'
}

}
(2) Application plug-in

The application plug-in needs to build at the module level Done in gradle file:

// apply plugin: ‘plugin id’
apply plugin: ‘com.sensorsdata.plugin’
After completing the above steps, you can see the “Hello, world!” output of the plug-in in in the compilation log every time you compile.

3.3 configurable plug-ins

If you want the function of the plug-in to be more flexible, you will generally reserve some configurable parameters, such as the compiled Android SDK version and build tools version can be configured in the “Android” block of the main module. The configuration of “Android” block is gradle’s extension. Let’s make a custom extension.

3.3.1 create extension class

Creating a class for extension is very simple: you only need to create a new ordinary class, and the properties defined in the class are the configurations that the extension can receive. It does not need to inherit any classes or implement any interfaces, as shown below:

class MyExtension{

public String nam = "name"
public String sur = "surname"

}

3.3.2 instantiating extension objects

You can create and manage extensions through the extensioncontainer. The extensioncontainer object can be obtained through the getextensions method of the project object:

def extension = project.getExtensions().create(‘myExt’,MyExtension)
project.afterEvaluate {

println("Hello from " + extension.toString())

}
The above code fragment can be copied directly into the apply method or placed in build Used in gradle files. Here we use the Create method to create an extension. Let’s look at the definition of the Create method:

<T> T create(String name, Class<T> type, Object… constructionArguments);
1. Name: represents the name of the extension to be created, for example: build For the block named “Android” in gradle, the Android gradle plug-in needs to fill in “Android” when creating this extension. The name of the extension cannot duplicate the existing one. For example, if the extension name created by the Android gradle plug-in is “Android”, then other extension names can no longer use “Android”;

2. Type: the class type of the extension. The class here is the class created in the previous section. Note that the attribute name of the class should be consistent with that in the extension;

3. Constructionarguments: the constructor parameter value of the class.

After using the Create method, you may be eager to print the value of the extension object immediately on the next line, but if you do so, you will find that the value printed by the extension object is not correct. Whether you’re building How to configure in gradle, the extension object just can’t read the value. For specific reasons, you can review the examples here, and you will find that the printed logic in the example is written in the afterevaluate method. The writing here is closely related to the life cycle of the plug-in. We will introduce the life cycle of the gradle plug-in in the next section.

4、 Gradle build lifecycle

The official definition of the life cycle of gradle Construction: the core of gradle is a dependency based language, which means that you can define the dependencies between tasks in gradle terms. Gradle will ensure that these tasks are executed in the order of dependencies, and each task will be executed only once. These tasks form a directed acyclic graph according to the dependencies. Gradle will use internal build tools to complete such a diagram before executing any task, which is the core of gradle. This design makes many impossible things possible.

Each gradle build goes through three different stages:

1. Initialization phase: gradle supports single project and multi project construction, so in the initialization phase, gradle will be built according to settings Gradle identifies the projects that need to be built and creates a project instance for each project. Android studio project and module are both projects for gradle;

2. Configuration phase: in this phase, project objects will be configured, and all project construction scripts will be executed. For example, extension objects and task objects are put into project objects at this stage;

3. Execution phase: after the configuration phase, all task objects are in the project object. Then, it will find the corresponding task object from the project object according to the task name specified in the terminal command and execute it.

Gradle provides many life-cycle monitoring methods to perform specific tasks at specific stages. Some callback methods are selected here, and a schematic diagram of gradle life cycle process is drawn according to the execution order, as shown in Figure 4-1:

Teach you to write gradle plug-in | data acquisition
Figure 4-1 schematic diagram of gradle life cycle process
In the life cycle callback method in the figure, there are project Beforeevaluate and project After evaluate, they are triggered before and after project configuration. In the previous example, the afterevaluate here is used. Since the last parameter of the method is closure, the writing method is optimized to afterevaluate {}.

For the object created with create, the printed value is naturally incorrect when the corresponding project has not been configured. It can only be correctly obtained after the configuration is completed and written in build Extension value in gradle. This happens because the logic written directly into the apply method is executed during the configuration phase.

5、 Summary

This paper first introduces the basic knowledge of gradle, including project and task, and then focuses on the detailed process of creating and using custom gradle plug-ins. I hope it can provide some help for writing custom gradle plug-ins.

Source: official account Shence technology community