目前国内对Android领域的探索已经越来越深,不少技术领域如插件化、热修复、构建系统等都对Gradle有迫切的需求,不懂Gradle将无法完成上述事情。所以Gradle必须要学习。
Gradle 里的几乎任何东西都是基于这两个基础概念:
- task
- project
掌握了这两个,你就掌握了一大半的 Gradle 知识了。
首先讲 Task
字面理解为任务,Gradle 中所有执行的事件都是借由 Task 执行的。
例如我们新建一个 Android 工程,在其根目录中输入:
gradle tasks -q
可以看到如下输出(你可能需要事先配置gradle的环境变量,或也可使用./gradlew
替代):
根据上图可以看到当前工程中的每条task
都已罗列出,并且有黄色的输出表示当前task
的描述。
其中-q
表示忽略gradle
本身的log
信息,加上这个参数可以屏蔽很多无关的输出,不加也不会影响执行。
Task声明格式
声明一个 task 只需要在任务名前面加上task
就可以了,例如下面声明了一个hello
的Task。
task hello
通常我们会给task附带一些执行动作,称之为Action
,例如
hello.doFirst{ println "hello first" } hello.doLast{ println "hello last" }
也可以附带一个闭包配置,称之为Configuration
,闭包中不仅可用做赋值操作,也可以执行一些自动执行的配置。
hello { println "hello" }
Task依赖
单独声明一个task
在实际开发中几乎不会有任何的意义,更多的时候是让多个task
组合起来,一个依赖另一个,形成一连串的任务集。
task hello hello.doFirst{ println "hello " } task world(dependsOn: "hello") << { println "world" }
上面这段代码定义了两个task,当我们执行hello
任务的时候,会输出 hello
,而执行world
任务的时候,由于声明了dependsOn: "hello"
,表示world
依赖hello
,会先执行hello,再执行world。
task xxx << { }
这样的语法等价于
task xxx xxx.dolast { }
你可以在任意位置新建一个名为build.gradle
的文本,来练习上面讲述的task
定义与依赖。
gradle, android, kotlin, tasks
接着讲 Project
Android │ ├──app │ └──build.gradle │ ├──library │ └──build.gradle │ ├──*.properties │ ├──build.gradle │ └──setting.gradle
一个 Android 工程,通常是由上述结构构成,其中有着许多不为人知的巧妙用法。
setting.gradle文件
关于setting.gradle
中也可以写代码,是很多人不知道的。在setting.gradle
文件中,可以指定一个project
位置,这里就可以将一个外部工程中的模块导入到APP工程中了。
getLocalProperties().entrySet().each { entry -> def moduleName = entry.key if (Boolean.valueOf(entry.value)) { def file = new File(rootProject.projectDir.parent, "/${moduleName.replace("\\W", "")}/${moduleName.toLowerCase()}") if (file.exists()) { include ":${moduleName.toLowerCase()}" project(":${moduleName.toLowerCase()}").projectDir = file } } }
build.gradle
一个项目的根gradle文件,用于描述这个项目的统一资源,其中包括各子资源的使用方式、插件的依赖环境等等。
subprojects{ apply plugin: 'com.android.library' dependencies { compile 'com.xxx.xxx:xxx:1.0.0' } }
通常我们在每个模块都会引用的 aar 的时候,都会在每个模块里面都去手动的compile
一遍,例如support
包。 但实际上有一个非常简单的办法,写一遍就可以了,就是在项目的根gradle
文件中的subprojects
闭包中声明这个dependencies
。
通常在写compile
依赖的时候,我们都会写成这样:
compile 'com.android.support:appcompat-v7:25.0.0'
其实在gradle
中,这是一个方法调用,它的本质是compile()
方法传入了一个map
参数,因此完整的写法实际上是这样的:
compile group: 'com.android.support' name:'appcompat-v7' version:'25.0.0'
同时,map 的可使用 key 不只是有常用的group
、name
、version
,还包括不常用的configuration
、classifier
等等。
再看Task
Groovy 是基于 Java 的,只不过在这基础上加了一大堆的闭包,来帮助更方便的开发构建脚本。如果你不会 Groovy,没关系,当成 Java 写就行了,其实当成 Kotlin 写是最恰当的。如果你还不会 Kotlin,我强烈推荐你查看我的 【 Kotlin Primer 】系列文章
每个Task都可以配置其输入与输出,如果一个Task的输出与上一次的输出一致,则不会重复执行。此刻,会在命令行中输出UP-TO-DATE
表示已经是最新的结果。
例如如下Task:
task transform { ext.srcFile = file('hello.txt') ext.destDir = new File(buildDir, 'generated') inputs.file srcFile outputs.dir destDir doLast { destDir.mkdirs() def ins = new BufferedReader(new FileReader(srcFile)) def stringBuilder = new StringBuilder() def temp while ((temp = ins.readLine()) != null) { stringBuilder.append(temp) } def destFile = new File(destDir, "world.txt") destFile.text = stringBuilder.toString() } }
重复执行后会输出UP-TO-DATE
gradle, android, kotlin, tasks
骚操作的背后
学习任何一门技术,最快的途径就是看源码,gradle
的源码位于src
目录中,例如在我本机的路径为:/Users/zhanghongjun/.gradle/wrapper/dists/gradle-3.3-all/55gk2rcmfc6p2dg9u9ohc3hw9/gradle-3.3/src
本地新建一个普通Java工程,导入源码查看代码与注释,这是最好的学习资料。
task hello
在 Groovy 中,方法括号可以省略,如果字符串的类型是可以被推断的,那么引号也可以省略
public interface org.gradle.api.Project{ Task task(String name); Task task(String name, Closure configureClosure); } // TaskFactory public TaskInternal createTask(Map<String, ?> args) { }
闭包的存在,目的是为了更好的为对象初始化。同 Kotlin 一样,当闭包做为最后一个参数的时候,可以省略括号。
Copy a = task(myCopy, type: Copy) a.from 'resources' a.into 'target' a.include('**/*.txt', '**/*.xml', '**/*.properties')
等价于
task myCopy(type: Copy) myCopy { from 'resources' into 'target' include('**/*.txt', '**/*.xml', '**/*.properties') }
本章就讲到这里,下一篇讲如何创建一个Gradle插件,完成编译时向指定类或新生成类中动态添加代码(包括jar包中)。