Android Gradle学习(四):Project详解

每一个 build.gradle 脚本文件被 Gradle 加载解析后,都会对应生成一个 Project 对象,在脚本中的配置方法其实都对应着 Project 中的API,如果想详细了解这些脚本的配置含义,有必要对 Project 类做些深入的剖析。

1. Project类图

当构建进程启动后,Gradle基于build.gradle中的配置实例化org.gradle.api.Project类,先来看看 Project 类的主要结构(节选部分常用):

Project 类图

接下来我们通过一些实际的例子,由浅入深的来体会这些 API 的含义。

2. getter/setter属性

File bd = getBuildDir()
println "buildDir = ${bd.getAbsolutePath()}"

//获取Project的名字
String name = getName()
println "project name = $name"

//设置Project的描述信息
setDescription "这是一个测试案例"
String desc = getDescription()
println "project description = $desc"

//获取Project的路径
String path = getPath();
println "project path = $path"

class VersionInfo {
    String version
    boolean release

    VersionInfo(String v, boolean release) {
        version = v
        this.release = release
    }

    String toString() {
        return "V-${version}-${release ? 'release' : 'debug'}"
    }
}
//设置Project的版本号,参数可以是任何对象,gradle内部会使用 toString() 方法返回的值
setVersion(new VersionInfo("1.0.0", true))
println("project version = ${getVersion()}")

//设置Project的分组
setGroup "TestGroup"
println("project group = ${getGroup()}")

直接执行 gradle 命令,可以看到在配置阶段输出以下结果:

> Configure project :
rootDir = /Users/hjy/Desktop/gradle
buildDir = /Users/hjy/Desktop/testgradle/build
project name = gradle
project description = 这是一个测试案例
project path = :
project version = V-1.0.0-release
project group = TestGroup

2. 创建task

我们在 build.gradle 文件中定义 task 的方式,其实都对应的是 Project 类中的 task() 方法,从 Project 类图中的 task API 可以看到有多种不同的形式,前面介绍 task 的章节已经介绍过了,这里就不赘述了。

3. 文件操作

3.1 通过mkdir创建目录
File mkDir = mkdir("${buildDir}/test");
File mkDir2 = mkdir("${buildDir}/test2")
println "检测目录是否创建成功:${mkDir.exists()}, ${mkDir2.exists()}"
3.2 通过file、files 定位文件
//定位单个文件,参数可以是相对路径、绝对路径
File testDir = file("${buildDir}/test")
println "文件定位是否成功:${testDir.exists()}"

//文件集合,Gradle里用 FileCollection 来表示
FileCollection fileCollection = files("${buildDir}/test", "${buildDir}/test2")
println "-------对文件集合进行迭代--------"
fileCollection.each {File f ->
    println f.name
}
println "-------文件迭代结束-------"
//获取文件列表
Set<File> set = fileCollection.getFiles()
println "文件集合里共有${set.size()}个文件"
3.3 通过fileTree创建文件树

Gradle里用 ConfigurableFileTree 来表示文件树,文件树会返回某个目录及其子目录下所有的文件,不包含目录。

//先在build目录下创建3个txt文件
file("${buildDir}/t1.txt").createNewFile()
file("${buildDir}/test/t2.txt").createNewFile()
file("${buildDir}/t1.java").createNewFile()

//1.通过一个基准目录创建文件树,参数可以是相对目录,也可以是绝对目录,与file()方法一样
println "通过基准目录来创建文件树"
ConfigurableFileTree fileTree1 = fileTree("build")
//添加包含规则
fileTree1.include "*.txt", "*/*.txt"
//添加排除规则
fileTree1.exclude "*.java"
fileTree1.each { f ->
    println f    
}

//2.通过闭包来创建文件树
println "通过闭包来创建文件树"
ConfigurableFileTree fileTree2 = fileTree("build") {
    include "*/*.txt", "*.java"
    exclude "*.txt"
}
fileTree2.each { f ->
    println f    
}

//3.通过map配置来创建文件树,可配置的选项有:dir: ''、include: '[]、exclude: []、includes: []、excludes: []
println "通过Map来创建文件树"
def fileTree3 = fileTree(dir: "build", includes: ["*/*.txt", "*.java"])
fileTree3 = fileTree(dir: "build", exclude: "*.java")
fileTree3.each { f ->
    println f    
}
3.4 复制文件

复制文件需要使用复制任务(Copy)来进行,它需要指定要复制的源文件和一个目标目录,复制的规则都是定义在 CopySpec 接口里的,更详细的说明可参见 API 文档。

task testCopyFile(type: Copy) {
    //复制build目录下的所有文件
    from "build"
    //复制单独的某个文件
    from "test.java"
    //复制某个文件树下的所有文件
    from fileTree("build")

    include "*.txt"
    include "*.java"
    exclude "t1.txt"
    //指定目标目录
    into "outputs"

    //对复制的文件重命名:通过闭包来映射
    rename { fileName ->
        //增加 rename_ 前缀
        return fileName.endsWith(".java") ? "rename_" + fileName : fileName
    }

    //通过正则来映射文件名:abctest.java 会映射成 abchjy.java
    rename '(.*)test(.*)', '$1hjy$2'
}

