深入学习-Gradle-自动化构建技术(三)Gradle-核心解密

首先,我们使用了 def 关键字定义了一个 getProjects 方法。然后,在注释1处,我们调用了 getAllprojects 方法返回一个包含根 project 与其子 project 的 Set 集合,并链式调用了 eachWithIndex 遍历 Set 集合。接着,在注释2处,我们会判断当前的下标 index 是否是0,如果是,则表明当前遍历的是 rootProject,则输出 rootProject 的名字,否则,输出 child project 的名字。

下面,我们在命令行执行 ./gradlew clean,其运行结果如下所示:

quchao@quchaodeMacBook-Pro Awesome-WanAndroid % ./gradlew clean
settings 评估完成(settings.gradle 中代码执行完毕)
项目结构加载完成(初始化阶段结束)
初始化结束,可访问根项目:root project ‘Awesome-WanAndroid’
初始化阶段,耗时:5ms
Configuration on demand is an incubating feature.

Configure project :
<>
Root Project Start
<
>
Root Project is root project ‘Awesome-WanAndroid’
child Project is project ‘:app’
配置阶段,root project 'Awesome-WanAndroid’耗时:284ms
Configure project :app

配置阶段,总共耗时:428ms
Task :app:clean
执行阶段,task ':app:clean’耗时:1ms
:app:clean spend 2ms
构建结束
Tasks spend time > 50ms:
执行阶段,耗时:9ms

可以看到,执行了初始化之后,就会先配置我们的 rootProject,并输出了对应的工程信息。接着,便会执行子工程 app 的配置。最后,执行了 clean 这个 task。

需要注意的是,rootProject 与其旗下的各个子工程组成了一个树形结构,但是这颗树的高度也仅仅被限定为了两层

2、getSubprojects

getSubprojects 表示获取当前工程下所有子工程的实例,示例代码如下所示:

/**

  • getAllsubproject 使用示例
    */
    this.getSubProjects()

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

同 getAllprojects 的用法一样,getSubprojects 方法返回了一个包含子 project 的 Set 集合,这里我们直接使用 each 方法将各个子 project 的名字打印出来。其运行结果如下所示:

quchao@quchaodeMacBook-Pro Awesome-WanAndroid % ./gradlew clean
settings 评估完成(settings.gradle 中代码执行完毕)

Configure project :
<>
Sub Project Start
<
>
child Project is project ‘:app’
配置阶段,root project 'Awesome-WanAndroid’耗时:289ms
Configure project :app

所有项目评估完成(配置阶段结束)
配置阶段,总共耗时:425ms
Task :app:clean
执行阶段,task ':app:clean’耗时:1ms
:app:clean spend 2ms
构建结束
Tasks spend time > 50ms:
执行阶段,耗时:9ms

可以看到,同样在 Gradle 的配置阶段输出了子工程的名字。

3、getParent

getParent 表示 获取当前 project 的父类,需要注意的是,如果我们在根工程中使用它,获取的父类会为 null,因为根工程没有父类,所以这里我们直接在 app 的 build.gradle 下编写下面的示例代码:

Configure project :
配置阶段,root project 'Awesome-WanAndroid’耗时:104ms
Configure project :app
gradlew version > 4.0
my parent project is Awesome-WanAndroid
配置阶段,project ':app’耗时:282ms

所有项目评估完成(配置阶段结束)
配置阶段,总共耗时:443ms

可以看到,这里输出了 app project 当前的父类,即 Awesome-WanAndroid project。

4、getRootProject

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

/**

  • 4、getRootProject 使用示例
    */
    this.getRootPro()

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

5、project

project 表示的是 指定工程的实例,然后在闭包中对其进行操作。在使用之前,我们有必要看看 project 方法的源码,如下所示:

/**

  • 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.
  • @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, Closure configureClosure);

可以看到,在 project 方法中两个参数,一个是指定工程的路径,另一个是用来配置该工程的闭包。下面我们看看如何灵活地使用 project,示例代码如下所示:

/**

  • 5、project 使用示例
    */

