This period of time to learn Gradle, also experienced gradle from a preliminary understanding to basic familiarity, and then to in-depth source code such a process of some twists and turns. Hence the idea of writing such a step-by-step article into the principle.

This article is about the basics of Gradle. After reading this article, you can:

  • Be clear about gradle’s definition and the pain points it solves
  • Basic understanding of how Android Gradle works
  • Basic understanding of gradle syntax
  • Learn basic Groovy development

If you want to learn more about Gradle, stay tuned for more gradle articles.

what is gradle?

Let’s take a look at gradle from Wikipedia.

Gradle is a project automation build tool based on Apache Ant and Apache Maven concepts. Instead of traditional XML, it uses a Groovy-based domain-specific language to declare project Settings. Currently, support is limited to Java, Groovy, and Scala, with plans for more languages in the future.

For those of you who are new to Gradle, this definition is not very well understood. You may copy a few configurations from online tutorials, but don’t understand the principles behind them. Gradle is a build tool. Gradle is based on maven concepts. Gradle is declared in Groovy. To understand these words, consider a few scenarios.

1. Channel management: There are dozens of large and small mobile phone market in China, and there are five or six big mobile phone manufacturers, each of which may have different customized ROM. If we want to adapt for different markets and vendors, we need to write code like this

if(isHuawei) {
    // dosomething
} else if(isOppo) {
    // dosomething
}
Copy the code

In this case, needless to say, a large amount of useless code is compiled into APK for a single phone, affecting package size and speed. To solve this problem, gradle introduces productFlavor and the ability to buildType, which can be packaged as needed. So it’s an automated build tool. You can look at the official documentation

2. Dependency management: We typically introduce a variety of tripartite libraries into our projects for code reuse. For example, manually add a JAR or AAR copy to the project and then add dependencies. The drawbacks of this approach are obvious. First, the configuration and deletion process is cumbersome, and second, the same JAR may be referenced by multiple projects, resulting in multiple JARS being copied unknowingly. Finally, versioning is tough. To solve this problem, Gradle is based on Maven repositories. When configuring and deleting repositories, you only need to operate on the coordinates of the repositories. All libraries are managed by Gradle.

So gradle is not a mystery, just a build tool based on a language (Groovy, Java, Kotlin). As long as we have a rough grasp of the basic usage and its internals, we should be able to understand the meaning of the commands we search online in our daily work. skr~

Gradle for Android

Let’s take a look at a few Gradle files that you often use in your daily work. The build.gradle root file usually puts the general configuration for the entire project. The build.gradle file under each module is the configuration for each module itself.

buildscript {
    ext.kotlin_version = '1.2.71'
    repositories {
        google()
        jcenter()
    }
    dependencies {
        classpath 'com. Android. Tools. Build: gradle: 3.2.1'
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
    }
}
allprojects {
    repositories {
        google()
        jcenter()
    }
}
Copy the code

Buildscript, AllProjects, Repositories, dependencies, executables, executables, executables, executables, executables, executables These are gradle’s specific grammars, called Domain-specific languages (DSLS). You can refer to the official website. Here you can see the AllProjects proxy is for each project, which can be interpreted as each module we write, which is the configuration for each module we write. Buildscript mainly configures packaging related things, such as gradle version, Gradle plugin version, etc., which are configured for the build tool itself. Repositories, Dependencies is the repository and coordinates of the repositories. So the build.gradle in the root directory is the entire configuration.

Build. gradle in module contains android, Dependencies and other configuration items.

apply plugin: 'com.android.application'

android{
    ...
}
dependencies{
    ...
}
Copy the code

Some of you may wonder why we don’t see the Android configuration item on the official website. This is mainly because it is not a Gradle DSL, but rather android-specific in the sense that it comes with the Android plugin ‘com.android.application’. If we delete the first line, we will find that the Android {} configuration item is missing.

Gradle configuration items are either built with Gradle or defined by various plug-ins. If you don’t know the configuration item, go to the official website to check it. It is better to give people fish than to give people fish. We will also explain how to import plug-ins and how to define plug-ins and configuration items later.

