Android developers have almost always had direct or indirect contact with Gradle. After all, using AS to guide projects is a problem: stuck in Gradle:Build Running

Half an hour later, it’s still building Running and you’re like oh my God, what the hell is this thing doing??

Learning Gradle may help you understand the process and make some optimizations to speed up compilation

For example, you can customize tasks or write Gradle plugins to reduce the size of APK, such as resource oblization tool AndResGuard, multi-channel packaging, and faster compilation.

Mastering Gradle while Mastering Gradle for free, learn Gradle systematically.

0x1. First introduction to Gradle

1. Automated build tools

Gradle** is defined as follows:

The automated build tool is written in pure Java and based on Ant and Maven concepts. It focuses on flexibility and performance. The build scripts are written in Groovy or Kotlin’s Domain-specific language (DSL) instead of cumbersome CONFIGURATION based on XML.

Word opening:

  • Build – the process of generating executable programs from source code;
  • Automation → Replacing some manual work with machines;

Build in Android is reflected in: compile source code → generate. Apk executable file, there is such a construction flow chart in Android official website:

Brief analysis of the process:

  • The IDE converts the source code into dex and the rest into compiled resources;
  • The APK wrapper combines the DEX and compiled resources into a single APK;
  • The packer optimizes the application using the Zipalign tool to sign for APK;

Of course, this is a highly abstract process, but the actual packaging process is much more complex:

  • Aapt command → generate r.Java file;
  • Aidl command → Generate Java files corresponding to AIDL;
  • Javac command → Compile Java source file to generate class file;
  • Dx.bat → class to class. Dex file
  • . Etc.

Think about it if every time you pack APK, you have to manually use a variety of commands and tools in order to pack it, how low the efficiency is. If you meet a dozen channel packages, you will die in place!

Tips: Different app markets may have different statistical needs, and you need to release an install pack for each market, which is called a channel pack.

Human beings are prone to make mistakes, especially in the repetitive task of manual intervention, where the channel identification is messed up and the wrong package is often made.

The work of pipelined iterative builds can be written into a script (build script) that is packaged each time it is executed and packaged automatically, allowing developers to concentrate on writing functional code to improve development efficiency.

The content of the build script is to follow the build process, execute commands, call tools, and finally output the generated executable file to a specific directory.

When it comes to scripts, some people just jump right in and prepare Python and Bash, but you don’t have to.

Open source automated build tools are great. There’s no need to reinvent the wheel


2. The differences between Ant, Maven and Gradle

In the early days of Android, Eclipse was used as an IDE, and the automatic construction tool → Apache Ant, written in Java, platform independent, based on the idea of task chain, adopted XML as the construction script, the default file is build.xml, and the basic configuration template is as follows:


        
<project name="HelloWorld" default="run" basedir=".">  
<property name="src" value="src"/>  
<property name="dest" value="classes"/>  
<property name="jarfile" value="hello.jar"/>  
<target name="init">  
   <mkdir dir="${dest}"/>  
</target>  
<target name="compile" depends="init">  
   <javac srcdir="${src}" destdir="${dest}"/>  
</target>  
<target name="build" depends="compile">  
   <jar jarfile="${jarfile}" basedir="${dest}"/>  
</target>  
<target name="test" depends="build">  
   <java classname="test.ant.HelloWorld" classpath="${hello_jar}"/>  
</target>  
<target name="clean">  
   <delete dir="${dest}" />  
   <delete file="${hello_jar}" />  
</target>  
</project> 
Copy the code

You can clearly see that the script defines five targets: init, compile, build, test, clean, and Target.

They also define their dependencies through depends to form the order of execution. Compile is executed before build Target, and init before compile.

Ant also supports custom targets, but there’s a problem: there’s no way to manage dependencies. Projects rely on third-party libraries, and you have to manually copy the correct version of the package into the lib directory.

Maven will be automatically packaged into your project at build time, as shown in the following example:


      
<project . xmlns...>
    <groupId>cn.coderpig</groupId>
    <artifactId>Test</artifactId>
    <version>1.0.0 - the SNAPSHOT</version>
		 
    <dependencies>
        <dependency>
            <groupId>com.squareup.okhttp3</groupId>
            <artifactId>okhttp</artifactId>
            <version>4.9.1</version>
        </dependency>
    </dependencies>
