Gradle, as an excellent build tool, is also the mainstream build tool of Android at present. No matter through the command line or through the GUI, Gradle is implemented behind the back, so it is very important to learn Gradle. Gradle components, plug-ins, hot fixes, and other technologies require a good understanding of Gradle. If you do not know Gradle, you will not be able to accomplish these things. Therefore, you must learn Gradle. This article mainly focuses on componentized Android projects, how to use Gradle to complete the extraction of common configuration items, Gradle how to define tasks, engineering test environment and formal environment automatic configuration, etc.

Gradle = Groovy/Kotlin + DSL

Gradle can be viewed as Groovy + Gradle DSL + Android DSL. The full name for DSL is Domain Specific Language, which means that the Language is not generic and can only be used in a Specific Domain. So a DSL is also a language. Groovy is a JVM language that, like Kotlin, is ultimately compiled into class files and executed on the JVM, so all of the Java language features are supported by Groovy, and we can write Java and Groovy together.

Groovy is as easy to use as JavaScript, with a much more flexible and simple syntax, lots of syntactic sugar, and closure features that allow you to achieve the same functionality as Java in much less code. Parsing XML files, for example, is very convenient in Groovy and can be done in a few lines of code, as opposed to dozens of lines in Java. I wrote a blog post on Groovy’s syntax, Using and Configuring Gradle, which briefly described Groovy printing strings, defining variables, methods, collections, closures, lists/maps, and more. For more detailed syntax, please refer to the official document www.groovy-lang.org/api.html.

Gradle execution order

Create a new Android project with Settings. gradle, build.gradle for the entire project, and build.gradle for the app Module.

Gradle scripts are executed in three steps:

1. Initialization: Analyze which modules will be built and create a corresponding project instance for each module. At this point the settings.gradle file will be parsed.

2. Configuration: deal with build scripts of all modules, deal with dependencies, attributes, etc. This is when the build.gradle file for each module is parsed and configured. This is when a linked list of the entire task is built (the linked list only refers to a collection of dependent tasks, not a linked list of data structures).

3. Execution: To execute a specific task according to the task list, all other tasks that this task depends on will be executed in advance.

Print the execution order in three separate files as follows:

The Task of Gradle

Define Gradle tasks

A Task is a Gradle unit of execution. Gradle builds tasks one by one.

For tasks defined in this way, the code inside the brackets is executed during the configuration phase, that is, whenever I execute any task, that code is executed because each task needs to be completely configured before it can be executed. But most of the time we don’t need to write configuration code, we want the code in parentheses to be executed only when executing our task, which can be done with doFirst or doLast. It should be noted that a project contains multiple tasks, and a Task contains multiple actions, where the Action is the specific operation required to complete a Task:

AMyTask execution results in the following:

. > Task :aMyTask before execute aMyTask2 before execute aMyTask1 after execute aMyTask1 after execute aMyTask2 BUILD SUCCESSFUL in 717msCopy the code

Note:println "run aMyTask"This code is not executed in the middle of the queue, it is executed during the configuration phase. All actions written directly to the Task are not added to the Action list, but are executed as the configuration information of the Task, so it is important to be aware of the declaration period. !

The extends DefaultTask extends the @taskAction annotation, which is equivalent to adding the annotated Action Action to the Action queue. See the following example where tasks can also inherit:

The Gradle API also provides other ways to create tasks:

tasks.create("aMyTask3"){
    println "run aMyTask3 ..."
}
Copy the code

Task can also inherit:

