Gradle学习

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

Why Gradle

Android Studio Project Site的描述如下

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:


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查看:


groovy_test2.png

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

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


groovy_test3.png

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

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

import groovy.transform.Field; 
@Field num = 1  //在num前面加上@Field标注,num就是test的成员变量了。
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_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_dirs.png

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

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


gradle_script_types.png
Init script

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

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

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


gradle_settings_methods.png

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

include ':app'
Build script

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


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"
    // 最低版本
    minSdk 9
    // 目标版本
    targetSdk 25
    // 版本号
    versionCode 1
    // 版本名称
    versionName "1.0"
    // 自动化测试
    testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    
    ndk {
        abiFilters 'arm64-v8a', 'armeabi-v7a'
    }
    resConfigs "en", "zh"
}

signConfigs:

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

buildTypes:

buildTypes {
    debug {
        minifyEnabled false
        signingConfig signingConfigs.debug
        buildConfigField 'boolean', 'ENV_PRODUCTION', "false"
    }

    uat {
        initWith debug // 从给定的构建类型复制所有属性
        buildConfigField 'boolean', 'ENV_PRODUCTION', "false"
    }
    
    release {
        minifyEnabled true // 是否混淆
        zipAlignEnabled true // zipAlign优化
        shrinkResources true // 移除无用的resource文件
        signingConfig signingConfigs.release // 签名
        proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        buildConfigField 'boolean', 'ENV_PRODUCTION', "true"
    }
}

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 = [CHANNEL_VALUE: "xiaomi"]
    }
    myapp {
        manifestPlaceholders = [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'
}

重命名 apk 名称:

static def releaseTime() {
    return new Date().format("yyyyMMdd", TimeZone.getTimeZone("UTC"))
}

android {
  defaultConfig {
    // ...
  }

  // ...

  applicationVariants.all { variant ->
    variant.outputs.all {
        outputFileName = "myapp_v${defaultConfig.versionName}_${releaseTime()}_${variant.name}.apk"
    }
  }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335

推荐阅读更多精彩内容