3.5 删除文件
//删除 build 目录下所有文件
delete("${buildDir}")

4. 多项目构建

前面我们介绍的例子,都是单独执行某一个 build.gradle 文件。但是我们在 Android 应用开发中,一个 Project 可以包含若干个 module ,这种就叫做多项目构建。在 Android Studio 项目中,根目录都有一个名叫 settings.gradle 的文件,然后每个 module 的根目录中又有一个 build.gradle 文件,Gradle 就是通过 settings.gradle 来进行多项目构建的。

4.1 通过 settings.gradle 引入子项目

1.先创建如下几个目录及文件:

多项目构建

如图所示,在项目根目录创建一个 settings.gradle,在根目录、app以及library目录下也都创建一个 build.gradle 文件。

2.在 settings.gradle 里引入子项目

include ":app", ":library"

3.在 build.gradle 里增加测试代码

//在根目录 build.gradle 里增加
println "-----root file config-----"

//在 app/build.gradle 里增加
println "-----app config-----"

//在 library/build.gradle 里增加
println "-----library config-----"

4.在项目根目录执行命令 gradle -q,结果如下:

-----root file config-----
-----app config-----
-----library config-----

这是一个多项目构建的简单例子,可以看到结构与我们的 Android 项目是类似的。Gradle 在运行时会读取并解析 settings.gradle 文件,生成一个 Settings对象,然后从中读取并解析子项目的 build.gradle 文件,然后为每个 build.gradle 文件生成一个 Project 对象,进而组装一个多项目构建出来。

Settings 里最核心的API就是 include 方法,通过该方法引入需要构建的子项目。

include(projectPaths: String...)

这里我们为每个 build.gradle 文件生成了一个 Project 对象,跟总共3个 Project,根目录的 Project 我们称之为 root project,子目录的 Project 我们称之为 child project。

4.2 项目配置

在根项目里可以对子项目进行配置:

//通过path定位并获取该 Project 对象
project(path: String): Project
//通过path定位一个Project,并进行配置
project(path: String, config: Closure): Project

//针对所有项目进行配置
allprojects(config: Closure)
//针对所有子项目进行配置
subprojects(config: Closure)

我们修改根目录 build.gradle 文件如下:

println "-----root file config-----"

//配置 app 项目
project(":app") {
    ext {
        appParam = "test app"
    }
}

//配置所有的项目
allprojects {
    ext {
        allParam = "test all project"
    }   
}

//配置子项目
subprojects {
    ext {
        subParam = "test sub project"
    }
}

println "allParam = ${allParam}"

修改 app/build.gradle 文件如下:

println "-----app config-----"
println "appParam = ${appParam}"
println "allParam = ${allParam}"
println "subParam = ${subParam}"

修改 library/build.gradle 文件如下:

println "-----library config-----"
println "allParam = ${allParam}"
println "subParam = ${subParam}"

运行结果如下:

-----root file config-----
allParam = test all project
-----app config-----
appParam = test app
allParam = test all project
subParam = test sub project
-----library config-----
allParam = test all project
subParam = test sub project

5. 构建脚本配置

5.1 buildscript

配置该 Project 的构建脚本的 classpath,在 Andorid Studio 中的 root project 中可以看到:

buildscript {
    repositories {
        google()
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.0.1'
    }
}
5.2 apply
apply(options: Map<String, ?>)

我们通过该方法使用插件或者是其他脚本,options里主要选项有:

  • from: 使用其他脚本,值可以为 Project.uri(Object) 支持的路径
  • plugin:使用其他插件,值可以为插件id或者是插件的具体实现类

例如:

//使用插件,com.android.application 就是插件id
apply plugin: 'com.android.application'
//使用插件,MyPluginImpl 就是一个Plugin接口的实现类
apply plugin: MyPluginImpl

//引用其他gradle脚本,push.gradle就是另外一个gradle脚本文件
apply from: './push.gradle'

6. 属性

6.1 Gradle属性

在与 build.gradle 文件同级目录下,定义一个名为 gradle.properties 文件,里面定义的键值对,可以在 Project 中直接访问。

//gradle.properties里定义属性值
company="hangzhouheima"
username="hjy"

在 build.gradle 文件里可以这样直接访问:

println "company = ${company}"
println "username = ${username}"
6.2 扩展属性

还可以通过 ext 命名空间来定义属性,我们称之为扩展属性。

ext {
  username = "hjy"
  age = 30
}

println username
println ext.age
println project.username
println project.ext.age

必须注意,默认的扩展属性,只能定义在 ext 命名空间下面。对扩展属性的访问方式,以上几种都支持。

系列文章

Android Gradle学习(一):Gradle基础入门
Android Gradle学习(二):如何创建Task
Android Gradle学习(三):Task进阶学习
Android Gradle学习(四):Project详解
Android Gradle学习(五):Extension详解
Android Gradle学习(六):NamedDomainObjectContainer详解
Android Gradle学习(七):Gradle构建生命周期
Android Gradle学习(八):统计Task执行时长
Android Gradle学习(九):一些有用的小技巧

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