class MyTask4 extends DefaultTask { @TaskAction void action(){ println "run MyTask4 ..." Task aMyTask5 (type: MyTask4){doLast {println "run MyTask5..." }}Copy the code

The output is:

. > Task :aMyTask5 run MyTask4 ... run MyTask5 ... BUILD SUCCESSFUL in 686ms 1 actionable task: 1 executedCopy the code

Task properties and methods

Common attributes of Task are as follows:

The property name describe
actions The sequence of actions to be performed by the task
dependsOn Returns the task that the task depends on
description Task Description
enabled Whether the task is enabled
finalizedBy Returns the task after completion of this task
group Grouping of tasks
mustRunAfter Returns the task after which the task must run
name Mission name
path Path of task
project The Project to which the task belongs

Common methods of Task are as follows:

Method name (no arguments listed) describe
dependsOn Set dependency tasks for tasks
doFirst Add a Task action to the Task to start executing the previous action
doLast Add a Task action after the Task action has finished executing
finalizedBy Add a terminating task to a task, that is, a task to be executed after the task is finished
hasProperty Checks whether the task has the specified properties
mustRunAfter State that the task must be executed after certain tasks
onlyIf Add an assertion to a task that can be executed only if the condition is met
property Returns the value of the specified property
setProperty Modifies the value of the specified attribute

Gradle declares cycles

This is the order in which Gradle is executed.

1. Initialization phase

Build a project instance: include ‘: app ‘, ‘: lib1 ‘, ‘:lib2’

2. Configuration phase

Gradle script of all projects will be executed to configure the project object. An object consists of multiple tasks. Tasks and related information will be created and configured at this stage.

3. Operation stage

Execute the dependent Task based on the name of the Task passed by the Gradle command. The Task Action is executed at this stage.

Task dependencies and order

A Project has multiple tasks whose relationships are maintained by directed acyclic graphs. Directed acyclic graphs are generated during the configuration process of the build, which can be monitored using gradle. TaskGraph.

DependsOn Sets a dependency task for a task

// define taskA task taskA {doLast {println 'taskA run... '}} // define taskB extend taskB {dependsOn taskA // set doLast {println 'taskB run... '}} // Define taskC extend task taskC(dependsOn: DependsOn taskA {doLast {println 'TaskC run... '}} // define taskD task taskD {doLast {println 'taskD run... '}} // define taskE extend taskA, taskD task taskE {doLast {println 'taskE run... '}} taske. dependsOn taskA, taskD dependsOn taskA, taskDCopy the code

FinalizedBy sets the final task for a task.

// define A task taskA {doLast {println 'taskA run... '}} // define taskB task taskB {finalizedBy taskA // set taskA to the final task of taskB doLast {println 'taskB run... '}} // define taskC task taskC {doLast {println 'taskC run... '}} // define taskD task taskD {doLast {println 'taskD run... Taskd. finalizedBy taskA, taskCCopy the code

FinalizedBy configuration for a task is similar to dependsOn, but has the opposite effect. After a task is executed, the terminating task is executed.

MustRunAfter If tasKB. mustRunAfter(taskA) indicates that taskB must be executed after taskA. This rule is strict.

task taskA { doLast { println 'TaskA run ... ' } } task taskB { doLast { println 'TaskB run ... '}} // taskA must execute taskA after taskBCopy the code

Run the command./gradlew taskA taskB to see that taskB is executed first.

How to Skip Tasks

Sometimes tasks need to be disabled or executed under certain conditions. Gradle provides a variety of ways to skip tasks.

Method 1: Each task has a Enabled attribute to enable or disable the task. The default value is True, indicating that the task is enabled. If set to false, the task is disabled.

Run task disableTask {enabled false // doLast {println 'disableTask run... '}} disabletask.enabled = false // 2Copy the code

Method 2: Use onlyIf. The task can be executed only when true is returned in onlyIf.

Task onlyIfTestTask {doLast {println 'onlyIfTestTask run... Onlyiftesttask.onlyif {! project.hasProperty('xx') }Copy the code

Method 3: Use StopExecutionException. If a task throws this exception, Gradle skips the task and moves on to the next task.

Throw new StopExecutionException()}} Task taskB(dependsOn:) TaskA) {doLast {// Does not affect the execution of nextTask println 'taskB run... '}}Copy the code

Method 4: Use the timeout attribute of the Task to limit the execution time of the Task. Once a task times out, its execution is interrupted and the task is marked as failing. Gradle has built-in tasks that respond to timeouts.

Task taskB(dependsOn:) {doLast {thread.sleep (100000)} timeout = duration.ofmillis (500)} Task taskB(dependsOn:) TaskA) {doLast {// Does not affect the execution of nextTask println 'taskB run... '}}Copy the code

In fact, the most common methods are method one and method two.

Tasks are the main execution skeleton of Gradle. You can flexibly configure and adjust the dependencies, execution order, and execution rules of tasks through their various properties and methods.

Extract duplicates in Gradle

Build. gradle for MyLibrary and build.gradle for App Module have many duplicates. So how do you extract duplicates in this part?

Start by creating a new app_config.gradle in the project root directory and extracting the common configuration from it:

Ext {username = "changlin"; // Extract the common items, Define key-value map app_android = [compileSdkVersion: 30, buildToolsVersion: "30.0.3", minSdkVersion: 23, targetSdkVersion: 30, versionCode: 1, versionName: "1.0", testInstrumentationRunner: "Androidx. Test. Runner. AndroidJUnitRunner"] / / depends on relevant app_impl = [" appcompat ": 'androidx. Appcompat: appcompat: 1.2.0', "material" : 'com. Google. Android. Material: material: 1.3.0', "junit" : 'the junit: junit: 4.13.2', "androidx_junit" : 'androidx. Test. Ext: junit: 1.1.2', "androidx_espresso" : 'androidx. Test. Espresso: espresso - core: 3.3.0'] / / compile related content app_compile = [sourceCompatibility: JavaVersion.VERSION_1_8, targetCompatibility: JavaVersion.VERSION_1_8, ] }Copy the code

Add app_config.gradle to build.gradle for the entire project:

println 'build.gradle run ... App_config. gradle apply from: 'app_config.gradle' buildscript {... } allprojects { ... }Copy the code

Build. Gradle for app Module and MyLibrary Module

{id 'com.android.application'} println 'app -> build. Gradle run... '// Use app_config.gradle, RootProject is built-in object def my_name = this. RootProject. Ext username println my_name println "rootProject. Name = ${rootProject.name}" android { compileSdkVersion app_android.compileSdkVersion buildToolsVersion app_android.buildToolsVersion defaultConfig { applicationId "com.tal.learn_gradle" minSdkVersion app_android.minSdkVersion targetSdkVersion app_android.targetSdkVersion versionCode app_android.versionCode versionName app_android.versionName testInstrumentationRunner app_android.testInstrumentationRunner } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } compileOptions { sourceCompatibility app_compile.sourceCompatibility targetCompatibility App_compile. Dependencies targetCompatibility}} {implementation 'androidx. Constraintlayout: constraintlayout: 2.0.4' / / implementation app_impl.appcompat // implementation app_impl.material // testImplementation app_impl.junit // AndroidTestImplementation app_impl. Androidx_junit / / androidTestImplementation app_impl. Androidx_espresso / / more simplified way, App_imp. each {k, v -> implementation v println "import > ${k}"}}Copy the code

Mylibrary Module build.gradle:

plugins {
    id 'com.android.library'
}

def app_android = this.rootProject.ext.app_android

android {
    compileSdkVersion app_android.compileSdkVersion
    buildToolsVersion app_android.buildToolsVersion

    defaultConfig {
        minSdkVersion app_android.minSdkVersion
        targetSdkVersion app_android.targetSdkVersion
        versionCode app_android.versionCode
        versionName app_android.versionName

        testInstrumentationRunner app_android.testInstrumentationRunner
        consumerProguardFiles "consumer-rules.pro"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }

    compileOptions {
        sourceCompatibility app_compile.sourceCompatibility
        targetCompatibility app_compile.targetCompatibility
    }
}

dependencies {
    implementation app_impl.appcompat
    implementation app_impl.material
    testImplementation app_impl.junit
    androidTestImplementation app_impl.androidx_junit
    androidTestImplementation app_impl.androidx_espresso
}
Copy the code

Extract so many duplicate projects, and see the result, still successful compilation:

Environment parameters are automatically changed

For example, define two urls in app_config.gradle:

// debug/release server_URL APP_server_URL = ["debug": "http://test.xxx.com/xxx", "release": "http://product.xxx.com/xxx" ]Copy the code

Build. Gradle in your App Module:

BuildTypes {debug {// Test environment with debug_URL buildConfigField("String", "SERVER_URL", "\"${app_server_URL. Debug}\"")} release {// Use release_URL buildConfigField("String", "SERVER_URL", "\"${app_server_url.release}\"") ... }}Copy the code

BuildConfig automatically generates URL constants when you Build the project:

The resources

Gradle official manual docs.gradle.org/current/use…

The official API of the Groovy language www.groovy-lang.org/api.html