深入理解Gradle项目构建,你需要知道这些

一、什么是Gradle

Gradle是一个基于Apache Ant和Apache Maven概念的项目自动化建构工具。它使用一种基于Groovy的特定领域语言来声明项目设置,而不是传统的XML。当前其支持的语言限于Java、Groovy和Scala,计划未来将支持更多的语言。

二、Gradle的生命周期

当我们执行编译比如 ./gradlew clean 或者 ./gradlew build可以清楚的看到控制台会有一个短暂的 LoadingConfiguring 以及Executing 三个执行阶段。这就是gradle的三个生命周期阶段。

Created with Raphaël 2.1.2 Start Initialization初始化阶段 Configuration配置阶段 Execution执行阶段 End

初始化阶段:它就是我们执行编译命令式的loading阶段,这个阶段执行时间较快,其实它执行的的setting.gradle。它主要解析的式工程中所有的Project所对应的project对象。
配置阶段:它解析的是所有project对象中的task,构建task拓扑图
执行阶段:执行的是具体的task,我们前面说到的cleanbuild其实对应的就是一个task。它的执行时间一般都比较长,原因是它要执行完task对应的所有依赖才可以执行自己。

1、如何监听生命周期的执行呢?

在根工程的build.gradle中去编写如下代码

this.beforeEvaluate {
    println '配置阶段开始啦。。。。'
}

this.afterEvaluate {
    println '配置阶段结束啦。。。。'
}

this.gradle.buildFinished {
    println 'gradle 执行完毕啦。。。'
}
//另外我们在setting.gradle中加入一个打印输出
println '初始化阶段开始啦。。。。'
include ':app', ':core', ':medialib', ':modulea'

这里写图片描述这里写图片描述
从图中可以看到,对应的配置和执行两个阶段,和对应的log输出,beforeEvaluate并没有打印,这个原因还未找到。
下面是一个clean命令完整的执行log

Executing tasks: [clean, :app:assembleDebug, :core:assembleDebug, :medialib:assembleDebug, :modulea:assembleDebug]

