前言

project在gradle里起到里重要的作用,上节我们也说过可以通过./gradlew projects打印当前项目下所有的project,准确的说是有build.gradle的文件既是一个project。而有多少project取决于在setting.gradle文件中设置了多少个。一个project对应一个输出,而具体输出什么取决于build.gradle里面的内容。

Project核心API

每个工程下都有一个build.gradle文件。根目录的build.gradle可以管理子工程下面的build.gradle。下面具体看下project相关api。

getAllProjects()

我们可以通过getAllprojects打印出所有的project,可以理解成和./gradlew projects命令一样。示例带入如下:

1
2
3
4
5
6
7
getAllprojects().eachWithIndex { Project entry, int i ->
if (i == 0) {
println("RootProject-------${entry.name}")
} else {
println("SubProject-------${entry.name}")
}
}

输出结果如下:

1
2
3
4
RootProject-------APMProjetct
SubProject-------apm
SubProject-------app
SubProject-------aspectj

getSubprojects()

此方法是获取所有子工程的实例,示例代码如下:

1
2
3
getSubprojects().eachWithIndex { Project entry, int i ->
println("SubProject-------${entry.name}")
}

输出结果如下:

1
2
3
4
> Configure project :
SubProject-------apm
SubProject-------app
SubProject-------aspectj

getRootProject()

既然我们可以拿到所有的子节点,那我们依然可以单独获取到根节点,示例代码如下:

1
println("the root project name is ${getRootProject().name}")

输出结果如下:

1
2
> Configure project :
the root project name is APMProjetct

getParent()

此方法是用于在子工程获取父工程的实例,示例代码如下:

1
println("the parent project name is ${getParent().name}")

输出结果可想而知:

1
2
> Configure project :aspectj
the parent project name is APMProjetct

projects()

project是为了让我们在父工程的build.gradle文件,针对子project做一些单独的处理,比如这样:

1
2
3
project("app") {
apply plugin: 'com.android.application'
}

allprojects()

allprojects 表示用于配置当前 project 及其每一个子 project,在 allprojects 中我们一般用来配置一些通用的配置,比如最常见的全局仓库配置。

1
2
3
4
5
6
allprojects {
repositories {
google()
jcenter()
}
}

subprojects()

此方法是为了让我们对所有的子project做一些配置,比如这样:

1
2
3
4
5
subprojects {
if(project.plugins.hasPlugin("com.android.library")){
apply from "../uploadMaven.gradle"
}
}

ext扩展属性

我们可以通过ext扩展属性默认修改其他工程build.gradle的文件配置。比如我在root project中新增了如下属性:

1
2
3
4
ext {
minSdkVersion = 21
targetSdkVersion = 30
}

