Gradle学习

Android默认使用Gradle作为构建工具。

Why Gradle

Android Studio Project Site的描述如下

Gradle学习_第1张图片
gradle_why.png

Gradle的出现满足了很多现在构建工具的需求,Gradle提供了一个DSL(领域特定语言),一个约定优于配置的方法,还有更强大的依赖管理,Gradle使得我们可以抛弃XML的繁琐配置,引入动态语言Groovy来定义你的构建逻辑。

Groovy

由于Gradle基于Groovy,要理解Gradle,必须先了解Groovy。
Groovy 被很多人认为是一种脚本语言,其实不准确,官网表述:

groovy_0.png

Groovy是一种基于JVM(Java虚拟机)的动态语言,它结合了Python、Ruby和Smalltalk的许多强大的特性,Groovy 代码能够与 Java 代码很好地结合,也能用于扩展现有代码。由于其运行在 JVM 上的特性,Groovy 可以使用其他 Java 语言编写的库。

基础:
  • 注释和java一样
  • 语句可以不以分号结尾
  • 定义变量是可以不指定类型,定义变量使用def关键字(不强制,但推荐,def会改变作用域)

按套路,现在该上“HelloWorld”了

java版HelloWorld:

public class Test {
    public static void main(String[] args) {
        System.out.println("Hello World");
    }
}

Groovy版HelloWorld:

println "Hello World"

Groovy版本比java版本简单很多,我们可以将groovy文件编译为class查看:

groovyc -d classes test.groovy

编译后得到test.class:


Gradle学习_第2张图片
groovy_test1.png
  • test.groovy被转为一个继承自Script的test类
  • 每一个脚本都会生成一个main函数
  • 脚本中的代码都会放到run函数中
  • 如果脚本中定义了函数,则函数会被定义在test类中

之前说def会改变变量作用域,举个栗子:

def num = 1 // 也可以 int num = 1
def printNum() {
    println num
}
printNum() 

结果是运行异常:

Caught: groovy.lang.MissingPropertyException: No such property: num for class: test
groovy.lang.MissingPropertyException: No such property: num for class: test
    at test.printNum(test.groovy:4)
    at test.run(test.groovy:7)

找不到属性?我们生成class查看:


Gradle学习_第3张图片
groovy_test2.png

结果一目了然,num是run方法中的局部变量,当然无法在printNum中访问。

我们再看不加def的class结果:


Gradle学习_第4张图片
groovy_test3.png

此时通过callGroovyObjectGetProperty来访问num变量。

也可以将num变为成员变量,这样printNum自然就可以访问了

import groovy.transform.Field; 
@Field num = 1  //在num前面加上@Field标注,num就是test的成员变量了。
Gradle学习_第5张图片
groovy_test4.png
引号:

Groovy支持单引号,双引号,三引号

  • 单引号中的内容严格对应Java的String,不对$进行转义。
  • 双引号的内容如果有$则会对$表达式先求值
  • 三引号可以指示一个多行的字符串,并可以在其中自由的使用单引号和双引号。
def name = "Candy"
println 'Hello $name'
println "Hello $name"
println "Hello ${name}"

输出:

Hello $name
Hello Candy
Hello Candy
函数:

除非指定了确定的返回类型(void也可以作为一种返回值),否则定义函数必须加上关键字def。
函数默认最后一行语句的值为函数返回值,可以显式的return返回值。

def method1() {
    return "This is method1"
}

def method2() {
    "This is method2"
}

String method3() {
    return "This is method3"
}

void method4() {
    println "hello method4"
}

println method1()
println method2()
println method3()
println method4()

输出:

This is method1
This is method2
This is method3
hello method4
null

参数可以不指定类型,参数可以有默认值:

def printPeople(name, age=18) {
    println "name is ${name}, age is ${age}"
}

printPeople("Jack", 22)
printPeople("Terry")

// 输出:
// name is Jack, age is 22
// name is Terry, age is 18

当函数有一个或多个参数时,调用的时候可以不加括号,比如:

println "Hello World"  // 等同于 println("Hello World")

def someMethod1(it) {
    println "someMethod1, it=${it}"
}

def someMethod2() {
    println "someMethod2"
}