初始化阶段开始啦。。。。
Parallel execution with configuration on demand is an incubating feature.
配置阶段结束啦。。。。
:clean
:app:clean
:medialib:clean
:modulea:clean
:core:clean
:medialib:preBuild UP-TO-DATE
:medialib:preDebugBuild UP-TO-DATE
:medialib:checkDebugManifest
:medialib:processDebugManifest
:medialib:compileDebugAidl
:modulea:preBuild UP-TO-DATE
:modulea:preDebugBuild UP-TO-DATE
:modulea:checkDebugManifest
:modulea:processDebugManifest
:modulea:packageDebugRenderscript NO-SOURCE
:modulea:generateDebugResValues
:modulea:platformAttrExtractor
:core:preBuild UP-TO-DATE
:core:preDebugBuild UP-TO-DATE
:core:checkDebugManifest
:core:processDebugManifest
:modulea:generateDebugBuildConfig
:modulea:prepareLintJar UP-TO-DATE
:medialib:compileDebugRenderscript
:modulea:processDebugJavaRes NO-SOURCE
:modulea:mergeDebugShaders
:modulea:compileDebugShaders
:medialib:generateDebugResValues
:medialib:generateDebugResources
:medialib:packageDebugResources
:modulea:generateDebugAssets
:modulea:mergeDebugAssets
:core:compileDebugAidl
:modulea:compileDebugNdk NO-SOURCE
:modulea:mergeDebugJniLibFolders
:modulea:transformNativeLibsWithMergeJniLibsForDebug
:medialib:packageDebugRenderscript NO-SOURCE
:medialib:platformAttrExtractor
:core:compileDebugRenderscript
:core:generateDebugResValues
:core:generateDebugResources
:core:packageDebugResources
:medialib:processDebugResources
:core:packageDebugRenderscript NO-SOURCE
:core:platformAttrExtractor
:core:processDebugResources
:app:preBuild UP-TO-DATE
:app:preDebugBuild
:app:compileDebugRenderscript
:app:checkDebugManifest
:app:generateDebugBuildConfig
:app:prepareLintJar UP-TO-DATE
:modulea:compileDebugAidl
:app:generateDebugResValues
:app:generateDebugResources
:app:createDebugCompatibleScreenManifests
:modulea:compileDebugRenderscript
:app:compileDebugAidl
:modulea:generateDebugResources
:modulea:packageDebugResources
:app:processDebugManifest
:modulea:transformNativeLibsWithIntermediateJniLibsForDebug
:modulea:mergeDebugConsumerProguardFiles
:modulea:transformNativeLibsWithSyncJniLibsForDebug
:medialib:generateDebugBuildConfig
:medialib:prepareLintJar
:medialib:generateDebugSources
:modulea:processDebugResources
:core:generateDebugBuildConfig
:medialib:javaPreCompileDebug
:core:prepareLintJar
:core:generateDebugSources
:core:javaPreCompileDebug
:core:compileDebugJavaWithJavac
:medialib:compileDebugJavaWithJavac
:app:mergeDebugResources
:modulea:generateDebugSources
:core:processDebugJavaRes NO-SOURCE
:core:transformClassesAndResourcesWithPrepareIntermediateJarsForDebug
:core:mergeDebugShaders
:core:compileDebugShaders
:core:generateDebugAssets
:core:mergeDebugAssets
:core:compileDebugNdk
:medialib:processDebugJavaRes NO-SOURCE
:core:compileDebugNdk NO-SOURCE
:medialib:transformClassesAndResourcesWithPrepareIntermediateJarsForDebug
:core:mergeDebugJniLibFolders
:modulea:javaPreCompileDebug
:medialib:mergeDebugShaders
:medialib:compileDebugShaders
:medialib:generateDebugAssets
:medialib:mergeDebugAssets
:modulea:compileDebugJavaWithJavac
:medialib:compileDebugNdk NO-SOURCE
:medialib:mergeDebugJniLibFolders
:medialib:transformNativeLibsWithMergeJniLibsForDebug
:core:transformNativeLibsWithMergeJniLibsForDebug
:medialib:transformNativeLibsWithIntermediateJniLibsForDebug
:core:transformNativeLibsWithIntermediateJniLibsForDebug
:medialib:extractDebugAnnotations
:core:extractDebugAnnotations
:modulea:transformClassesAndResourcesWithPrepareIntermediateJarsForDebug
:modulea:extractDebugAnnotations
:app:splitsDiscoveryTaskDebug
:app:processDebugResources
:modulea:transformResourcesWithMergeJavaResForDebug
:medialib:mergeDebugConsumerProguardFiles
:medialib:transformResourcesWithMergeJavaResForDebug
:core:mergeDebugConsumerProguardFiles
:core:transformResourcesWithMergeJavaResForDebug
:core:transformClassesAndResourcesWithSyncLibJarsForDebug
:medialib:transformClassesAndResourcesWithSyncLibJarsForDebug
:modulea:transformClassesAndResourcesWithSyncLibJarsForDebug
:core:transformNativeLibsWithSyncJniLibsForDebug
:core:bundleDebug
:medialib:transformNativeLibsWithSyncJniLibsForDebug
:modulea:bundleDebug
:medialib:bundleDebug
:medialib:compileDebugSources
:medialib:assembleDebug
:modulea:compileDebugSources
:modulea:assembleDebug
:core:compileDebugSources
:core:assembleDebug
:app:generateDebugSources
:app:javaPreCompileDebug
:app:compileDebugJavaWithJavac
:app:compileDebugNdk NO-SOURCE
:app:compileDebugSources
:app:mergeDebugShaders
:app:compileDebugShaders
:app:generateDebugAssets
:app:mergeDebugAssets
:app:transformClassesWithDexBuilderForDebug
:app:transformDexArchiveWithExternalLibsDexMergerForDebug
:app:transformDexArchiveWithDexMergerForDebug
:app:mergeDebugJniLibFolders
:app:transformNativeLibsWithMergeJniLibsForDebug
:app:processDebugJavaRes NO-SOURCE
:app:transformResourcesWithMergeJavaResForDebug
:app:validateSigningDebug
:app:packageDebug
:app:assembleDebug

BUILD SUCCESSFUL in 13s
105 actionable tasks: 103 executed, 2 up-to-date
gradle 执行完毕啦。。。

可以看到配置阶段结束后执行了对应的clean任务, 但是执行了很多其他的任务,原因是clean 这个task有很多其他的依赖。以上是关于Gradle生命周期的监听,好像讲的并不好。

三、Gradle project的概念