那么我们可以在子工程的build.gralde文件修改文件如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
android {
compileSdkVersion 30
buildToolsVersion "30.0.1"

defaultConfig {
applicationId "com.xxxxx.apmprojetct"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 1
versionName "1.0"

testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
}

另外我们还可以通过apply的形式依赖自己新增的gradle文件,比如我们可以新增一个config.gradle文件,具体内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
android {
signingConfigs {
debug {
storeFile file('xxx.jks')
storePassword 'xxx'
keyAlias 'xxxx'
keyPassword 'xxxx'
v1SigningEnabled true
v2SigningEnabled true
}
release {
storeFile file('xxx.jks')
storePassword 'xxx'
keyAlias 'xxxx'
keyPassword 'xxx'
v1SigningEnabled true
v2SigningEnabled true
}
}
}

那么我们可以在工程中这样使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
apply from: 'config.gradle'
android {
buildTypes {
debug {
signingConfig signingConfigs.debug
minifyEnabled false
shrinkResources false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
versionNameSuffix "_dev"
}
release {
signingConfig signingConfigs.release
minifyEnabled true
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}

gradle文件操作

gradle中操作本地文件可以使用的是Project.file()方法,通过指定文件的相对路径或者绝对路径来进行操作。比如我们可以新建一个文件,可以查看一个文件的绝对路径,也可以判断一个文件是否存在,示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
// 使用相对路径
File configFile = file('src/main/java/test.java')
configFile.createNewFile();

// 使用绝对路径
configFile = file(‘/Users/xxx/xxx/src/main/java/test.java’)
println(configFile.absolutePath)

// 使用一个文件对象
configFile = file(new File('src/main/java/test.java'))
// 打印文件是否存在
println(configFile.exists())

同步一下,输出结果如下:

1
2
/Users/xxx/xxx/src/main/java/test.java
true

文件集合

Gradle中文件集合就是类似于java中的数组,Gradle中使用FileCollection接口表示,我们可以使用Project.files()方法来获得一个文件集合对象,所有文件集合都是用到的时候才会创建。示例代码如下:

1
2
3
FileCollection collection = files('src/test1.txt',
new File('src/test2.txt'),
['src/test3.txt', 'src/test4.txt'])

我们可以这样遍历文件:

1
2
3
4
// 遍历所有集合
collection.each { File file ->
println file.name
}

我们还可以这样转换文件:

1
2
3
4
5
6
7
8
9
10
// 把文件集合转换为Set类型
Set set1 = collection.files
Set set2 = collection as Set
// 把文件集合转换为List类型
List list = collection as List
// 把文件集合转换为String类型
String path = collection.asPath
// 把文件集合转换为File类型
File file1 = collection.singleFile
File file2 = collection as File

除此之外,我们还可以新增或者删除一个文件,比如这样:

1
2
3
// 添加或者删除一个集合
def union = collection + files('src/test5.txt')
def different = collection - files('src/test3.txt')

文件树

文件树你可以理解成是一个有层级结构的文件集合,所以文件树中包含了所有文件集合的操作。我们可以使用Project.fileTree()方法来创建文件树对象,还可以使用过虑条件来包含或排除相关文件。示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 指定目录创建文件树对象
FileTree tree = fileTree(dir: 'src/main')

// 给文件树对象添加包含指定文件
tree.include '**/*.java'
// 给文件树对象添加排除指定文件
tree.exclude '**/Abstract*'

// 使用路径创建文件树对象,同时指定包含的文件
tree = fileTree('src').include('**/*.java')

// 通过闭包创建文件树
tree = fileTree('src') {
include '**/*.java'
}

// 通过map创建文件树
tree = fileTree(dir: 'src', include: '**/*.java')
tree = fileTree(dir: 'src', includes: ['**/*.java', '**/*.xml'])
tree = fileTree(dir: 'src', include: '**/*.java', exclude: '**/*test*/**')

既然说到了文件树的创建,当然,我们也可以对文件树进行一些业务操作,比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 遍历文件树的所有文件
tree.each {File file ->
println file
}

// 过虑生成新的文件树对象
FileTree filtered = tree.matching {
include 'org/gradle/api/**'
}

// 使用“+”号合并两个文件树,同文件集合的“+”操作一样
FileTree sum = tree + fileTree(dir: 'src/test')

// 访问文件树中各项内容
tree.visit {element ->
println "$element.relativePath => $element.file"
}

文件拷贝

我们可以通过copy的task对文件进行copy动作,并且可以指定copy内容和过滤copy内容,还可以在copy的过程中对文件进行重命名操作,先来个简单的,比如我需要从A目录copy到B目录,那我们可以这样做:

1
2
3
4
task copyTask(type: Copy) {
from 'src/main/java/fileA'
into 'src/main/kotlin/fileB'
}

当然,我们也是支持批量copy的,比如这样:

1
2
3
4
5
6
7
8
9
10
task copyTask(type: Copy) {
// 拷贝src/main/webapp目录下所有的文件
from 'src/main/java'
// 拷贝单独的一个文件
from 'src/main/res/index.html'
// 从Zip压缩文件中拷贝内容
from zipTree('src/main/assets.zip')
// 拷贝到的目标目录
into 'src/main/mouble'
}

前面我们也说了,我们可以在copy的时候进行重命名,最常见的场景,就是项目拆解模块的时候,我们需要重新命名,那我们可以这样操作:

1
2
3
4
5
6
7
8
task rename(type: Copy) {
from 'moudleA/src/main/res/'
into 'moudleB/src/main/res/'
// 使用一个闭包方式重命名文件
rename { String fileName ->
fileName.replace('moudleA', 'moudleB')
}
}

大量的节约了手动重命名的成本。
同样我们也可以新增过滤条件:

1
2
3
4
5
6
7
task copyTaskWithPatterns(type: Copy) {
from 'src/main/drawable'
into 'src/main/drawable-xxhdpi'
include '*.jpg'
exclude { details -> details.file.name.endsWith('.png') &&
details.file.text.contains('.webp') }
}

当然我们还可以拷贝的同时,并删除对应目录文件,我们可以这么操作:

1
2
3
4
5
task libs(type: Sync) {
from configurations.runtime
// 拷贝之前会把$buildDir/libs目录下所有的清除
into "$buildDir/libs"
}

总结

本文我们简单的介绍了projects的相关操作以及在gradle中我们如何去操作文件,当然,gradle中除了projects,还有一个核心的task,这个我们放在下一节进行介绍。

参考

暴力突破 Gradle 自动化项目构建(五)- Gradle 核心之 Project
gradle操作文件详解