One, foreword
In our last article, We learned that The Android plugin is a custom Gradle plugin. Gradle is an open source automated build tool that focuses on flexibility and performance, and plug-ins are designed to package modular, reusable build logic. Specific logic can be implemented through plug-ins, packaged and shared with others. For example: Shence Android full buried point plug-in is through the plug-in to process specific functions during compilation, so as to realize the control click and Fragment page browsing full buried point collection.
In this article we will first introduce the basics of Gradle and then illustrate how to implement a custom Gradle plug-in. /gradlew is used to execute Gradle commands. For Windows users, you need to change gradlew to gradlew.
Gradle foundation
Gradle has two important concepts: Project and Task. In this section, we describe their respective roles and their relationships.
2.1 the Project profile
Project is the most important API for interacting with Gradle. We can understand what Project means by looking at the Project structure of Android Studio, as shown in Figure 2-1:
Figure 2-1 Structure of the Android Studio project
Figure 2-1 shows a project (named BlogDemo) used in the writing process, including app and Plugin modules. Both “Project” and “Module” are abstracted by Gradle into Project objects at build time. 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 children of that parent Project;
Every Project has a build.gradle configuration file, so when you create a Project using Android Studio, there is a build.gradle file in the root directory. Each Module has a build.gradle file in its directory.
Gradle uses settings. Gradle to build multiple projects. Figure 2-1 shows the relationships between projects.
The parent Project can obtain all the child Project objects, so that the parent Project can do some unified configuration in the corresponding build.gradle file, such as managing the dependency of the Maven central library:
. allprojects { repositories { google() jcenter() }}...Copy the code
2.2 introduction of Task
A Project executes a series of tasks during the build process. Gradle performs some basic work. Each Task performs some basic work. Gradle performs some basic work. For example, when you click on Android Studio’s Run button, Android Studio compiles and runs the project, essentially executing a series of tasks. This may include tasks compiling Java source code, tasks compiling Android resources, tasks compiling JNI, confusing tasks, tasks generating Apk files, tasks running apps, etc. You can also see what tasks are actually running in the Build Output of Android Studio, as shown in Figure 2-2:
Figure 2-2 Android Studio Build output logs
On the right, you can see that a Task consists of two parts: the name of the Module on which the Task resides and the name of the Task. When running a Task, you need to specify a Task in this way.
In addition, you can customize the implementation of your own Task, let’s create the simplest Task:
// add to build.gradletask hello { println 'Hello World! '}Copy the code
This code creates a Task named “hello”. If you want to execute this Task separately, you can type “./gradlew hello “in Terminal of Android Studio. After executing, you can see the console output “Hello World!” .
Build Gradle plugin
3.1 introduction of the Plugin
Plugins and Tasks are similar in that they encapsulate some business logic, and plugins can be used to package reusable compilation logic (that is, modularize some compilation logic). You can customize the Gradle plug-in, implement the necessary logic and publish it to a remote repository or share it as a local JAR package. This way, when you want to reuse it later or share it with others, you can refer directly to the remote repository package or to the local JAR package.
The most common Plugin is the Android Gradle Plugin. You can see the first line of the build.gradle file in the main Module of the project: “apply plugin: ‘com.android.application'”. This is android gradle plugin. “Com.android. application” refers to the plugin ID, which helps you generate a working APK file.
The plugin can also read the configuration written in the build.gradle file. The main Module’s build.gradle file contains a block named “Android” that defines properties such as the lowest system version supported by the App, the version number of the App, and so on. You can think of the “Android” Android blocks here as data classes or base classes, and define properties as class member variables. At runtime, the Android Gradle Plugin can take objects instantiated by “Android” blocks and run different compilation logic based on their property values.
3.2 Build a Gradle plug-in for a standalone project
Gradle plug-ins are implemented in three ways: Build Script, buildSrc Project, and Standalone Project:
Gradle file. The Plugin is visible only to the current build.gradle file.
2, buildSrc project is written in a logical rootProjectDir buildSrc/SRC/main/Java (the last folder can also be a groovy or kotlin, mainly depends on what language you use to implement custom plug-in) directory, Plugin only works for the current project;
Standalone project Standalone project is a Standalone project where the logic is written in a Standalone project and the JAR package can be directly compiled and published to a remote repository or local site.
For the purposes of this article we will focus on a Standalone project, the Gradle plug-in for a Standalone project.
3.2.1 Directory Structure Analysis
Figure 3-1 shows the general structure of a Gradle plug-in for an independent project:
Figure 3-1 Gradle plug-in project directory
Groovy folder and Resources folder in main folder:
- The groovy folder contains the source files (Gradle plug-ins can also be written in Java and Kotlin, the folder name depends on the actual language).
- Below the Resources folder are the resource files.
The resources folder contains a fixed format of meta-INF /gradle-plugins/ xxxx. properties. XXXX represents the id of the plugin that needs to be specified in the future.
Currently, Android Studio’s support for Gradle plugin development is not good enough, and many tasks that the IDE could have done have to be done manually. For example:
Android Studio cannot create a Gradle plugin Module directly. You can only create a Java Library Module and delete the extra folder.
2, Create a class by default, create a Java class, the filename suffix is “. Java “, to create a Groovy syntax class you need to manually create a file with the suffix “. Groovy “, and add the package and class declarations.
3, Resources need to be manually created, folder name need to pay attention to spelling;
Delete build.gradle from Module. Add gradle plugins, dependencies, etc.
3.2.2 Writing plug-ins
Before writing the plugin code, we need to make some changes to build.gradle, as follows:
apply plugin:
'groovy'
apply plugin:
'maven'
dependencies {
implementation gradleApi()
implementation localGroovy()
}
uploadArchives{
repositories.mavenDeployer {
// The local repository path, take the repo folder in the project root directory 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'
}
}
This is mainly divided into three parts:
1. Apply plugin: The ‘Groovy’ plugin is used because our project is developed in Groovy and the ‘Maven’ plugin will be used for later plugin releases;
Dependencies:
UploadArchive: Here are some maven-related configurations, including the location of the publish repository, groupId, artifactId, and version number.
With that in mind, you can start writing the source code. The Gradle Plugin requires that the entry class implement the org.gradle.api.Plugin interface and then implement its own logic in the apply implementation method:
package com.sensorsdata.pluginclass MyPlugin implements Plugin<Project>{ @Override void apply(Project project) { println 'Hello,World! ' }}Copy the code
In this example, the Apply method is the entry method for our entire Gradle plug-in, acting like the main method in various languages. The Project input type for the apply method is explained in section 2 and won’t be repeated here. Since Plugin classes and Project classes have many same names, it is important to select the org.gradle. API class when importing.
Finally, one more piece of preparation needs to be done: Gradle plugins do not automatically find entry classes. Instead, they require the developer to write the name of the entry class in resources/ meta-INF /gradle-plugins/ xxxx.properties. The content format is “implementation-class= fully qualified name of entry class”. Here the configuration of the sample project is as follows:
// com.sensorsdata.plugin.propertiesimplementation-class=com.sensorsdata.plugin.MyPluginCopy the code
3.2.3 Releasing plug-ins
When you’re done writing the plug-in, execute it on the terminal
./gradlew uploadArchiveCopy the code
You can publish the plug-in. The build.gradle file used to write the plug-in in the previous section pre-configured the configuration for publishing to the Maven repository, so when we execute this command here, the repo folder containing the packaged JAR files appears in the project root directory.
3.2.4 Using plug-ins
There are two main steps to using plug-ins:
(1) Declare the plug-in
Declaring plug-ins needs to be done at the Project level in the build.gradle file, which has a block called buildScript, The BuildScript block is divided into repositories block and Dependencies block. The Repositories block is used to declare the remote repository address of the dependencies to be referenced, and the Dependencies block is used to declare the dependencies for a specific reference. Here we use the JAR package just published to the local repo folder as an example, the reference code is as follows:
// We just released the plugin to the repo folder URL under the root directory'repo' } } dependencies { // classpath '$group_id:$artifactId:$version' classpath 'com. Sensorsdata. Myplugin: myplugin: 1.0.0' }}Copy the code
(2) Application plug-ins
Application plug-ins need to be done in the build.gradle file at the Module level:
// apply plugin: 'plugin id'apply plugin: 'com.sensorsdata.plugin'Copy the code
After completing the above steps, you will see the plug-in output “Hello,World!” in the build log at every compile time. .
3.3 Configurable plug-ins
If you want the plugin to be more flexible, you will usually reserve some configurable parameters, such as the android SDK version, build-Tools version, and so on that can be configured and compiled in the “Android” block of the main Module. This configuration of the “Android” block is a Gradle Extension. Let’s create a custom Extension.
3.3.1 Creating the Extension class
Creating a class for Extension is simple: Just create a normal class, and the properties defined in the class are the configurations Extension can receive. It does not need to inherit any classes, nor does it need to implement any interfaces, as follows:
class MyExtension{ public String nam = "name" public String sur = "surname"}Copy the code
3.3.2 Instantiating the Extension object
Extensions can be created and managed through ExtensionContainer. The ExtensionContainer object is retrieved through the getExtensions method of the Project object:
def extension = project.getExtensions().create('myExt',MyExtension)project.afterEvaluate { println("Hello from " + extension.toString())}Copy the code
The code snippet above can be copied directly into the Apply method or used in the build.gradle file. Here we use the create method to create Extension. Let’s look at the definition of the create method:
<T> T create(String name, Class<T> type, Object... constructionArguments);Copy the code
1. Name: represents the name of the Extension to be created. For example: build.gradle block named “Android”. For example, if the Extension name created by the Android Gradle plugin is “Android”, no other Extension name can be used with “Android”.
2. Type: Indicates the class type of this Extension. The class here is the class created in the previous section.
3, constructionArguments: Class constructor argument values.
After using the create method, you might be tempted to print out the value of the Extension object immediately on the next line, but if you do, you might find that the Extension object prints the wrong value. No matter how you configure it in build.gradle, the Extension object will not read the value. To see why, review the example here and see that the logic printed in the example is written in the afterEvaluate method. The way this is written has a lot to do with the life cycle of a Gradle plug-in, which we will cover in the next section.
Gradle build lifecycle
Gradle is a dependency based language at its core. In Gradle terms this means that you can define tasks and dependencies between tasks. Gradle ensures that tasks are executed in the order of their dependencies and that each Task is executed only once. These tasks form a directed acyclic graph based on the dependencies. At the heart of Gradle is the fact that it uses internal build tools to complete such diagrams before executing any Task. This design makes a lot of things that would otherwise be impossible possible.
Each Gradle build goes through three different stages:
Gradle builds single and multiple projects, so in the initialization phase, Gradle determines which projects to participate in the build according to settings. Gradle creates a Project instance for each Project. Android Studio projects and Modules are projects for Gradle.
2. Configuration phase: In this phase, Project objects are configured and all Project build scripts are executed. For example, Extension objects, Task objects, etc. are put into Project objects at this stage.
3. Execution phase: After the configuration phase, all tasks are in the Project object. The Task name specified by the terminal command is then searched for the corresponding Task from the Project object and executed.
Gradle provides a number of lifecycle listening methods to perform specific tasks at specific stages. A Gradle lifecycle diagram is drawn based on the order in which the callbacks are executed, as shown in Figure 4-1:
Figure 4-1 Gradle life cycle process diagram
In the figure, there are projection. beforeEvaluate and projection. afterEvaluate, which are triggered before and after a Project is configured, respectively. In the previous example, afterEvaluate was used here, optimized to afterEvaluate{} because the last argument to the method was a closure.
The value of Extension written in build.gradle can be obtained only after the configuration is complete. This happens because the logic written directly into the Apply method is executed during the configuration phase.
Five, the summary
This article first introduces the basic knowledge of Gradle, including Project and Task, and then explains the detailed process of custom Gradle plug-in from creation to use. I hope to provide some help in writing a custom Gradle plug-in.
In this paper, the author
Gu Xin
God data | SDK technical adviser
My name is Gu Xin, shence Data Android technology consultant. Shence Data is the first company I worked for, and it is very good. I like to do Android related development and also like to contact with emerging technologies. I hope to learn and make progress together with you in the open source community.
The copyright of this article belongs to “Shence Data open source community”, commercial reprint please contact us for authorization; Non-commercial reprint please indicate the source, and attach the Shence data open source community service number two-dimensional code.