Like attention, no more lost, your support means a lot to me!

🔥 Hi, I’m Chouchou. GitHub · Android-Notebook has been included in this article. Welcome to grow up with Chouchou Peng. (Contact information at GitHub)


directory


1. Pre-knowledge

In Gradle, each element in the build. Gradle script is mapped to a Java class. Two of the most important classes are “Project” and “Task”. In this article, I will take you through the principles and uses of Gradle projects and tasks, and analyze the life cycle of Gradle builds.

The content of this article will involve the following pre/related knowledge, dear I have prepared for you, please enjoy ~

  • Gradle preconditions: “Gradle” | an article (concept & Groovy & configuration & command)

  • Official documentation: Project API documentation

  • Official documentation: Task API documentation


2. Project Project

Project represents a build component. During the “build-initialization phase”, Gradle engine instantiates one Project instance for each build. Gradle script file. So the build logic we write in build.gradle is essentially written inside the Project class.

org.gradle.api.Project.java

public interface Project extends Comparable<Project>, ExtensionAware, PluginAware {
}
Copy the code

Editting…


3. Task

A Project consists of one or more tasks, which represent an executable Task.

org.gradle.api.Task.java

public interface Task extends Comparable<Task>, ExtensionAware
}
Copy the code

3.1 define the Task

There are three main types of syntax for defining tasks:

  • Using the task function:
task('hello')
task('world') {
}
Copy the code
  • Using DSL languages:
task(hello)
task(world) {
}
Copy the code
  • Use the Tasks container:
tasks.create('hello')
tasks.create('world') {
}
Copy the code

3.2 Task attribute

Once a Task is defined, you can set properties. There are two main syntax for setting properties:

  • Set when creating Task:
task hello(group : 'MyTask')
Copy the code
  • Call setter Settings:
task hello {
    setGroup('MyTask')
}
Copy the code

Properties can be accessed using the $sign or getter method, for example:

task hello(group : 'MyTask') {
    println "$group"
    println getGroup()
}
Copy the code

Some common attributes in Task are:

attribute describe
name Task identifier, specified when defining a Task
group The group to which Task belongs
description Description of the Task
type The Task type defaults to DefaultTask (seeIn section 3.7)
actions Action list (seeIn section 3.3)
dependsOn Dependent Task collection

The three most important concepts of Task are “Task action”, “Task dependency”, and “Task configuration” :

3.3 the Task action

Actions are the build logic in a Task that runs in the Build-execute phase. The Task interface provides two methods to declare Task tasks:

action describe
doFirst(Closure) Represents the first action of the Task
doLast(Closure) Represents the last action of the Task

Such as:

task helloworld {
    doFirst {
        println "hello"
    }
    doLast {
        println "world"
    }
}
Copy the code

In addition, you can add actions outside the Task closure:

task helloworld

helloworld.doFirst { println "hello" }
helloworld.doLast { println "world" }
Copy the code

3.4 Task input/output

3.5 the Task depends on

A Task does not exist on its own. A dependsOn method is used to declare a dependency on one or more tasks, for example:

