破解Gradle(四) Project完全掌握

691 阅读4分钟

每一个build.gradle脚本文件被Gradle加载解析后,都是会生成一个Project对象。在脚本中的配置方法其实都对应着Project中的API。

Project API 文档

在开始前,我们先介绍一些最常用到的API,具体的可以看下文档:

API描述
getRootProject()获取根Project
getRootDir返回根目录文件夹
getBuildDir返回构建目录,所有的build生成物都放入到这个里面
setBuildDir(File path)设置构建文件夹
getParent()获取此Project的父Project
getChildProjects获取此Project的直系子Project
setProperty(String name, @Nullable Object value)给此Project设置属性
getProject()但会当前Project对象,可用于访问当前Project的属性和方法
getAllprojects返回包括当前Project,以及子Project的集合
allprojects(Closure configureClosure)返回包括当前Project,以及子Project的集合到闭包中
getSubprojects返回当前Project下的所有子Project
subprojects(Closure configureClosure)返回当前Project下的所有子Project到闭包中
Task task(String name)创建一个Task,添加到此Priject
getAllTasks(boolean recursive)如果recursive为true那么返回当前Project和子Project的全部Task,如果为false只返回当前Project的所有task
getTasksByName(String name, boolean recursive)根据名字返回Task,如果recursive为true那么返回当前Project和子Project的Task,如果为false只返回当前Project的task
beforeEvaluate(Closure closure)在Project评估之前调用
afterEvaluate(Closure closure);在项目评估之后调用
hasProperty(String propertyName)查看是否存在此属性
getProperties()获取所有属性
findProperty(String propertyName);返回属性的value
dependencies(Closure configureClosure)为Project配置依赖项
buildscript(Closure configureClosure)为Project配置build脚本
project(String path, Closure configureClosure)根据路径获取Project实例,并在闭包中配置Project
getTasks()返回此Project中所有的tasks

首先我们来看下一个项目中有多少个Project?可以来执行./gradlew projects命令。

可以看到输出的结果:

------------------------------------------------------------
Root project
------------------------------------------------------------

Root project 'GradleTestDemo'
\--- Project ':app'

project方法都是执行在配置阶段

接下来我们通过一些实际的例子,由浅如深的来体会这些API的含义。下面所有的例子都是在根目录下的build.gradle执行。

一、 Project API分解

1.1 getAllprojects

this.getProjects()

def getProjects(){
    println '------'
    println 'Root project'
    println '-----'
    getAllprojects().eachWithIndex{ Project project, int index ->
      //下标为 0,表明当前遍历的是 rootProject
        if (index == 0){
            println "Root prohrct ':${project.name}"
        } else {
            println "+---- project ':${project.name}"
        }
    }
}

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

------
Root project
-----
Root prohrct ':GradleTestDemo
+---- project ':app

1.2 getSubprojects

表示获取当前工程下所有子工程的实例

this.getSubProjects()

def getSubProjects() {
    println "<================>"
    println " Sub Project Start "
    println "<================>"
    // getSubprojects 方法返回一个包含子 project 的 Set 集合
    this.getSubprojects().each { Project project ->
        println "child Project is $project"
    }
}

1.3 getParent

getParent 表示 获取当前 project 的父类,需要注意的是,如果我们在根工程中使用它,获取的父类会为 null,因为根工程没有父类。

1.4 getRootProject

如果我们想在根工程仅仅获取当前的 project 实例该怎么办呢?直接使用 getRootProject 即可在任意 build.gradle 文件获取当前根工程的 project 实例。

this.getRootPro()

def getRootPro() {
    def rootProjectName = this.getRootProject().name
    println "root project is $rootProjectName"
}

1.5 project

指定app进行配置, 在父工程中完成子工程中的配置

project('app'){
    Project project ->
        apply plugin: 'com.android.application'
        group 'com.haha'
        version '1.0.0-release'
        dependencies {
        }
        android{
        }
}

配置当前节点工程和其subproject的所有工程

allprojects {
		group 'com.haha'
    version '1.0.0-release'
}

不包括当前结点工程,只包括它的子subproject

/** 
* 给所有的子工程引入 将 aar 文件上传置 Maven 服务器的配置脚本 
*/
subprojects { Project project ->
    if (project.plugins.hasPlugin('com.android.library')){
        apply from: '../publishToMaven.gradle'
    }
}

二、属性相关API分解

默认定义属性:

public interface Project extends Comparable<Project>, ExtensionAware, PluginAware {
  //默认从build.gradle中读取配置
    String DEFAULT_BUILD_FILE = "build.gradle";
  //路径分隔符,用“:”代表分隔符
    String PATH_SEPARATOR = ":";
  //代表一个输出文件夹
    String DEFAULT_BUILD_DIR_NAME = "build";
    String GRADLE_PROPERTIES = "gradle.properties";
    String SYSTEM_PROP_PREFIX = "systemProp";
    String DEFAULT_VERSION = "unspecified";
 		String DEFAULT_STATUS = "release";
  	......
}