</project>
Copy the code

> coderpig:Test: 1.0.0-snapshot > coderpig:Test: 1.0.0-snapshot Com. Squareup. Okhttp3: okhttp: 4.9.1, Maven automatically wrapped okhttp incoming, no will automatically go to the download (remote warehouse, through the repository tag specified).

Instead of defining tasks by Target in Ant, Maven abstractions a build lifecycle that meets most projects: Clean → default → site, requiring users to use plug-ins for a given build lifecycle.

Good normalization is good, but it also brings a problem: custom build requirements are cumbersome to implement and not as flexible as Ant.

Also, Both Ant and Maven use XML to define build scripts, which are less readable and extensible, and less capable of performing tasks (no loops, trouble judging conditions, etc.).

So here comes a more powerful Gradle that combines the strengths of Ant and Maven:

  • ① Abandon tedious XML and write build scripts using Grovvy DSL or Kotlin DSL;
  • (2) It can automatically drop jar packages like Maven, namely dependency management;
  • (3) There is a default standard build lifecycle, but also supports flexible custom tasks, etc.
  • (4) Build level supports gradual migration from Ant and Maven;

Tips: Domain Specific Language (DSL) is a computer Language that focuses on Specific Domain problems. For example, SQL supports only database-related operations and regular expressions support only retrieval and substitution of strings. To express in a concise form, intuitive and easy to understand, so that the cost of reading and adjusting the code can be reduced, is to seek refinement not wide.

The following three differences are briefly summarized:

Ant supports automatic packaging logic, Maven has more automatic jar packages, standardized packaging logic, but is not easy to customize. Gradle can automatically load jar packages and write its own scripts, which are much easier to write than Ant.


Gradle download configuration

Gradle is based on the JVM and requires a Java environment (version 1.8 or higher). Download Gradle from Gradle’s official website.

I am used to the latter. Sometimes I need to look at the source code and documentation. After downloading the ZIP package, decompress it and configure the environment variable GRADLE_HOME.

Path environment variable added:

After the configuration is complete, open the terminal and type gradle -v to verify that it works.


4. Gradle Wrapper

By default, Android Studio uses Gradle Wrapper instead of Gradle directly, and commands use gradlew instead of Gradle.

Gradle Wrapper is a Wrapper around Gradle so that developers don’t have to worry about Gradle versions.

To create a new directory, type the following command:

gradle wrapper
Copy the code

The generated file directory structure is as follows:

├ ─ ─. Gradle ├ ─ ─ gradle │ └ ─ ─ wrapperwrapper | └ ─ ─ gradle - wrapper. Jar / / used to download gradle required; | └ ─ ─ gradle - wrapper. The properties / / configuration file; ├─ Gradlew // Executable scripts for Linux; └─ gradlew. Bat //Windows executable script;Copy the code

Type gradlew build. If the gradlew version is not locally available in the configuration file, the Wrapper process will be started to download Gradle.

The gradle-wrapper.properties configuration file contains the following contents:

Windows: C:\Users\ username. Gradle directory, open to see download each gradle version:

There is another benefit to encapsulating a layer: Gradle can be used to build projects on machines that do not have Gradle installed, with one caveat:

Each Gradle version corresponds to one Daemon. The basic starting point of Gradle is 512 MB. If your computer is poorly configured, you should avoid running multiple Gradle versions at the same time. Suggestion: Manage Gradle by yourself, that is, create Gradle environment locally. Configure AS AS AS follows:


5. Initial experience with build scripts

Type the following command to create a new build.gradle file and compile it:

touch build.gradle
echo println("Hello Gradle!"); >> build.gradle
gradlew
Copy the code

The output is as follows:

Gradle is executed by looking for a file named build.gradle or build.gradle. KTS from the current directory and executing its contents.

Gradle Init can also automatically initialize different types of projects:

The following uses the Kotlin Application project as an example. Open the directory to view the files created:

Gradle builds the project by typing gradlew build:


6. Where did all the bags go

The question is: where are all the downloaded third-party dependencies?

A: ~/. Gradle /cache/dodules-2/files-2.1/ package name/library name/version number /hash string /, as shown in the following example:

If you don’t want to run gradle under ~/. Gradle, add the environment variable GRADLE_USER_HOME.

Later, gradle downloads things into this directory:

The above changes do not take effect in Android Studio, and sometimes you need to configure them yourself:


The execution architecture of Gradle

When I tried to delete the C:\Test directory, I found that I could not delete it:

A process is occupying the folder. What process is that? A: For daemon processes, type gradle –status to see a wave:

Process ID: 10276 → Process is idle (BUSY indicates that a task is being built) → Additional information: 6.1.1, Open task Manager to locate the process:

Java code is compiled into class bytecode and runs on the JVM. Use the JDK’s jVisualvm.

It’s a daemon daemon called GradleDaemon, so why have a daemon resident in the background?

Here’s Maven for a start:

Maven starts a Maven JVM process during build time and shuts it down after build. It starts every Time Maven builds are used, and loading the required JAR files is a time-consuming process.

Gradle 3.0 uses Daemon mode by default:

Start a very lightweight client JVM process that communicates only with the background Deamon JVM process. After building the client process, the Deamon process is closed, but the Deamon process is still in IDLE state. When building the client process next time, directly enable the Deamon process to reduce the construction time. By default, the Deamon process is kept in the background for three hours. If it is not started during this period, it is shut down.

0x3. Gradle configuration

Gradle is configured in three places, with the following priorities:

Command line arguments > ~ /. Gradle/gradle properties > project root directory/gradle properties

Make a list of the most commonly used command line options, so that you can get a sense of them and use them again (see official documentation for more details).

# command structuregradle [taskName...]  [--option-name...]Incremental compilation: the same task in the same project will not be executed more than once unless necessary;
# cache: reuse cached results as long as the Task input remains the same, regardless of whether the Task is in the same project.

# the Tasks to perform
gradle myTask   Execute a Task
gradle :my-subproject:taskName  Execute tasks in the subproject
gradle my-subproject:taskName   Gradle clean is executed for all subprojects.
gradle task1 task2  # Run multiple tasks
gradle dist --exclude-task test # exclude a task from execution
gradle dist -x test # same as above
gradle test --rerun-tasks   # force an up-to-date Task TO perform full compilation instead of incremental compilation;
gradle test --continue  By default, if the Task fails, the build will fail.

Common tasks (and Task conventions between plug-ins)
gradle build
gradle run
gradle check
gradle clean    Delete the build directory

# Build details
gradle projects List all subprojects
gradle tasks    # list all tasks (tasks assigned to Task groups)
gradle tasks --group="build setup"  List tasks for a specific Task group
gradle tasks --all  List all tasks
gradle -q help --task libs  View the details of a Task
gradle myTask --scan    # Generate visual build reports
gradle dependencies # list project dependencies
gradle -q project:properties    List the project properties

# Debug options-? , -- - hhelp  # Help message- v - version# Version information
-s, --stacktrace    Print the exception stack trace.
-S, --full-stacktrace   # More complete information than above;

# Performance dependent
--build-cache   # reuse cache
--no-build-cache    # Do not reuse cache, default
--max-workers   # Maximum number of processors
--parallel  # Build projects in parallel
--no-parallel   # Build projects in parallel
--priority  # Gradle specifies the priority of the process started
--profile   Generate a performance report


# daemon
--daemon # Build using the Deamon process
--no-daemon # Build without the Deamon process
--foreground    Start the deamon process
--status    # View running and recently stopped Deamon processes.
--stop  # Stop all deamon processes of the same version

# log options
-q, --quiet Log only errors
-w, --warn
-i, --info 
-d, --debug
--console=(auto,plain,rich,verbose) # specify the output type
--warning-mode=(all,fail,none,summary)  # specify the warning level

# Execute options
--include-build # Composite build
--offline   # Offline Build
--refresh-dependencies  # Force to clear the dependent cache
--dry-run Look at the order of Task execution without actually executing the Task
--no-rebuild # Do not duplicate build project dependencies