task first { doFirst { println "first" } } task second { doFirst { println "second" } } task third { doFirst { println DependsOn ([first,second]) $dependsOn([first,second]) $dependsOn([first,second])Copy the code

3.6 the Task allocation

Code that is not defined in Task actions is Task configuration, which is completely different: A Task configuration runs in the Build-configure phase and a Task action runs in the Build-execute phase, where the configuration code is always executed before the action code.

3.7 Customizing tasks

3.8 the Task type


4. Understand the Gradle build lifecycle

Whenever a Gradle build is executed, there are three life cycle stages — “initialize” & “configure” & “execute” :

4.1 Initiliazation Phase

During the initialization phase, Gradle parses the setting. Gradle file in the project root directory and constructs a Settings instance. Settings is an interface that contains the following methods:

org.gradle.api.initialization.Settings.Java

public interface Settings extends PluginAware, ExtensionAware { String DEFAULT_SETTINGS_FILE = "settings.gradle"; void include(String... var1); void includeFlat(String... var1); File getSettingsDir(); File getRootDir(); Gradle getGradle(); . }Copy the code

You should be familiar with the include method, which specifies the project to participate in the build (build.gradle). During the initialization phase, Gradle instantiates a Project instance for each contained Project.

setting.gradle

include ':app'
Copy the code

In setting.gradle, you can also do these things:

  • Include specifies the items in the directory

By default, Gradle looks for included projects in the project root directory, so if you want to include projects in other directories, you can configure it like this:

Project (:video).projectdir = new File(".. \\libs\\video")Copy the code
  • Listening life cycle

In setting.gradle, you can add listeners to the nodes in the build process by adding listeners to setting.getgradle () :

setting.gradle

Include ':app' gradle.addBuildListener(new BuildListener() {void buildStarted(gradle var1) {println '[build]'} void SettingsEvaluated (Settings var1) {// Note: Cannot access var1.gradle.rootProject from here, Println '[settings.gradle parses]'} void projectsLoaded(gradle var1) {println '[initialization phase ends]'} void ProjectsEvaluated (Gradle var1) {println 'BuildResult var1'} void buildFinished(BuildResult var1) {println 'BuildResult var1'}}Copy the code

The Build Output is as follows:

Executing tasks: [:app:generateDebugSources] in project E:\workspace ... Task :app:preBuild up-to-date > Task :app:preDebugBuild up-to-date  > Task :app:checkDebugManifest UP-TO-DATE > ... > Task :app:assembleDebug BUILD SUCCESSFUL in 14s 24 actionable tasks: 19 executed, 5 up-to-dateCopy the code

In addition to setting listener methods, you can also set closures directly, for example:

Gradle. ProjectsLoaded {println '[end of initialization]'}Copy the code

4.2 Configuration Phase

In the configuration phase, Gradle parses the build. Gradle file in each Project, completes the “Project configuration” and “Task configuration”, and creates a directed acyclic graph based on the Task dependencies. Such as:

build.gradle

apply plugin: 'com.android.application' println 'configure app project' task hello {println' configure Hello task' doFirst{println 'hello action '}} .Copy the code

The Build Output is as follows:

Executing tasks: [:app:assembleDebug] in project E:\workspace\... Settings. gradle parses complete > Configure project :app configures Hello Task > Task :app:preBuild UP-TO-DATE > Task :app:preDebugBuild UP-TO-DATE > Task :app:checkDebugManifest UP-TO-DATE > ... > Task :app:assembleDebug BUILD SUCCESSFUL in 14s 24 actionable tasks: 19 executed, 5 up-to-dateCopy the code

Confusing: The Task configuration runs in the Configuration phase and the Task action runs in the Execution phase.

  • Listen for Task directed acyclic graph generation

setting.gradle

gradle.getTaskGraph().addTaskExecutionGraphListener(new TaskExecutionGraphListener() { @Override void GraphPopulated (TaskExecutionGraph graph) {println '[Task directed acyclograph generation]'}})Copy the code

4.3 Execution Phase

After obtaining a directed acyclic graph of a Task, the execution phase is to execute the Task actions sequentially based on dependencies.

4.4 Monitoring the Construction Time

long beginOfSetting = System.currentTimeMillis() def beginOfConfig = false def configHasBegin = false def BeginOfProjectConfig = new HashMap() def beginOfProjectExecute gradle.projectSloaded {println" " + (System.currentTimeMillis() - beginOfSetting) + "ms" } gradle.beforeProject { project -> if (! configHasBegin) { configHasBegin = true beginOfConfig = System.currentTimeMillis() } beginOfProjectConfig.put(project, System.currentTimeMillis()) } gradle.afterProject { project -> def begin = beginOfProjectConfig.get(project) println "[configuration" + project + "], time: "+ (System. CurrentTimeMillis () - the begin) +", "ms} gradle. TaskGraph. WhenReady {println" 】 【 configuration phase is over, the total time consuming: " + (System.currentTimeMillis() - beginOfConfig) + "ms" beginOfProjectExecute = System.currentTimeMillis() } gradle.taskGraph.beforeTask { task -> task.doFirst { task.ext.beginOfTask = System.currentTimeMillis() } task.doLast { Println "execute" + task +" "+ (System.currentTimemillis () -task.beginofTask) + "ms"}} gradle.buildfinished {// If the project contains buildSrc, The build to buildSrc projects will beginOfProjectExecute the null pointer if(null! + (system.currentTimemillis () -beginOfProjectexecute) + "ms"}}Copy the code

5. To summarize

  • Initialization phase: parse setting.gradle to instantiate Project instances for each contained Project;
  • Gradle file in each Project, complete “Project configuration” and “Task configuration”, and create a directed acyclic graph based on Task dependencies.
  • Execution phase: Task actions are executed sequentially based on dependencies.


The resources

  • Gradle in Action. By Benjamin Muschko
  • An in-depth exploration of Gradle automated build technology (3) by JsonChao

Creation is not easy, your “three lian” is chouchou’s biggest motivation, we will see you next time!