someMethod1(1) // someMethod1, it=1
someMethod1 2 // someMethod1, it=2

someMethod2() // someMethod2
someMethod2 // 报错

另外一个语法特性是Command Chain,不仅可以省略圆括号,又可以省略”.”号。比如:a(b).c(d),可以写成:a b c d。

def name(name) {
    println "name: $name"
    return this
}
def age(age) {
    println "age: $age"
    return this
}

name "Jerry" age 18

函数调用不加括号在Gradle中有很多运用,举个栗子:

include ':app'

实际是函数调用:


Gradle学习_第6张图片
gradle_include.png
闭包

Groovy闭包文档:http://www.groovy-lang.org/closures.html

闭包语句:

def clos = {param -> println "${param}"}
clos.call("Hello") // 等同于 clos("Hello"),输出 Hello

如果闭包没定义参数的话,则隐含有一个参数it:

def clos = {println "${it}"}
clos("Hello") // 输出 Hello

当然也可以指定闭包没有参数:

def clos = {->println "${it}"}
clos("Hello") // 报错

当Closure作为函数最后一个参数时,可以将Closure拿到括号外边,比如:

def doSomething(arg, Closure clos){
   print "${arg} "
   clos()
}

doSomething ("Hello", {println 'Candy'})
doSomething "Hello", {println 'Bob'}
doSomething ("Hello") {println 'Tom'}


// 输出:
// Hello Candy
// Hello Bob
// Hello Tom

闭包在gradle中有大量的运用,比如:

repositories {
    jcenter()
}

这在gradle脚本中很常见的写法,其实repositories是一个函数:

/**
 * Configures the repositories for the script dependencies. Executes the given closure against the {@link
 * RepositoryHandler} for this handler. The {@link RepositoryHandler} is passed to the closure as the closure's
 * delegate.
 *
 * @param configureClosure the closure to use to configure the repositories.
 */
void repositories(Closure configureClosure);

可以看到这个函数的参数是一个闭包,那么上面的写法就等同于:

repositories({
   jcenter()
})

至此Groovy暂时告一段落,我们已经了解了一些Groovy的基础知识,下一步就是学习Gradle了。

Gradle

Gradle是一个框架,它负责定义流程和规则。而具体的编译工作则是通过插件的方式来完成的。比如编译Java有Java插件,编译Groovy有Groovy插件,编译Android APP有Android APP插件,编译Android Library有Android Library插件。

我们在AndroidStudio中创建project时,会默认生成一个多项目结构的Gradle工程:


Gradle学习_第7张图片
gradle_dirs.png

上图三个.gradle结尾的文件就是Gradle的构建脚本。

共有三种不同类型的脚本:


Gradle学习_第8张图片
gradle_script_types.png
Init script

我们大部分人并不常用,但是它确实可以配置:

  • 在命令行里指定: gradle -I 或者 --init-script
  • 在USER_HOME/.gradle/init.d目录下,放置init.gradle文件
Settings script

settings.gradle对于多项目的Project是必须的,文件在rootProject根目录下,Settings脚本的Delegate是Settings对象,可以看下Settings类都有哪些函数:


Gradle学习_第9张图片
gradle_settings_methods.png

我们常用的就是include函数了:

include ':app'
Build script

build.gradle脚本文件是我们最常编辑的,绝大部分配置工作都在这里面。每一个待编译的Project都对应一个build.gradle,它的Delegate是Project:


Gradle学习_第10张图片
gradle_project_des.png

每一个待编译Project都有一个build.gradle,每一个build.gradle文件都会转换成一个Project对象,在构建的时候包含一系列的Task。比如一个Android APK的编译包含:Java源码编译Task、资源编译Task、JNI编译Task、lint检查Task、打包生成APK的Task、签名Task等。

Task

Task 是Gradle中的一种数据类型,它代表了一些要执行的工作。不同的插件可以添加不同的Task。每一个Task都需要和一个Project关联。

创建Task:

task hello1 {
    doLast {
        println '--- Task hello1 ---'
    }
}

task hello2
hello2.doLast {
    println '--- Task hello2 ---'
}