// 1、闭包参数可以放在括号外面
project(“app”) { Project project ->
apply plugin: ‘com.android.application’
}

// 2、更简洁的写法是这样的:省略参数
project(“app”) {
apply plugin: ‘com.android.application’
}

使用熟练之后,我们通常会采用注释2处的写法。

6、allprojects

allprojects 表示 用于配置当前 project 及其旗下的每一个子 project,如下所示:

/**

  • 6、allprojects 使用示例
    */

// 同 project 一样的更简洁写法
allprojects {
repositories {
google()
jcenter()
mavenCentral()
maven {
url “https://jitpack.io”
}
maven { url “https://plugins.gradle.org/m2/” }
}
}

在 allprojects 中我们一般用来配置一些通用的配置,比如上面最常见的全局仓库配置。

7、subprojects

subprojects 可以 统一配置当前 project 下的所有子 project,示例代码如下所示:

/**

  • 7、subprojects 使用示例:
  • 给所有的子工程引入 将 aar 文件上传置 Maven 服务器的配置脚本
    */
    subprojects {
    if (project.plugins.hasPlugin(“com.android.library”)) {
    apply from: ‘…/publishToMaven.gradle’
    }
    }

在上述示例代码中,我们会先判断当前 project 旗下的子 project 是不是库,如果是库才有必要引入 publishToMaven 脚本。

3、project 属性

目前,在 project 接口里,仅仅预先定义了 七个 属性,其源码如下所示:

public interface Project extends Comparable, ExtensionAware, PluginAware {
/**

  • 默认的工程构建文件名称
    */
    String DEFAULT_BUILD_FILE = “build.gradle”;

/**

  • 区分开 project 名字与 task 名字的符号
    */
    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 关键字让我们有能力去定义自身所需要的扩展属性。有了它便可以对我们工程中的依赖进行全局配置。下面,我们先从配置的远古时代讲起,以便让我们对 gradle 的 全局依赖配置有更深入的理解。

ext 扩展属性

1、远古时代

在 AS 刚出现的时候,我们的依赖配置代码是这样的:

android {
compileSdkVersion 27
buildToolsVersion “28.0.3”

}

2、刀耕火种

但是这种直接写值的方式显示是不规范的,因此,后面我们使用了这种方式:

def mCompileSdkVersion = 27
def mBuildToolsVersion = “28.0.3”

android {
compileSdkVersion mCompileSdkVersion
buildToolsVersion mBuildToolsVersion

}

3、铁犁牛耕

如果每一个子 project 都需要配置相同的 Version,我们就需要多写很多的重复代码,因此,我们可以利用上面我们学过的 subproject 和 ext 来进行简化:

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

// 在 app moudle 下的 build.gradle 中
android {
compileSdkVersion this.compileSdkVersion
buildToolsVersion this.buildToolsVersion

}

4、工业时代

使用 subprojects 方法来定义通用的扩展属性还是存在着很严重的问题,它跟之前的方式一样,还是会在每一个子 project 去定义这些被扩展的属性,此时,我们可以将 subprojects 去除,直接使用 ext 进行全局定义即可:

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

5、电器时代

当项目越来越大的时候,在根项目下定义的 ext 扩展属性越来越多,因此,我们可以将这一套全局属性配置在另一个 gradle 脚本中进行定义,这里我们通常会将其命名为 config.gradle,通用的模板如下所示:

ext {

android = [
compileSdkVersion : 27,
buildToolsVersion : “28.0.3”,

]

version = [
supportLibraryVersion : “28.0.0”,

]

dependencies = [
// base
“appcompat-v7” : “com.android.support:appcompat-v7:${version[“supportLibraryVersion”]}”,

]

annotationProcessor = [
“glide_compiler” : “com.github.bumptech.glide:compiler:${version[“glideVersion”]}”,

]

apiFileDependencies = [
“launchstarter” : “libs/launchstarter-release-1.0.0.aar”,

]

debugImplementationDependencies = [
“MethodTraceMan” : “com.github.zhengcx:MethodTraceMan:1.0.7”
]

releaseImplementationDependencies = [
“MethodTraceMan” : “com.github.zhengcx:MethodTraceMan:1.0.5-noop”
]


}

6、更加智能化的现在

尽管有了很全面的全局依赖配置文件,但是,在我们的各个模块之中,还是不得不写一大长串的依赖代码,因此,我们可以 使用遍历的方式去进行依赖,其模板代码如下所示:

// 在各个 moulde 下的 build.gradle 脚本下
def implementationDependencies = rootProject.ext.dependencies
def processors = rootProject.ext.annotationProcessor
def apiFileDependencies = rootProject.ext.apiFileDependencies

// 在各个 moulde 下的 build.gradle 脚本的 dependencies 闭包中
// 处理所有的 aar 依赖
apiFileDependencies.each { k, v -> api files(v)}

// 处理所有的 xxximplementation 依赖
implementationDependencies.each { k, v -> implementation v }
debugImplementationDependencies.each { k, v -> debugImplementation v }

// 处理 annotationProcessor 依赖
processors.each { k, v -> annotationProcessor v }

// 处理所有包含 exclude 的依赖
debugImplementationExcludes.each { entry ->
debugImplementation(entry.key) {
entry.value.each { childEntry ->
exclude(group: childEntry.key, module: childEntry.value)
}
}
}

也许未来随着 Gradle 的不断优化会有更加简洁的方式,如果你有更好地方式,我们可以来探讨一番。

在 gradle.properties 下定义扩展属性

除了使用 ext 扩展属性定义额外的属性之外,我们也可以在 gradle.properties 下定义扩展属性,其示例代码如下所示:

// 在 gradle.properties 中
mCompileVersion = 27

// 在 app moudle 下的 build.gradle 中
compileSdkVersion mCompileVersion.toInteger()

4、文件相关 API

在 gradle 中,文件相关的 API 可以总结为如下 两大类

  • 1)、路径获取 API

  • getRootDir()

  • getProjectDir()

  • getBuildDir()

  • 2)、文件操作相关 API

  • 文件定位

  • 文件拷贝

  • 文件树遍历