Settings. gradle this file determines whether each module participates in the build. Settings. gradle is a switch for each module. If you turn this module off, you cannot use it. Other modules that depend on it will also fail.

3. Gradle.properties add and modify parameters that can be used directly during the build process. Not only can you add custom parameters, you can also modify system parameters oh ~

Settings. gradle configures all modules involved in building a project. Each module has its own build.gradle that configures its own modules. This is an overview of android builds. Of course, after reading this part, I still don’t know how to write, so let’s go to the code level.

Groovy – Learn gradle keys

Gradle can be written in Groovy, Kotlin, Java, and other languages, but Groovy is the most popular gradle configuration. Let’s talk about groovy basics. Not too much, just enough.

1. The string

Groovy strings are divided into two types: java.lang.String and groovy.lang.gString. The single and triple quotation marks are strings. Double quotes are of type GString. Support for placeholder interpolation operations. Like Kotlin, Groovy’s interpolation operations are marked with ${} or $. ${} is used as a general substitution for strings or expressions, and $is mostly used in the A.B form.

def number = 1 
def eagerGString = "value == ${number}"
def lazyGString = "value == ${ -> number }"

println eagerGString
println lazyGString

number = 2 
println eagerGString 
println lazyGString
Copy the code

2. The Character Character

Groovy has no explicit Character. But you can enforce it.

char c1 = 'A' 
assert c1 instanceof Character

def c2 = 'B' as char 
assert c2 instanceof Character

def c3 = (char)'C' 
assert c3 instanceof Character
Copy the code

4.List

Groovy lists are very similar to Python lists. Support dynamic expansion, support to place a variety of data. Use methods to support def and direct definition. It can also be indexed as python does

//List stores any type
def heterogeneous = [1."a".true]

// Determine the default type of List
def arrayList = [1.2.3]
assert arrayList instanceof java.util.ArrayList

// Use as to force type
def linkedList = [2.3.4] as LinkedList    
assert linkedList instanceof java.util.LinkedList

// Define the specified type List
LinkedList otherLinked = [3.4.5]          
assert otherLinked instanceof java.util.LinkedList

// Index like Python
assert letters[1] = ='b'
// Negative numbers are indexed from right to left
assert letters[- 1] = ='d'    
assert letters[2 -] = ='c'
// Specify item assignment judgment
letters[2] = 'C'             
assert letters[2] = ='C'
// Append item to List
letters << 'e'               
assert letters[ 4] = ='e'
assert letters[- 1] = ='e'
// Get a subset of List
assert letters[1.3] = = ['b'.'d']         
assert letters[2.4.] = = ['C'.'d'.'e'] 
Copy the code

5.Map

// Define a Map
def colors = [red: '#FF0000'.green: '#00FF00'.blue: '#0000FF']   
// Get some values for the specified key
assert colors['red'] = ='#FF0000'    
assert colors.green  == '#00FF00'
Copy the code

6. Operator

  • ** : the power operator.
  • ? . : security placeholder. Avoid null-pointer exceptions like Kotlin.
  • @ : direct domain access operator. Groovy automatically supports property getters, but sometimes we have a special getter that we write ourselves, and we can use the direct domain access operator when we don’t want to call that particular getter. This is similar to Kotlin’s
  • The.& : method pointer operator, because closures can be used as arguments to one method, or as arguments to another method if you want a method to be arguments to another method.
  • ? : : binary operator. Similar to kotlin.
  • *.The expansion operator is used to obtain a collection of elements that perform the specified methods on each element of the original collection.
cars = [
    new Car(make: 'Peugeot'.model: '508'),
   null.new Car(make: 'Renault'.model: 'Clio')]
assert cars*.make == ['Peugeot'.null.'Renault']     
assert null*.make == null 
Copy the code

What’s important in Groovy is the concept of closures. The official definition is that “a closure in Groovy is an open, anonymous block of code that accepts arguments, returns values, and assigns variables”. Much like Kotlin’s lambda functions, closures are defined before they are executed. But there are some subtle differences. Let’s take a closer look at Gradle closures.

Closures are blocks of code that can be used as method parameters. Groovy’s closures are more like a code block or method pointer that is defined somewhere and then executed on subsequent calls. A Closure is really an instance of a Closure type. It’s written very much like Kotlin’s lambda function.

