title | date | categories | tags | description | ||
---|---|---|---|---|---|---|
Project |
2021-01-23 |
|
|
|
- Settings文件
- Build文件
- buildscript 和 allprojects
- 自定义属性
- Task 与 Project
- 访问任务的方式
- Project类图
- getter/setter属性
- 文件操作
- 多项目构建
在Gradle 中,定义了一个设置文件,默认名:settings.gradle,放在根目录下
settings.gradle的主要作用是为了配置子工程。
在Gradle 中, 多工程是通过工程树来表示。就相当于我们在Android 中的 Project 和 Module 一样。根工程(根目录,rootProject)就相当于Android 中的Project。然后,一个根工程有多个子工程,子工程就相当于Android中的Module一样。
一个子工程只有在settings.gradle 文件中配置过,才能被找到,才能在构建的时候被包含进去。
我们可以看一下,在我们Android 工程中的settings.gradle长什么样子:
可以看到,最外层的My Application 就是 根工程rootProject。在根工程中有两个子工程,分别是app 和 common。 在不明确指定子工程路径的情况下,默认目录就是同级目录。可以看到,我的 app 目录和 common目录都是同 setting.gradle文件同一目录的,所以可以不指定目录了。
如果想要指定目录:
project(':app').projectDir = new File()
project(':app') 是使用project方法,查找名字为app的子工程。 返回值就是 app 子工程对象,然后修改这个子工程的 projectDir 属性值。这样就可以了。
TIP: 好像冒号 : 就表示根工程 冒号+xx 就表示子工程
每个 Project 都有一个 build.gradle 文件。
gradle 构建的时候,每一个 build.gradle 脚本文件被 Gradle 加载解析后,都会对应生成一个 Project 对象。所以在 build.gradle 里写的 DSL,其实都是 Project 接口的一些方法,Project 其实是一个接口,真正的实现类是 DefaultProject。后面看Project的类图
RootProject可以获取到所有的ChildProject,因此可以在RootProject中对ChildProject进行统一配置和管理。
subprojects {
repositories {
jcenter()
}
}
配置项目中所有的ChildProject的依赖的仓库为jcenter
allprojects {
repositories {
jcenter()
}
}
配置项目中所有的Project的依赖的仓库为jcenter,包括RootProject和ChildProject
参考:https://blog.csdn.net/u013700502/article/details/85231687
subprojects allprojects 其实是两个方法,接受闭包作为参数。这个方法会对工程进行遍历,遍历的过程中会调用我们自定义的闭包,所以,我们可以在闭包里配置、打印或者修改Project属性都可以。
这里我们要明确这两个配置。
- buildscript 配置的 是gradle脚本自身需要使用的资源。
- build.gradle 文件中直接声明的依赖项、仓库地址等信息是项目自身需要的资源
- gradle在执行脚本时,会优先执行buildscript代码块中的内容,然后才会执行剩余的build脚本。
buildscript {
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.5.2'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
看下我的 buildscript。dependencies
指的是 gradle脚本运行,需要依赖什么插件。repositories
说的是这个插件去哪里下载。然后在使用的时候,apply plugin 来引用就可以了。
在 allprojects 中的dependencies
以及 我们app的 build.gradle 中的dependencies
说的是项目的依赖项。
比如我代码想要使用一下appcompat 这个库。那就在app下的build.gradle 中的dependencies添加,代表我们需要依赖 appcompat 这个库。
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'com.google.android.material:material:1.0.0'
}
然后,这个库去哪里下载呢?就在 repositories
里配置。可以每个ChildProject单独配,也可以直接写在RootProject的repositories
中,这样就是告诉gradle,项目需要的依赖,比如 appcompat 库,还有其他乱七八糟的库,都去jcenter下载去吧。
allprojects {
repositories {
jcenter()
}
}
Project和 Task都允许用户添加额外的自定义属性。
看完这个大概就明白了
// 是在project下的
// 定义一个Project的属性
ext.age = 330
// 通过代码块,定义多个Project属性
ext{
android = "androidandroidandroid"
}
project.tasks.create("printVersion"){
doLast {
// 访问Project的age属性
println(project.age)
// 访问Project的android属性
println(project.android)
println(project.ext.android)
println(rootProject.android)
println(rootProject.ext.android)
// 错误,这样写是访问Task的ext。但是我们这个printVersion Task没有android这个属性
//println(ext.android)
// 也可以不带 project 直接访问。默认就是project.
println(android)
// 这样写,说的是 task的属性,即:Task 有个 eee的属性, 注意:不是project的属性
ext.eee = "22"
println("${ext.eee}")
//这样访问,肯定没有这个
//println("${project.ext.eee}")
println("作为task的属性: ${printVersion.eee} ")
// 全局project.ext
project.ext.fff = "fffff"
project.fff = "1111";// 这样写也可以
println("${rootProject.ext.fff}") // 有
println("${rootProject.fff}") // 有
//println("${ext.fff}") // 没
}
}
RootProject 就相当于一个我们Android 中的Application。 一个Application里有好多Module。Module就相当于ChildProject。 每个Project都可以包含多个Task。 Task就是一个任务,一个操作。
我们创建的Task,都会作为Project的一个属性来管理。属性名就是Task的名称。我们可以来验证一下。
task hello {
doLast {
println 'Hello world!'
println project.hasProperty("hello") // true
}
}
- 任务名. 前面说了, 我们创建的Task,都会作为Project的一个属性来管理。属性名就是Task的名称。 因此, 我们可以使用project.属性名 来访问任务, project 可以省略.
- TaskContianer 是用来管理所有的 Task 实例集合的,可以通过 Project.getTasks() 来获取 TaskContainer 实例。然后,我们可以通过tasks来查找任务
tasks['hello'].doLast {
print "hello"
}
访问的时候, 任务名就是key。
其实,[]
在Groovy中是一个操作符。 a[b]
对应的 是 a.getAt(b)
这个方法。对应的,我们tasks['hello']
其实就是调用tasks.get('hello')
这个方法,查看Gradle源码,可以看到,这个方法是通过tasks.findByName(String name)
来实现的。
- 通过路径访问
tasks.findByPath('项目名称:任务名称')
tasks.getByPath()
get
方法找不到会报错。find
找不到返回null
- 通过名称访问
tasks.findByName()
task.getByName()
采用路径的方式,可以传路径,也可以只传入名字。但是通过名字访问,参数只能是名字,不能是路径。
当构建进程启动后,Gradle基于build.gradle中的配置实例化org.gradle.api.Project类,先来看看 Project 类的主要结构(节选部分常用):
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()}")
File mkDir = mkdir("${buildDir}/test");
File mkDir2 = mkdir("${buildDir}/test2")
println "检测目录是否创建成功:${mkDir.exists()}, ${mkDir2.exists()}"
//定位单个文件,参数可以是相对路径、绝对路径
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()}个文件"
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
}
复制文件需要使用复制任务(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'
}
//删除 build 目录下所有文件
delete("${buildDir}")
前面我们介绍的例子,都是单独执行某一个 build.gradle 文件。但是我们在 Android 应用开发中,一个 Project 可以包含若干个 module ,这种就叫做多项目构建。在 Android Studio 项目中,根目录都有一个名叫 settings.gradle 的文件,然后每个 module 的根目录中又有一个 build.gradle 文件,Gradle 就是通过 settings.gradle 来进行多项目构建的。
- 先创建如下几个目录及文件:
在项目根目录创建一个 settings.gradle,在根目录、app以及library目录下也都创建一个 build.gradle 文件。
- 在 settings.gradle 里引入子项目
include ":app", ":library"
- 在 build.gradle 里增加测试代码
//在根目录 build.gradle 里增加
println "-----root file config-----"
//在 app/build.gradle 里增加
println "-----app config-----"
//在 library/build.gradle 里增加
println "-----library config-----"
- 在项目根目录执行命令 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。
在根项目里可以对子项目进行配置:
//通过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
buildscript
配置该 Project 的构建脚本的 classpath,在 Andorid Studio 中的 root project 中可以看到:
buildscript {
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.0.1'
}
}
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'
- Gradle属性
在与 build.gradle 文件同级目录下,定义一个名为 gradle.properties 文件,里面定义的键值对,可以在 Project 中直接访问。
//gradle.properties里定义属性值
company="hangzhouheima"
username="hjy"
在 build.gradle 文件里可以这样直接访问:
println "company = ${company}"
println "username = ${username}"
- 扩展属性 还可以通过 ext 命名空间来定义属性,我们称之为扩展属性。
ext {
username = "hjy"
age = 30
}
println username
println ext.age
println project.username
println project.ext.age
必须注意,默认的扩展属性,只能定义在 ext 命名空间下面。对扩展属性的访问方式,以上几种都支持。