其中定义的属性可能你会觉得太少,但gradle也支持扩展的属性。而定义扩展属性总共有两种方式:

第一种最常见的做法是在根目录下通过 ext 命名空间来定义属性:

// 在根目录下的 build.gradle 中
ext {
    compileSdkVersion = 27
    buildToolsVersion = "28.0.3"
}

//子build.gradle直接调用父类扩展属性,根project下所有的属性会再子project中被继承
compileSdkVersion this.compileSdkVersion

在项目越来越庞大的时候,仍然定义在根build.gradle下,会让根目录显得越来越复杂。这时候我们可以开启一个新的gradle脚本专门来定义扩展属性。将其命名为 common.gradle,如下所示:

//用来存放应用中的所有配置变量,统一管理
ext {
    android = [
            compileSdkVersion   : 30,
            versionCode         : 1,
            versionName         : '1.0.0',
    ]
}

在根build.gradle文件下来引入,会从当前工程下寻找common.gradle.

apply from: this.file('common.gradle')

在子build.gradle中就可以调用到我们的扩展属性了。

compileSdkVersion this.rootProject.ext.android.compileSdkVersion

第二种是在gradle.properties中定义一个变量

isLoadTest = false

然后再settiing.gradle中进行处理

if(hasProperty('isLoadTest') ? isLoadTest.toBoolean() : false){
  include ':Test'
}

三、文件属性操作分解

3.1 路径获取

//根工程文件路径
println getRootDir().absolutePath
//build文件地址
println getBuildDir().absolutePath
//当前文件根目录
println getProjectDir().absolutePath

3.2 通过file/files定位文件

//定位单个文件
println getContent("gradle.properties")
def getContent(String path){
    try {
        //相对于当前的project工程开始查找
        def file = file(path)
        return file.text
    } catch(GradleException e){
        println 'file not found'
    }
    return null
}

//文件集合,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 通过copy拷贝文件

//文件拷贝
copy {
    print 'the file'
    from file ('build/outputs/apk/')
    into getRootDir().path + '/apk66/'
    //进行文件排除
    exclude{
    }
    //重命名文件
    rename('app-debug.apk', 'haha.apk')

}

3.4 对文件数进行遍历

我们可以 使用 fileTree 将当前目录转换为文件数的形式,然后便可以获取到每一个树元素(节点)进行相应的操作

//对文件树进行遍历
//将apk下的每个文件拷贝到test目录下
fileTree('build/outputs/apk'){FileTree fileTree ->
    fileTree.visit{ FileTreeElement element ->
        print 'the file name is:' + element.file.name
        copy {
            from element.file
            into getRootProject().getBuildDir().path + '/test/'
        }
    }
}

四、 依赖相关API分解

buildscript {
    // 配置我们工程的仓库地址
    repositories {
        google()
        jcenter()
        mavenCentral()
        maven { url 'https://maven.google.com' }
        maven { url "https://plugins.gradle.org/m2/" }
        maven {
            url uri('../PAGradlePlugin/repo')
        }
    }
    
    // 配置我们工程的插件依赖
    dependencies {
        classpath 'com.android.tools.build:gradle:3.1.4'
        
        ...
    }

在app module目录下的dependendencies是来为应用程序添加第三方依赖的,这里我们需要注意下exclude和transitives使用的用法。

implementation(rootProject.ext.dependencies.glide) {
        // 排除依赖:一般用于解决资源、代码冲突相关的问题
        exclude module: 'support-v4' 
  			exclude group: 'com.androoid.support'
        // 传递依赖:A => B => C ,B 中使用到了 C 中的依赖,
        // 且 A 依赖于 B,如果打开传递依赖,则 A 能使用到 B 
        // 中所使用的 C 中的依赖,默认都是不打开,即 false
        transitive false 
}

image-20220109183453918

一般工程A是不允许直接调用工程C下,所以一般为false。

提一下provided引入只在编译中起作用,不在运行中起作用。解决利用占位编译,防止重复引用。

五、gradle执行外部命令

我们一般是 使用 Gradle 提供的 exec 来执行外部命令

//执行外部命令
task apkcopy(group: 'imooc'){
    doLast {
      //gradle执行阶段去执行
        def sourcePath = this.buildDir.path + '/outputs/apk'
        def desationPath = '/Users/zhengxiaobo/Downloads/'
        def command = "mv -f ${ sourcePath} ${desationPath}"
        //执行外部命令
        exec {
            try {
                executable 'bash'   //执行类型
                args '-c', command    //唯一会变的就是commond,在执行其他的gradle命令时,修改这里就可以了
                println 'the coommand is execute success'
            } catch(GradleException e){
                println 'the command is execute failed'
            }
        }

    }
}

参考

深入理解Android之Gradle

深度探索 Gradle 自动化构建技术(三、Gradle 核心解密)

Project文档