对于使用eclipse的同学可能都知道,一个项目工程就是一个project然后依赖一个library包。 对于Android studio开发的同学,一个项目工程由一个project和多个module组成。这里写图片描述
但是对于gradle而言,其实不管是project还是module 它都是一个project,只是project对应的是根project,而module对应的是子project。 原因是他们每个文件夹下都对应有一个build.gradle 。如果你把这个文件删除,他就不是一个project了,编译时编辑器会提示你将它移除。
通过./gradlew projects这个命令可以得到验证,他会打印该工程下的所有的project名字。
这里写图片描述
一个build.gradle 对应的就是 一个project那么我们能利用project做些什么呢?下面介绍下与project相关的核心api吧,看看到底可以利用project做那些事。

1、project相关的api

this.getAllprojects() //获取当前工程所有的project
this.getSubprojects() //获取当前project下的所有子project
this.getParent() // 获取当前project的父project
this.getRootProject() //  获取当前工程的根project

//模仿打印一个  ./gradlew projects命令
this.getAllprojects().eachWithIndex { Project project, int index ->
    if(index == 0) {
        println "Root project '${project.name}'"
    }else {
        println "+---project '${project.name}'"
    }
}

this.getSubprojects().eachWithIndex { Project project, int index ->
    println "+---project '${project.name}'"
}

//打印当前build.gradle的父project
println this.getParent()

//打印当前build.gradle的根project
println this.getRootProject()

通过上面的方法,我们可以知道在根project中获取到所有的子project,那么我们可以在根project中去管理子project吗? 当然是可以。
如何管理呢?方法一、就是通过遍历找到指定的子project然后对它进行操作
方法二、通过gradle提供的api来找到子project进行操作
1、project(“path”){} 参数1是目标project的路径 参数2 是一个闭包,闭包的参数就是目标project
下面看看源码的介绍

/**
* <p>Locates a project by path and configures it using the given closure. If the path is relative, it is
* interpreted relative to this project. The target project is passed to the closure as the closure's delegate.</p>
*
* @param path The path.
* @param configureClosure The closure to use to configure the project.
* @return The project with the given path. Never returns null.
* @throws UnknownProjectException If no project with the given path exists.
*/
Project project(String path, @DelegatesTo(value = Project.class, strategy = Closure.DELEGATE_FIRST) @ClosureParams(ReturnType.class) Closure configureClosure);

下面验证下是否与我们所想的那样,我们在app build.gradle这个project下打印一段话

//根工程的build.gradle
project("app"){
    println "++++++ I am app project。。。。。。"
}

//对应的日志
Executing tasks: [:app:generateDebugSources, :app:generateDebugAndroidTestSources, :app:mockableAndroidJar, :core:generateDebugSources, :core:generateDebugAndroidTestSources, :core:mockableAndroidJar, :medialib:generateDebugSources, :medialib:generateDebugAndroidTestSources, :medialib:mockableAndroidJar, :modulea:generateDebugSources, :modulea:generateDebugAndroidTestSources, :modulea:mockableAndroidJar]

初始化阶段开始啦。。。。
Parallel execution with configuration on demand is an incubating feature.
++++++ I am app project。。。。。。

可以看到日志被打印出来,它跟我们在app build.gradle添加一句打印是一样的效果
2、allprojects{}

allprojects { Project project ->
    println "${project.name}++++++  所有的 project都加一句打印。。。。。。"
}

//打印的日志
初始化阶段开始啦。。。。
Parallel execution with configuration on demand is an incubating feature.
GradleStudy++++++  所有的 project都加一句打印。。。。。。
app++++++  所有的 project都加一句打印。。。。。。
core++++++  所有的 project都加一句打印。。。。。。
medialib++++++  所有的 project都加一句打印。。。。。。
modulea++++++  所有的 project都加一句打印。。。。。。

3、subprojects {} 跟上面两个功能一样,这里就不举例了

2、属性相关的api

说到属性相关的api 我们应该接触的最多,看下面

android {
    compileSdkVersion 26
    ....
}

是不是非常熟悉,compileSdkVersion就是project的扩展属性,那我们如何定义扩展属性呢?

1、可以通过ext扩展块来定义,如在app build.gradle 下定义

ext{
    mCompileSdkVersion = 26
}
android {
    compileSdkVersion mCompileSdkVersion
    ...
}

编译发现我们是可以通过的,但是好想我们这样定义并没有给我们带来好处,我们依然要每个build.gradle去配置属性。其实结合上一小节中的project相关api做到一个地方配置,所有地方都可以使用。