# Environment options
-b, --build-file # specify the build file
-c, --settings-file # specify the Settings file
-g, --gradle-user-home  # specify the default.Gradle directory
-p, --project-dir   # specify the starting directory of Gradle
--project-cache-dir Specifies the cache directory, default. Gradle
-D, --system-prop   # Set JVM system properties
-I, --init-script   # specify the initialization script
-P, --project-prop  # specify the project attributes of the root project.
Copy the code

Some properties can be configured in a gradle.properties file instead of entering them from the command line. See System Properties for more information

0x4, Gradle base

1. Gradle build lifecycle

Gradle builds in three phases:

2. Lifecycle Hooks

3. Grovvy Basic Grammar

Grovvy is a scripting language for the JVM, a dynamic language based on Java extensions that, in addition to Java compatibility, adds new features such as closures.

Gradle compiles **. Gradle ** Groovy scripts into. Class Java bytecode files that run on the JVM.

Gradle is an automated build tool that runs on the JVM. Groovy is a language based on the JVM. The relationship between the two is similar to Android and Java.

The syntax of Gradle is simple. If you are interested in Groovy, you can go to the Groovy API.

Since Gradle 4.0, Kotlin DSL scripts are officially supported. Both can coexist. This section is based on Groovy DSL. (The syntax is so similar that Kotlin will migrate to Groovy without any pressure.)

① Basic rules

  • Comments are consistent with Java and support: // or /**/
  • Do not end with a semicolon;
  • Single quoted strings do not escape the $sign. Double quoted strings can use string templates. Triple quoted strings are formatted strings.
  • Method parentheses can be omitted and return is not required. The last line of code is returned by default.
  • Blocks of code can be passed as arguments

Definition (def keyword definition)

// Define variables: Groovy supports dynamic typing. You can define variables without specifying a type

def a = 1   // Define integer types. The Groovy compiler wraps all primitive types as object types
def b = "String: ${a}"  // Define a string template
de double c = 1.0   // Define type Double
Copy the code

③ Declare variables

// Local variables, visible only in the scope in which they are declared
def dest = "dest"
task copy(type: Copy) {
    from "source"
    into dest
}

// ext Additional properties. All enhanced objects in the Gradle domain model can hold additional user-defined properties
ext {
    springVersion = "3.1.0. RELEASE"
    emailNotification = "[email protected]"
}

task printProperties {
    doLast {
        println springVersion
        println emailNotification
    }
}

// Variables declared with type modifiers are visible in closures but not in methods
String localScope1 = 'localScope1'

println localScope1

closure = {
    println localScope1
}

def method() {
    try {
        localScope1
    } catch (MissingPropertyException e) {
        println 'localScope1NotAvailable'
    }
}

closure.call()
method()

// Output result:
// localScope1
// localScope1
// localScope1NotAvailable
Copy the code

(4) function

// A function with no return value uses the def keyword, and the result of the last line of code is the return value
// No parameter function
def fun1() { }

// has a parameter function
def fun2(def1, def2) { }

// If the function return type is specified, the def keyword can not be added
String fun3() { return "Return value" }

// The effect is the same as fun3
String fun4() { "Return value" }

// Function calls can be made without parentheses
println fun3
Copy the code

(5) cycle

// I before b, output 5 tests
for(i = 0; i <5; i++) { println("Test")}// Output 6 tests
for(i in 0.. 5) {
    println("Test")}// If you want to output 5, you can change it to:
for(i in 0.. <5)