1)、路径获取 API

关于路径获取的 API 常用的有 三种,其示例代码如下所示:

/**

  • 1、路径获取 API
    */
    println “the root file path is:” + getRootDir().absolutePath
    println “this build file path is:” + getBuildDir().absolutePath
    println “this Project file path is:” + getProjectDir().absolutePath

然后,我们执行 ./gradlew clean,输出结果如下所示:

Configure project :
the root file path is:/Users/quchao/Documents/main-open-project/Awesome-WanAndroid
this build file path is:/Users/quchao/Documents/main-open-project/Awesome-WanAndroid/build
this Project file path is:/Users/quchao/Documents/main-open-project/Awesome-WanAndroid
配置阶段,root project 'Awesome-WanAndroid’耗时:538ms

2)、文件操作相关 API

1、文件定位

常用的文件定位 API 有 file/files,其示例代码如下所示:

// 在 rootProject 下的 build.gradle 中

/**

  • 1、文件定位之 file
    */
    this.getContent(“config.gradle”)

def getContent(String path) {
try {
// 不同与 new file 的需要传入 绝对路径 的方式,
// file 从相对于当前的 project 工程开始查找
def mFile = file(path)
println mFile.text
} catch (GradleException e) {
println e.toString()
return null
}
}

/**

  • 1、文件定位之 files
    */
    this.getContent(“config.gradle”, “build.gradle”)

def getContent(String path1, String path2) {
try {
// 不同与 new file 的需要传入 绝对路径 的方式,
// file 从相对于当前的 project 工程开始查找
def mFiles = files(path1, path2)
println mFiles[0].text + mFiles[1].text
} catch (GradleException e) {
println e.toString()
return null
}
}

2、文件拷贝

常用的文件拷贝 API 为 copy,其示例代码如下所示:

/**

  • 2、文件拷贝
    */
    copy {
    // 既可以拷贝文件,也可以拷贝文件夹
    // 这里是将 app moudle 下生成的 apk 目录拷贝到
    // 根工程下的 build 目录
    from file(“build/outputs/apk”)
    into getRootProject().getBuildDir().path + “/apk/”
    exclude {
    // 排除不需要拷贝的文件
    }
    rename {
    // 对拷贝过来的文件进行重命名
    }
    }