Our common closure looks like this

// The basic closure
{ item++ }                                          
// Use -> to separate arguments from code
{item -> item++ }                                       
// Use the implicit parameter it
{ println it }                               
// Use the displayed name argument
{ name -> println name }

// Call the method
a.call()
a()

// Groovy's closures support arguments whose last argument is of variable length.
def multiConcat = { int n, String... args ->                
    args.join(' ')*n
}
Copy the code

Notice, if we just write a = {item++}, that’s just defining a closure, and it won’t work. You must call a.call() to run it. So you get the idea that a closure is just a block of code. We can run it when we need it. Isn’t that similar to lambda functions?

And if you look at the website, you’ll see that there are some statements that say,

What is a delegate? There are three kinds of objects inside closures.

  • This corresponds to the class that defines the closure and, if defined in an inner class, refers to the inner class
  • Owenr corresponds to the class or closure that defines the closure. If defined in a closure, owenr corresponds to the closure, otherwise identical to this
  • The delegate defaults to the owner, or to a custom delegate pointing to

This and owner are easier to understand. We can get this using the closure’s getxxx method

def thisObject = closure.getThisObject()
def ownerObject = closure.getOwner()
def delegate = closure.getDelegate()
Copy the code

The most important thing is the delegate object. Closures can set a delegate object, and the point of setting a delegate is to associate the closure with a specific object. Let’s start with an example, using custom Android closures as an example.

android {
    compileSdkVersion 25
    buildToolsVersion "25.0.2"

    defaultConfig {
        minSdkVersion 15
        targetSdkVersion 25
        versionCode 1
        versionName "1.0"}}Copy the code

This closure corresponds to two entity classes.

# Android.groovy
class Android {
    private int mCompileSdkVersion
    private String mBuildToolsVersion
    private ProductFlavor mProductFlavor

    Android() { this.mProductFlavor = new ProductFlavor() } void compileSdkVersion(int compileSdkVersion) { this.mCompileSdkVersion  = compileSdkVersion } void buildToolsVersion(String buildToolsVersion) { this.mBuildToolsVersion = buildToolsVersion } void defaultConfig(Closure closure) { closure.setDelegate(mProductFlavor) closure.setResolveStrategy(Closure.DELEGATE_FIRST) closure.call() } @Override StringtoString() {
        return "Android{" +
                "mCompileSdkVersion=" + mCompileSdkVersion +
                ", mBuildToolsVersion='" + mBuildToolsVersion + '\'' + ", mProductFlavor=" + mProductFlavor + '}' } } # ProductFlavor.groovy class ProductFlavor { private int mVersionCode private String mVersionName private int mMinSdkVersion private int mTargetSdkVersion def versionCode(int versionCode) { mVersionCode = versionCode } def versionName(String versionName) { mVersionName = versionName } def minSdkVersion(int minSdkVersion) { mMinSdkVersion = minSdkVersion } def targetSdkVersion(int targetSdkVersion) { mTargetSdkVersion = targetSdkVersion } @Override String toString() { return "ProductFlavor{" + "mVersionCode=" + mVersionCode + ", mVersionName='" + mVersionName + '\'' + ", mMinSdkVersion=" + mMinSdkVersion + ", mTargetSdkVersion=" + mTargetSdkVersion + '}' } }Copy the code

And then when we define it, we write it