// The number of loops from 0 to 4
4.times{
    println(${it}")}Copy the code

⑥ Ternary operator, judgment

// Consistent with Java, short counting can also be abbreviated like this:
def name = 'd'
defresult = name? :"abc"

// Is it still useful? Null, same as Kotlin, person not null → Data property not null → print nameprintln person? .Data? .name// asType is a type conversion
def s3 = s1.asType(Integer)
Copy the code

All landowners closure

Closures are essentially blocks of code that can be passed as functions by variables at run time, preserving access to the scope of the variables that define them.

// Define closure
def clouser = { String param1, int param2 -> // The arrow is preceded by the parameter definition and followed by the code
    println "Code part"
}

// Call the closure
clouser.call()
clouser()

// If the closure has no parameters, it implies an it parameter, similar to this
def noParamClosure = { it-> true }

// The last argument to a function is a closure and the parentheses can be omitted, similar to the use of a callback function
task CoderPig {
    doLast ({
      println "Test"  
    })
}

task CoderPig {
    doLast {
      println "Test"}}// All key variables in a closure refer to the same variable when there is no closure nesting, and when there is a closure:
// this: class at closure definition;
// owner, delegate: which closure object is closest to it;

// Closure delegate: Each closure has a delegate object, which Groovy uses to find
// Variables and method references that are not local variables or parameters of the closure are proxy mode
class Info {
    int id;
    String code;

    def log() {
        println("code:${code}; id:${id}")}}def info(Closure<Info> closure) {
    Info p = new Info()
    closure.delegate = p
    // Delegate mode takes precedence
    closure.setResolveStrategy(Closure.DELEGATE_FIRST)
    closure(p)
}

task configClosure {
    doLast {
        info {
            code = "cix"
            id = 1
            log()
        }
    }
}

// Output: Task :configClosure
// code:cix; id:1
Copy the code

Today the collection

// Array, the definition of the extension as follows, other similar to Java
def array1 = [1.2.3.4.5] as int[]
int[] array2 = [1.2.3.4.5]

/* List: linked List, corresponding to ArrayList, variables defined by [], the element can be any object. * /

// Define a list
def testList = [1.2.3] 

// Add elements, shift left to add new elements
testList << 300;    
testList.add(6)
testList.leftShift(7)

/ / delete
testList.remove(7)
testList.removeAt(7)
testList.removeElement(6)
testList.removeAll { return it % 2= =0 }   // Custom rules

/ / to find
int result = testList.find { return it % 2= =0 }
def result2 = testList.findAll { return it % 2! =0 }
def result3 = testList.any { return it % 2! =0 }
def result4 = testList.every { return it % 2= =0 }

// Get the minimum, maximum, and number of conditions
list.min()
list.max(return Math.abs(it))
def num = findList.count { return it >= 2 }

/ / sortingTestlist.sort () sortlist.sort {a, b -> a == b?0 : 
            Math.abs(a) < Math.abs(b) ? 1 : - 1
} 

/* Map: key table, corresponding to LinkedHashMap, defined with: colon, key must be a string, can not be quoted around */

/ / access
aMap.keyName
aMap['keyName']
aMap.anotherkey = "i am map"
aMap.anotherkey = [a: 1.b: 2]

/ / traverse
def result = ""
[a:1.b:2].each { key, value -> 
    result += "$key$value" 
}
// Traversal with index (counter from 0, two arguments passed when map.entry object)
[a:1.b:2]. EachWithIndex {entry, index, -> result +="$entry$index" 
}

[a:1.b:2]. EachWithIndex {key, value, index, -> result +="$key$value$index" 
}

/ / group
def group = students.groupBy { def student ->
    return student.value.score >= 60 ? 'pass' : 'Fail'
}

/* Range, an extension to List */
def range = 1.. 5
println(range)  // Output: [1, 2, 3, 4, 5]
range.size()  / / the length
range.iterator() / / the iterator
def s1 = range.get(1)   // Get the element labeled 1
range.contains(5)  // Whether to include element 5
range.last()    // The last element
range.remove(1) // Remove the element labeled 1
range.clear()   // Clear the list
println("Number one:"+range.from) // The first data
println("One last statistic:"+range.to)   // Last data
Copy the code

4. [‘ t? : n]

Gradle builds consist of one or more projects. An example of printing Project information is as follows (build. Gradle), which creates two child modules:

Def printProject(pj) {// define a method to print an item. Println (" = = = = = = = = = = = = = = = ") println (" access to information of the project: ") println (" the project name: "+ pj. Name) println (" path:" + pj. Path) println (" the project description: "+ pj.description) println(" Directory containing build script:" + pj.projectdir) println(" Build file directory: "+ pj.builddir) println(" Which group belongs to: "+ pj.group) println(" Def getAllProjectInfo() {// root project printProject(project) // all subprojects, call getallprojectprojects () to getAllprojects, EachWithIndex {Project entry, int I -> printProject(entry)}} // Call getAllProjectInfo()Copy the code

The running results are as follows:

You can also configure a Project, such as:

Project ('module1') {description(" submodule 1") version("v0.0.1") group("Test")}Copy the code

More apis can be found in the Project class:

5. Task (Task)

① Creating a Task

/* === ① Create a Task customTask1, Execute this Task with gradlew customTask1 === */ def customTask1 = Task ("customTask1") CustomTask1. doFirst {println "Task [${name}] → DoLast {println "Task [${name}] → execute "} > Task :customTask1 // Task [customTask1] → Before // Task [customTask1] → After /* ========== ====== */ def customTask2 = task(group: 'test', "customTask2") customTask2.doLast {println(" Task: "+ name +" Belongs to group: "+ CustomTask2.group)} // Output: > Task :customTask2 // Task :customTask2 Belongs to group: Test / * = = = = = = = = = = = = = = = = = (3) closure method to realize the = = = = = = = = = = = = = = = = = = = * / task customTask3 {group (" test ") description (" task name + closure to create task ") DoLast {println(" Task: "+ name +" Belongs to group: "+ CustomTask2.group)}} // Output: > Task :customTask3 // Task :customTask3 Belongs to group: Test / * = = = = = = = = = = = = = = = = = = = = = = = (4) by TaskContainer create = = = = = = = = = = = = = = = = = = = = * / tasks in the create (" customTask4 ") {group (" test ") Description (" created by TaskContainer ") doLast {println(" Task: "+ name +" Belongs to group: "+ CustomTask2.group)}} > Task :customTask4 // Task :customTask4 Belongs to group: The test / * = = = = = = = = = = = = = = = = = = = = 5 custom Task = = = = = = = = = = = = = = = = = = = * / class CustomTask extends DefaultTask {/ / @taskaction def doSomeThing() {println(" doSomeThing ")}} Task (type: CustomTask, "CustomTask5 ") {doFirst {println" 1 before task execution "} doLast {println "2 after task execution"} doFirst {println "2 before task execution}} > Task: customTask5 / / Task 1/2 / / Task execution before/after perform some action / / Task / * = = = = = = = = = = = = = = = = = = = = attached: The Map can be used in the configuration of the = = = = = = = = = = = = = = = = = = = * / / / type - based on an existing Task to create, similar to class inheritance, Default value DefaultTask // overwrite → Whether to replace the existing Task (usually used in combination with type) false // dependsOn → Configure the current Task dependency. Default [] // Action → An action or closure added to a task, default null // Description → Task description, default null // group → Task group, default nullCopy the code

② Access Task

// With the attribute name, the task created will be an attribute of Project, Customtask3. name // Access tasks['customTask1'] from TaskContainer collection. Name // Access tasks['customTask1'] from TaskContainer find or get The former cannot be found and returns null, Tasks.findbyname ("customTask1") Tasks.findByName ("customTask1") tasks.findByName("customTask1") Tasks.getbypath (":customTask1") tasks.getByName("customTask1") // Access CustomTask1.name by task nameCopy the code

③ Task execution

Gradle tasks can be configured before or after a task is executed using doFirst and doLast. For an example, see ⑤ customizing tasks above, and you can also get all executable actions via getActions().

④ Task sequencing

You can use the shoundRunAfter and mustRunAfter methods to control which of the two tasks executes first;

⑤ Enable and disable tasks

The default value is true, which indicates that the Task is enabled. For example, customTask1.enable = true

⑥ Task onlyIf assertion

Assertions are conditional expressions, and a Task has a onlyIf method that takes a closure as an argument and executes the Task if the closure returns true. This allows you to control which tasks need to be executed using the assertion of the Task. For example: Package channel packages on demand.

⑦ Task rules

The created tasks are in the TaskContain, and you can add the responding task rules using its addRule method.

8 Task input and output

Task provides inputs and outputs attributes.

⑨ Mounts custom tasks to the build process

During the execution of a Task, call the execute() method of another Task.

Tips: I have a general impression of the API, which will be explained in depth in the future


reference

  • Mastering Gradle
  • Evolution of build tools: Ant, Maven, Gradle
  • Wikipedia: Gradle
  • A deep exploration of Gradle Automated Build technology
  • Gradle Pit Climbing Guide – Introduction
  • This time, get to know Gradle inside out!
  • Juejin. Cn/post / 693281…

This article is part of the “Gold Nuggets For Free!” Event, click to view details of the event