3、文件树遍历

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

/**

  • 3、文件树遍历
    */
    fileTree(“build/outputs/apk”) { FileTree fileTree ->
    fileTree.visit { FileTreeElement fileTreeElement ->
    println “The file is $fileTreeElement.file.name”
    copy {
    from fileTreeElement.file
    into getRootProject().getBuildDir().path + “/apkTree/”
    }
    }
    }

5、其它 API

1、依赖相关 API

根项目下的 buildscript

buildscript 中 用于配置项目核心的依赖。其原始的使用示例与简化后的使用示例分别如下所示:

原始的使用示例

buildscript { ScriptHandler scriptHandler ->
// 配置我们工程的仓库地址
scriptHandler.repositories { RepositoryHandler repositoryHandler ->
repositoryHandler.google()
repositoryHandler.jcenter()
repositoryHandler.mavenCentral()
repositoryHandler.maven { url ‘https://maven.google.com’ }
repositoryHandler.maven { url “https://plugins.gradle.org/m2/” }
repositoryHandler.maven {
url uri(‘…/PAGradlePlugin/repo’)
}
// 访问本地私有 Maven 服务器
repositoryHandler.maven {
name “personal”
url “http://localhost:8081:/JsonChao/repositories”
credentials {
username = “JsonChao”
password = “123456”
}
}
}

// 配置我们工程的插件依赖
dependencies { DependencyHandler dependencyHandler ->
dependencyHandler.classpath ‘com.android.tools.build:gradle:3.1.4’

简化后的使用示例

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 moudle 下的 dependencies

不同于 根项目 buildscript 中的 dependencies 是用来配置我们 Gradle 工程的插件依赖的,而 app moudle 下的 dependencies 是用来为应用程序添加第三方依赖的。关于 app moudle 下的依赖使用这里我们 需要注意下 exclude 与 transitive 的使用 即可,示例代码如下所示:

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

2、外部命令执行

我们一般是 使用 Gradle 提供的 exec 来执行外部命令,下面我们就使用 exec 命令来 将当前工程下新生产的 APK 文件拷贝到 电脑下的 Downloads 目录中,示例代码如下所示:

/**

  • 使用 exec 执行外部命令
    */
    task apkMove() {
    doLast {
    // 在 gradle 的执行阶段去执行
    def sourcePath = this.buildDir.path + “/outputs/apk/speed/release/”
    def destinationPath = “/Users/quchao/Downloads/”
    def command = “mv -f $sourcePath $destinationPath”
    exec {
    try {
    executable “bash”
    args “-c”, command
    println “The command execute is success”
    } catch (GradleException e) {
    println “The command execute is failed”
    }
    }
    }
    }

四、Task

只有 Task 才可以在 Gradle 的执行阶段去执行(其实质是执行的 Task 中的一系列 Action),所以 Task 的重要性不言而喻。

1、从一个例子 出发

首先,我们可以在任意一个 build.gradle 文件中可以去定义一个 Task,下面是一个完整的示例代码:

// 1、声明一个名为 JsonChao 的 gradle task
task JsonChao
JsonChao {
// 2、在 JsonChao task 闭包内输出 hello~,
// 执行在 gradle 生命周期的第二个阶段,即配置阶段。
println(“hello~”)
// 3、给 task 附带一些 执行动作(Action),执行在
// gradle 生命周期的第三个阶段,即执行阶段。
doFirst {
println(“start”)
}
doLast {
println(“end”)
}
}
// 4、除了上述这种将声明与配置、Action 分别定义
// 的方式之外,也可以直接将它们结合起来。
// 这里我们又定义了一个 Android task,它依赖于 JsonChao
// task,也就是说,必须先执行完 JsonChao task,才能
// 去执行 Android task,由此,它们之间便组成了一个
// 有向无环图:JsonChao task => Android task
task Andorid(dependsOn:“JsonChao”) {
doLast {
println(“end?”)
}
}

首先,在注释1处,我们声明了一个名为 JsonChao 的 gradle task。接着,在注释2处,在 JsonChao task 闭包内输出了 hello~,这里的代码将会执行在 gradle 生命周期的第二个阶段,即配置阶段。然后,在注释3处,这里 给 task 附带一些了一些执行动作(Action),即 doFirst 与 doLast,它们闭包内的代码将执行在 gradle 生命周期的第三个阶段,即执行阶段

对于 doFirst 与 doLast 这两个 Action,它们的作用分别如下所示:

  • doFirst表示 task 执行最开始的时候被调用的 Action
  • doLast表示 task 将执行完的时候被调用的 Action

需要注意的是,doFirst 和 doLast 是可以被执行多次的

最后,注释4处,我们可以看到,除了注释1、2、3处这种将声明与配置、Action 分别定义的方式之外,也可以直接将它们结合起来。在这里我们又定义了一个 Android task,它依赖于 JsonChao task,也就是说,必须先执行完 JsonChao task,才能 去执行 Android task,由此,它们之间便组成了一个 有向无环图:JsonChao task => Android task

执行 Android 这个 gradle task 可以看到如下输出结果:

Task :JsonChao
start
end
执行阶段,task ':JsonChao’耗时:1ms
:JsonChao spend 4ms
Task :Andorid
end?
执行阶段,task ':Andorid’耗时:1ms
:Andorid spend 2ms
构建结束
Tasks spend time > 50ms:
执行阶段,耗时:15ms

2、Task 的定义及配置

Task 常见的定义方式有 两种,示例代码如下所示:

// Task 定义方式1:直接通过 task 函数去创建(在 “()” 可以不指定 group 与 description 属性)
task myTask1(group: “MyTask”, description: “task1”) {
println “This is myTask1”
}

// Task 定义方式2:通过 TaskContainer 去创建 task
this.tasks.create(name: “myTask2”) {
setGroup(“MyTask”)
setDescription(“task2”)
println “This is myTask2”
}

定义完上述 Task 之后再同步项目,即可看到对应的 Task Group 及其旗下的 Tasks,如下图所示:

Task 的属性

需要注意的是,不管是哪一种 task 的定义方式,在 “()” 内我们都可以配置它的一系列属性,如下:

project.task(‘JsonChao3’, group: “JsonChao”, description: “my tasks”,
dependsOn: [“JsonChao1”, “JsonChao2”] ).doLast {
println “execute JsonChao3 Task”
}

目前 官方所支持的属性 可以总结为如下表格:

选型描述默认值
“name”task 名字无,必须指定
“type”需要创建的 task ClassDefaultTask
“action”当 task 执行的时候,需要执行的闭包 closure 或 行为 Actionnull
“overwrite”替换一个已存在的 taskfalse
“dependsOn”该 task 所依赖的 task 集合[]
“group”该 task 所属组null
“description”task 的描述信息null
“constructorArgs”传递到 task Class 构造器中的参数null
使用 “$” 来引用另一个 task 的属性

在这里,我们可以 在当前 task 中使用 “$” 来引用另一个 task 的属性,示例代码如下所示:

task Gradle_First() {

}

task Gradle_Last() {
doLast {
println “I am not $Gradle_First.name”
}
}

使用 ext 给 task 自定义需要的属性

当然,除了使用已有的属性之外,我们也可以 使用 ext 给 task 自定义需要的属性,代码如下所示:

task Gradle_First() {
ext.good = true
}

task Gradle_Last() {
doFirst {
println Gradle_First.good
}
doLast {
println “I am not $Gradle_First.name”
}
}

使用 defaultTasks 关键字标识默认执行任务

此外,我们也可以 使用 defaultTasks 关键字 来将一些任务标识为默认的执行任务,代码如下所示:

defaultTasks “Gradle_First”, “Gradle_Last”

task Gradle_First() {
ext.good = true
}

task Gradle_Last() {
doFirst {
println Gradle_First.goodg
}
doLast {
println “I am not $Gradle_First.name”
}
}

注意事项

每个 task 都会经历 初始化、配置、执行 这一套完整的生命周期流程

3、Task 的执行详解

Task 通常使用 doFirst 与 doLast 两个方式用于在执行期间进行操作。其示例代码如下所示:

// 使用 Task 在执行阶段进行操作
task myTask3(group: “MyTask”, description: “task3”) {
println “This is myTask3”
doFirst {
// 老二
println “This group is 2”
}

doLast {
// 老三
println “This description is 3”
}
}

// 也可以使用 taskName.doxxx 的方式添加执行任务
myTask3.doFirst {
// 这种方式的最先执行 => 老大
println “This group is 1”
}

Task 执行实战

接下来,我们就使用 doFirst 与 doLast 来进行一下实战,来实现 计算 build 执行期间的耗时,其完整代码如下所示:

// Task 执行实战:计算 build 执行期间的耗时
def startBuildTime, endBuildTime
// 1、在 Gradle 配置阶段完成之后进行操作,
// 以此保证要执行的 task 配置完毕
this.afterEvaluate { Project project ->
// 2、找到当前 project 下第一个执行的 task,即 preBuild task
def preBuildTask = project.tasks.getByName(“preBuild”)
preBuildTask.doFirst {
// 3、获取第一个 task 开始执行时刻的时间戳
startBuildTime = System.currentTimeMillis()
}
// 4、找到当前 project 下最后一个执行的 task,即 build task
def buildTask = project.tasks.getByName(“build”)
buildTask.doLast {
// 5、获取最后一个 task 执行完成前一瞬间的时间戳
endBuildTime = System.currentTimeMillis()
// 6、输出 build 执行期间的耗时
println “Current project execute time is ${endBuildTime - startBuildTime}”
}
}

4、Task 的依赖和执行顺序

指定 Task 的执行顺序有 三种 方式,如下图所示:

1)、dependsOn 强依赖方式

dependsOn 强依赖的方式可以细分为 静态依赖和动态依赖,示例代码如下所示:

静态依赖

task task1 {
doLast {
println “This is task1”
}
}

task task2 {
doLast {
println “This is task2”
}
}

// Task 静态依赖方式1 (常用)
task task3(dependsOn: [task1, task2]) {
doLast {
println “This is task3”
}
}

// Task 静态依赖方式2
task3.dependsOn(task1, task2)

动态依赖

// Task 动态依赖方式
task dytask4 {
dependsOn this.tasks.findAll { task ->
return task.name.startsWith(“task”)
}
doLast {
println “This is task4”
}
}

2)、通过 Task 指定输入输出

我们也可以通过 Task 来指定输入输出,使用这种方式我们可以 高效地实现一个 自动维护版本发布文档的 gradle 脚本,其中输入输出相关的代码如下所示:

task writeTask {
inputs.property(‘versionCode’, this.versionCode)
inputs.property(‘versionName’, this.versionName)
inputs.property(‘versionInfo’, this.versionInfo)
// 1、指定输出文件为 destFile
outputs.file this.destFile
doLast {
//将输入的内容写入到输出文件中去
def data = inputs.getProperties()
File file = outputs.getFiles().getSingleFile()

// 写入版本信息到 XML 文件

}

task readTask {
// 2、指定输入文件为上一个 task(writeTask) 的输出文件 destFile
inputs.file this.destFile
doLast {
//读取输入文件的内容并显示
def file = inputs.files.singleFile
println file.text
}
}

task outputwithinputTask {
// 3、先执行写入,再执行读取
dependsOn writeTask, readTask
doLast {
println ‘输入输出任务结束’
}
}

首先,我们定义了一个 WirteTask,然后,在注释1处,指定了输出文件为 destFile, 并写入版本信息到 XML 文件。接着,定义了一个 readTask,并在注释2处,指定输入文件为上一个 task(即 writeTask) 的输出文件。最后,在注释3处,使用 dependsOn 将这两个 task 关联起来,此时输入与输出的顺序是会先执行写入,再执行读取。这样,一个输入输出的实际案例就实现了。如果想要查看完整的实现代码,请查看 Awesome-WanAndroid 的 releaseinfo.gradle 脚本

此外,在 McImage 中就利用了 dependsOn 的方式将自身的 task 插入到了 Gradle 的构建流程之中,关键代码如下所示:

// inject task
(project.tasks.findByName(chmodTask.name) as Task).dependsOn(mergeResourcesTask.taskDependencies.getDependencies(mergeResourcesTask))
(project.tasks.findByName(mcPicTask.name) as Task).dependsOn(project.tasks.findByName(chmodTask.name) as Task)
mergeResourcesTask.dependsOn(project.tasks.findByName(mcPicTask.name))

通过 API 指定依赖顺序

除了 dependsOn 的方式,我们还可以在 task 闭包中通过 mustRunAfter 方法指定 task 的依赖顺序,需要注意的是,在最新的 gradle api 中,mustRunAfter 必须结合 dependsOn 强依赖进行配套使用,其示例代码如下所示:

// 通过 API 指定依赖顺序
task taskX {
mustRunAfter “taskY”

doFirst {
println “this is taskX”
}
}

task taskY {
// 使用 mustRunAfter 指定依赖的(一至多个)前置 task
// 也可以使用 shouldRunAfter 的方式,但是是非强制的依赖
// shouldRunAfter taskA
doFirst {
println “this is taskY”
}
}

task taskZ(dependsOn: [taskX, taskY]) {
mustRunAfter “taskY”
doFirst {
println “this is taskZ”
}
}

5、Task 类型

除了定义一个新的 task 之外,我们也可以使用 type 属性来直接使用一个已有的 task 类型(很多文章都说的是继承一个已有的类,不是很准确),比如 Gradle 自带的 Copy、Delete、Sync task 等等。示例代码如下所示:

// 1、删除根目录下的 build 文件
task clean(type: Delete) {
delete rootProject.buildDir
}
// 2、将 doc 复制到 build/target 目录下
task copyDocs(type: Copy) {
from ‘src/main/doc’
into ‘build/target/doc’
}
// 3、执行时会复制源文件到目标目录,然后从目标目录删除所有非复制文件
task syncFile(type:Sync) {
from ‘src/main/doc’
into ‘build/target/doc’
}

6、挂接到构建生命周期

我们可以使用 gradle 提供的一系列生命周期 API 去挂接我们自己的 task 到构建生命周期之中,比如使用 afterEvaluate 方法 将我们第三小节定义的 writeTask 挂接到 gradle 配置完所有的 task 之后的时刻,示例代码如下所示:

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

最后

我见过很多技术leader在面试的时候,遇到处于迷茫期的大龄程序员,比面试官年龄都大。这些人有一些共同特征:可能工作了7、8年,还是每天重复给业务部门写代码,工作内容的重复性比较高,没有什么技术含量的工作。问到这些人的职业规划时,他们也没有太多想法。

其实30岁到40岁是一个人职业发展的黄金阶段,一定要在业务范围内的扩张,技术广度和深度提升上有自己的计划,才有助于在职业发展上有持续的发展路径,而不至于停滞不前。

不断奔跑,你就知道学习的意义所在!

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!**

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

[外链图片转存中…(img-AdZTV9A2-1713678402080)]

[外链图片转存中…(img-pDtBzjxZ-1713678402081)]

[外链图片转存中…(img-PXgcoza4-1713678402082)]

[外链图片转存中…(img-qOjfo1so-1713678402083)]

[外链图片转存中…(img-7ExYkj6U-1713678402084)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

最后

我见过很多技术leader在面试的时候,遇到处于迷茫期的大龄程序员,比面试官年龄都大。这些人有一些共同特征:可能工作了7、8年,还是每天重复给业务部门写代码,工作内容的重复性比较高,没有什么技术含量的工作。问到这些人的职业规划时,他们也没有太多想法。

其实30岁到40岁是一个人职业发展的黄金阶段,一定要在业务范围内的扩张,技术广度和深度提升上有自己的计划,才有助于在职业发展上有持续的发展路径,而不至于停滞不前。

不断奔跑,你就知道学习的意义所在!

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

  • 27
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值