tasks.create('hello3')
hello3.doLast {
    println '--- Task hello3 ---'
}

使用命令gradle [task]来执行task,如果task是驼峰命名时(比如task名字是helloWorld),可以使用缩写 gradle hW

task依赖:

task hello {
    doLast {
        println 'Hello World !'
    }
}

task intro() {
    doLast {
        println "I am Gradle"
    }
}
intro.dependsOn(hello)
// 或:
// task intro(dependsOn: hello) { 
//    doLast {
//        println "I am Gradle"
//    }
// }

执行gradle -q intro后输出:

Hello World !
I am Gradle

Android Plugin for Gradle

Android Plugin for Gradle 是通过Gradle插件机制实现的Android平台的Gradle构建插件,详情请参考:
Android Gradle Plugin User Guide
Android Plugin DSL Reference
Configure Your Build

build.gradle解读

defaultConfig:

defaultConfig {
    // 包名
    applicationId "com.goodl.gradledemo"
    // 最低版本
    minSdkVersion 9
    // 目标版本
    targetSdkVersion 25
    // 版本号
    versionCode 1
    // 版本名称
    versionName "1.0"
    // 自动化测试
    testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}

signConfigs:

signingConfigs {
    //debug模式签名
    debug {}
    //签名打包
    release {
        //签名文件所在路径
        storeFile file("ray.jks")
        //签名密码
        storePassword "111111"
        //别名
        keyAlias "rayhahah"
        keyPassword "111111"
    }
    //自定义签名配置
    ray{
        //和上面的属性一致,根据个人需求实现不同配置
    }
}

buildTypes:

buildTypes {
        debug {
            minifyEnabled false
            zipAlignEnabled false
            shrinkResources false
            signingConfig signingConfigs.debug
        }
        release {
            minifyEnabled true // 是否混淆
            zipAlignEnabled true // zipAlign优化
            shrinkResources false // 移除无用的resource文件
            signingConfig signingConfigs.release // 签名
            proguardFiles 'proguard-rules.pro'
            applicationVariants.all { variant ->
                variant.outputs.each { output ->
                    def outputFile = output.outputFile
                    if (outputFile != null && outputFile.name.endsWith('.apk')) {
                        // 输出apk名称为myapp_1.0.apk
                        def fileName = "myapp_${defaultConfig.versionName}.apk"
                        output.outputFile = new File(outputFile.parent, fileName)
                    }
                }
            }
            proguardFile 'proguard-rules.pro'
        }
    }

sourceSets:


sourceSets {
//这样的配置适用于将Eclipse中的项目结构迁移到AndroidStudio中
    main {
        //指定src资源目标目录
        java.srcDirs = ['src']
        //指定asset的目标目录
        assets.srcDirs = ['assets']
        //指定res的目标目录
        res.srcDirs = ['res']
        //指定依赖C文件的目标目录
        jni.srcDirs = ['jni']
         //指定依赖so文件的目标目录
        jniLibs.srcDirs = ['libs']
        //指定Manifest的目标文件路径
        manifest.srcFile 'AndroidManifest.xml'
    }
 }

productFlavors:

//多渠道打包配置
productFlavors {
    xiaomi {
        manifestPlaceholders = [UMENG_CHANNEL_VALUE: "xiaomi"]
    }
    myapp {
        manifestPlaceholders = [UMENG_CHANNEL_VALUE: "myapp"]
    }
}

lintOptions:

lintOptions {
  //启用出错停止grgradle构建
  abortOnError false
  // true--检查所有问题点,包含其他默认关闭项
  checkAllWarnings true
  // 关闭指定问题检查
  disable 'TypographyFractions','TypographyQuotes'
  // 打开指定问题检查
  enable 'RtlHardcoded','RtlCompat', 'RtlEnabled'
  // 仅检查指定问题
  check 'NewApi', 'InlinedApi'
  // true--生成HTML报告(带问题解释,源码位置,等)
  htmlReport true
  // html报告可选路径(构建器默认是lint-results.html )
  htmlOutput file("lint-report.html")
  // 忽略指定问题的规则(同关闭检查)
  ignore 'TypographyQuotes'
}

你可能感兴趣的:(Gradle学习)