Def android = {compileSdkVersion 25 buildToolsVersion"25.0.2"
        defaultConfig {
            minSdkVersion 15
            targetSdkVersion 25
            versionCode 1
            versionName "1.0"Android.delegate = bean Android.call () println bean.toString() // Print the result Android{mCompileSdkVersion=25, mBuildToolsVersion='25.0.2', mProductFlavor=ProductFlavor{mVersionCode=1, mVersionName='1.0', mMinSdkVersion=15, mTargetSdkVersion=25}}
Copy the code

This allows you to assign the values declared in the closure to two objects, Android and ProductFlavor, to handle.

In the image above, ScriptHandler is set as a buildScript delegate. This means that buildScript defines parameters that are used by ScriptHandler. If you are interested, you can go to ScriptHandler source ~

Project and Task-Gradle build systems

Now that we’ve covered the basics of gradle configuration, you probably know how to write gradle. You may not understand gradle’s build system. Here we are going to dive into gradle’s building systems, Project and Task. This looks like something to think about down here.

Task is the smallest executable unit in gradle scripts. The class diagram is as follows:

Note that the default name of a Gradle build script is build. Gradle. When running Gradle in a shell, Gradle will look for a file named build. Gradle in the current directory. So only tasks defined in build.gradle are valid.

Tasks can be declared in three ways. We can define tasks according to our own project needs. For example, custom tasks take over gradle compilation

task myTask2 << {
    println "doLast in task2"
}

Task (String name)
project.task("myTask3").doLast {
    println "doLast in task3"
}

Taskcontainer.create (String name
project.tasks.create("myTask4").doLast {
    println "doLast in task4"
}
Copy the code

TaskContianer is used to manage a collection of Task instances. You can get TaskContainer instances from project.gettasks (). Common interfaces:

findByPath(path: String): Task
getByPath(path: String): Task
getByName(name: String): Task
withType(type: Class): TaskCollection
matching(condition: Closure): TaskCollection

/ / create a task
create(name: String): Task
create(name: String, configure: Closure): Task 
create(name: String, type: Class): Task
create(options: Map
       
        )
       ,>: Task
create(options: Map
       
        , configure: Closure)
       ,>: Task

// Listen when tasks are added to TaskContainer
whenTaskAdded(action: Closure)
Copy the code

Gradle supports incremental compilation. Anyone who knows how to compile a profile knows that a large number of tasks in it are up-to-date. So what does up-to-date mean? Gradle tasks cache the results of each run, and the next run checks whether the input and output of a Task have changed. If there is no change it is up-to-date, skip compilation.

2.Project starts with the Project object.Project is the main interface for interacting with Gradle. The build.gradle file has a one-to-one relationship with Project. The build.gradle file is the delegate of the Project object, and the configuration in the script is corresponding to the Project Api. Gradle build process starts with the Project class instantiated according to build. Gradle. That is, at build time, each build.gradle file generates a Project object that is responsible for building the current Module.

A Project is essentially a container containing multiple tasks, all of which are stored in a TaskContainer. We can see that from the name

You can see dependencies, Configuration, AllProjects, subprojects, beforeEvaluate, afterEvaluate these are common configuration items, Accept a Closure in the build.gradle file.

Ok, so now we’ve talked about build.gradle, but as you all know, we have a settings.gradle in our project, what is this for? This will speak about the Lifecycle of a Project, the steps Gradle takes to build a Project.

  • Create a Settings instance for the build.
  • Evaluate the settings.gradle script, if present, against the Settings object to configure it.
  • Use the configured Settings object to create the hierarchy of Project instances.
  • Finally, evaluate each Project by executing its build.gradle file, if present, In order to show off the most recent voltage-wise order, such that a project is evaluated before its child projects. This order can be overridden by callingProject.evaluationDependsOnChildren() or by adding an explicit evaluation dependency using Project.evaluationDependsOn(java.lang.String).

That is, the Project object depends on the construction of the Settings object. This is why we often configure the module to be imported in settings.gradle files.

Gradle properties and settings.gradle properties. This file holds key-value pair properties that can be accessed by gradle scripts in your project using ext.xxx.

We can also use the Properties class to create Properties files on the fly. Such as:

def defaultProps = new Properties()
defaultProps.setProperty("debuggable".'true')
defaultProps.setProperty("groupId", GROUP)
Copy the code

And properties can be inherited, and properties defined in a project can be automatically inherited by the quilt project. So any subproject can be accessed using project.ext.xxx. Common configuration plug-ins are used for configuration between different subprojects

apply from: rootProject.file('library.gradle')
Copy the code

conclusion

Gradle configuration, gradle configuration, Gradle configuration, Gradle configuration For reasons of space, we will put the in-depth content in the next article. Stay tuned for an in-depth article on Gradle

I am Android stupid bird journey, a accompany you slowly become stronger of the public number.

Android Gradle: Groovy closures