// 1、在根project中定义所有的子project
subprojects {
    ext{
        mCompileSdkVersion = 26
    }
}
// 2 、在app build.gradle中直接使用
android {
    compileSdkVersion mCompileSdkVersion
    ...
}

这样我们就可以在一个地方编写,所有子project都可以使用。但是这样还是有点不好,因为这样等于是用代码代替块手动在每个子project中去添加ext扩展模块。本质上还是在每个子的build.gradle 加入了ext扩展块。
下面介绍一种一个地方定义所有地方都可以使用的方法。 结合我们java中对象的特点,父类属性的子类都可以使用。我们能不能在根project中添加ext扩展属性呢?试一试

// 1、在根project中定义
ext{
    mCompileSdkVersion = 26
}
// 2 、在app build.gradle中直接使用
android {
    compileSdkVersion mCompileSdkVersion
    ...
}

编译并没有报错,说明这样是可行的
最后介绍一种终极版本的定义方式,基于gradle配置文件,然后使用apply from 引入的方式,引入扩展属性。
1、在根工程下新建一个xxxx.gradle 文件这里写图片描述

// 输入以下内容
ext{
    android = [
            mCompileSdkVersion : 26
    ]
}

2、通过在根apply from: this.file('xxxxx.gradle')
根build.gradle中

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

3、引入你需要的属性rootProject.ext.xxxx

android {
    compileSdkVersion rootProject.ext.android.mCompileSdkVersion
    ....
}

2、属性扩展的另一种定义方式

在gradle.properties中定义,注意这里只能定义key和value形式
这里写图片描述
然后在setting.gradle中判断是否加载medialib模块

include ':app', ':core', ':medialib', ':modulea'
if(hasProperty(isLoadMedialib) && !isLoadMedialib.toBoolean()){
    include ':medialib'
}

3、文件相关的api

1、获取类型的api

println "项目文件的根路径 === ${getRootDir().absolutePath}"
println "项目build文件的路径 === ${getBuildDir().absolutePath}"
println "项目的路径 == ${getProjectDir().absolutePath}"

//输出结果为:
项目文件的根路径 === /Users/xxx/Desktop/xxx/workspace/GradleStudy
项目build文件的路径 === /Users/xxx/Desktop/xxx/workspace/GradleStudy/build
项目的路径 == /Users/xxx/Desktop/xxx/workspace/GradleStudy

通过这种方式可以找到对应功能的文件路径,通常使用这个动态配置signingConfigs的storeFile路径等等

2、文件定位

this.file("xxxx.gradle")
this.files("xxxx.gradle")

3、拷贝

copy {
    from this.file("buildconfig.gradle")
    into getRootProject().getBuildDir()
}

执行的结果吧文件正确的拷贝到了根project 的build文件夹下
这里写图片描述

4、文件树遍历


fileTree("app"){FileTree fileTree ->
    fileTree.visit {FileTreeElement element ->
        if(element.file.isFile() && element.file.name.startsWith("ic_launcher_background")){
            println " =========  ${element.file.name}"
        }

    }
}

输出结果为
这里写图片描述

5、依赖相关的api 和 外部命令执行

依赖相关的api其实我们经常见到

buildscript {

    repositories {  //配置仓库地址
        google()
        jcenter()
    }
    dependencies { //配置project相关的'插件'依赖
        classpath 'com.android.tools.build:gradle:3.0.0'
    }
}

注意dependencies 其实有两个另一个是在子project的android 下,它主要配置的是应用程序所需要的jar 、aar等依赖
外部命令的执行,将build/outputs/apk文件夹下的apk等文件拷贝到桌面

// 1、定义一个task acopy
task acopy{
    println " 我是 acopy task"
} 

acopy.doLast{
    println "命令执行成功sss  ====="
    def sourcePath = this.getBuildDir().path + "/outputs/apk"
    println sourcePath
    def destinationPath = "/Users/dsh/Desktop/"
    def command = "mv -f ${sourcePath} ${destinationPath}"

    exec{
        try{
            executable 'bash'
            args '-c',command
            println "命令执行成功  ====="
        }catch (GradleException e){
            println "命令执行失败  ====="
        }
    }
}

执行 ./gradlew acopy命令
这里写图片描述

猜你喜欢

转载自blog.csdn.net/dingshuhong_/article/details/80213095
今日推荐