3. 使用本地 Gradle 文件编译项目
Gradle 拥有良好的兼容性,为了在没有 Gradle 环境的机器上也能顺利使用 Gradle 构建项目,AS 新创建的项目默认会在根目录下添加 wrapper 配置
其中 gradle-wrapper.properties 文件中提供了该项目使用的 Gradle 构建工具远程下载地址,这里会对应一个具体的版本号,IDE 开发工具默认会根据这个路径去下载 Gradle 给该项目使用
distributionUrl=https ://services.gradle.org/distributions/gradle- 6.1. 1-all.zip
这样就会产生一个问题:
每个项目单独管理自己的 gradle,很可能会造成机器上同时存在多个版本的 Gradle,进而存在多个版本的 Daemon 进程,这会造成机器资源吃紧,即便关闭 AS 开发工具也没用,只能重启机器才会好转。
所以这里我推荐,尤其是给使用 AS 的朋友推荐:在本地创建 Gradle 环境,统一管理 Gradle 构建工具,避免出现多版本同时运行的问题。AS 本身就很吃内存了,每一个 Daemon 构建进程起码都是 512M 内存起步的,多来几个 Daemon 进程,我这 8G 的 MAC 真的搂不住
1. 打开 AS 中 Gradle 配置:
gradle-wrapper.properties -- 使用 wrapper 也就是 AS 来管理 Gradle
Specifiled location -- 使用本地文件,也就是我们自己管理 Gradle
2. 在本地解压 Gradle 压缩包,记住路径,下面配 path 需要
这样配置后,AS 会忽略 gradle-wrapper.properties 文件
4. 配置 path
export GRADLE_HOME= /Users/zbzbgo/gradle/gradle- 6.6. 1
export PATH=${PATH} :/Users/zbzbgo/gradle/gradle-6.6. 1/bin
----------------官方写法如下--------------------------------
export GRADLE_HOME= /Users/zbzbgo/gradle/gradle- 6.6. 1
export PATH=$PATH :$GRADLE_HOME/bin
5. 测试 Gradle 安装是否成功
运行 gradle --version,出现版本号则 Gradle 配置成功
6. 执行一次 Gradle 命令
学习新语言我们都喜欢来一次 hello world,这里我们也来一次。
随便创建一个文件夹,在其中创建一个文件,以.gradle结尾,使用 text 编辑器打开,输入:
println( "hello world!")
然后 gradle xxx.gradle 执行该文件
OK,成功,大家体验一下,groovy 是种语言,gradle 是种构建工具,可以编译 .gradle 文件。
5
Gradle Task
先剧透一下
Task 是 Gradle 执行的基本单元,为什么这么说?实际上根据 Gradle 构建项目的流程,是先把所有的 .gradle 脚本执行一遍,编译生成对应的 Gradle、Setting、Project 对象,然后根据我们在构建脚本中设置的构建配置,生成一张 Task 组成的:有向无环图,先来看看这张图
最终,Gradle 会按照图上一个个 Task 的关联顺序挨个执行,每一个 Task 都完成一个特定功能,按照顺序把 Task 图执行一遍,整个项目的构建任务就完成了 ┗|`O′|┛ 嗷~~
什么是 Task
Gradle中,每一个待编译的工程都叫一个Project。每一个Project在构建的时候都包含一系列的Task。比如一个Android APK的编译可能包含:Java源码编译Task、资源编译Task、JNI编译Task、lint检查Task、打包生成APK的Task、签名Task等。一个Project到底包含多少个Task,其实是由编译脚本指定的插件决定。插件是什么呢?插件就是用来定义Task,并具体执行这些Task的东西
Task 我们可以看成一个个任务,这个任务可以是编译 java 代码、编译 C/C++ 代码、编译资源文件生成对应的 R 文件、打包生成 jar、aar 库文件、签名、混淆、图片压缩、打包生成 APK 文件、包/资源发布等。不同类型的项目 Task 任务种类不同,Gradle 现在可以构建 java、android、web、lib 项目。
下图中这些都是一个个 Task 任务,每一个 Task 任务都是为了实现某个目的,可以理解为一个步奏,最终一个个步奏按序执行就完成了整个项目构建。
Task 是完成一类任务,实际上对应的也是一个对象。而 Task 是由无数个 Action 组成的,Action 代表的是一个个函数、方法,每个 Task 都是一堆 Action 按序组成的执行图,就好像我们在 Class 的 main 函数中按照逻辑调用一系列方法一样。
创建 Task
taskhello{
println"hello world"
}
task(hello2){
println"hello world2"
}
task ( 'hello3'){
println"hello world3"
}
运行 Task
命令行执行 gradle hello
Task 的 action
上面说过 Task 内部 action 也不是唯一的,而是一个集合,我们可以往里面添加各种 action,要注意 action 之间有前后执行顺序的,这个是规定好了的。这些 action 接收都是闭包,看下面 Task 中的声明
//在 Action 队列头部添加 Action
Task doFirst(Action superTask> action);
Task doFirst(Closure action);
//在 Action 队列尾部添加 Action
Task doLast(Action superTask> action);
Task doLast(Closure action);
//删除所有的 Action
Task deleteAllActions;
doFirst{...}、doLast{...} 接受的都是闭包
doFirst 添加的 action 最先执行
task 自身的 action 在中间执行,这个无法在 task 外部添加,可以在自定义 task 时写
doLast 添加的 action 最后执行
另外 task 也可以向对象那样操作,task 里面也可以写代码,比如打印 AA,但是这些代码只能在配置阶段执行,而 task 的 action 都是在运行阶段执行的。配置阶段和执行阶段不了解的看下面内容
task speak{
println( "This is AA!")
doFirst {
println( "This is doFirst!")
}
doLast {
println( "This is doLast!")
}
}
speak.doFirst {
println( "This is doFirst!")
}
action 可以添加多个的,按照添加顺序执行,比如 doLast 就可以添加多个进来
task speak{
println( "This is AA!")
doFirst {
println( "This is doFirst!")
}
doLast {
println( "This is doLast1...!")
}
}
speak.configure {
doLast {
println'his is doLast2...'
}
}
speak.doLast{
println'his is doLast3...'
}
动态创建 Task
4.times { counter ->
task "task $counter"{
doLast {
println "I'm task number $counter"
}
}
}
Task 之间共享数据
2个 Task 之间使用、操作同一个数据。早先在 .gradle 脚本中直接声明变量,Task 直接使用该变量的方式是不行的、会报错,需要借助 ext 全局变量。但在 6.6.1 我发现可以直接用变量了,应该是官方优化了
ext {
name = "AAA"
}
def age = 18
task s1 {
doLast {
age = 12
rootProject.ext.name = "BBB"
println( "This is s1...")
}
}
task s2 {
doLast {
println( "age --> "+ age)
println( "name --> "+ rootProject.ext.name)
println( "This is s2...")
}
}
Task 依赖
Task 依赖是指我们可以指定 Task 之间的执行顺序,重点理解 dependsOn 就行了
A.dependsOn B --> 执行A的时候先执行B
A.mustRunAfter B --> 同时执行 A/B,先执行B再执行A,若执行关系不成立报错
A.shouldRunAfter B --> 同 mustRunAfter,但是执行关系不成立不会报错
1. dependsOn -->
task s1{
doLast {
println( "This is s1...")
}
}
task s2{
doLast {
println( "This is s2...")
}
}
s1.dependsOn s2
-----------或者----------------
task s2{
dependsOn s1
doLast {
println( "This is s2...")
}
}
-----------dependsOn 还没声明出来的 task 要加 ""----------------
task s1{
dependsOn "s2"
doLast {
println( "This is s2...")
}
}
2. mustRunAfter -->
task s1{
doLast {
println( "This is s1...")
}
}
task s2{
doLast {
println( "This is s2...")
}
}
s1.mustRunAfter s2
自定义 Task
Gradle 中 Task 都是继承自 DefaultTask,我们自定义 Task 也需要继承这个类,重点是写自己需要的方法,然后加上@TaskAction注解表示这个方法是 Task 中的 action,可以加多个,按照倒序执行
classMyTaskextendsDefaultTask{
String message = "mytask..."
@TaskAction
defss1{
println( "This is MyTask --> action1!")
}
@TaskAction
defss2{
println( "This is MyTask --> action2!")
}
}
task speak( type:MyTask) {
println( "This is AA!")
doFirst {
println( "This is doFirst!")
}
doLast {
println( "This is doLast!")
}
}
系统默认 Task
gradle 默认提供了很多 task 给我们使用,比如 copy、delete
1. copy
copy 复制文件
task speak ( type: Copy) {
...
}
//数据源目录,多个目录
publicAbstractCopyTask from( Object... sourcePaths)
//目标目录,单一
publicAbstractCopyTask into( ObjectdestDir)
//过滤文件 包含
publicAbstractCopyTask include( String... includes)
//过滤文件 排除
publicAbstractCopyTask exclude( String... excludes)
//重新命名,老名字 新名字
publicAbstractCopyTask rename( StringsourceRegEx, StringreplaceWith)
//删除文件 Project 接口
boolean delete( Object... paths);
复制图片:多个数据源 -->
复制图片:多个数据源 -->
task copyImage( type: Copy){
from'C:Usersyiba_zyjDesktopgradlecopy',
'C:Usersyiba_zyjDesktopgradlecopy'
into'C:Usersyiba_zyjDesktop'
}
复制文件:过滤文件,重命名 -->
复制文件:过滤文件,重命名 -->
task copyImage( type: Copy){
from'C:Usersyiba_zyjDesktopgradlecopy'
into'C:Usersyiba_zyjDesktop'
include "*.jpg"
exclude "image1.jpg"
rename( "image2.jpg", "123.jpg")
}
2. Delete
删除文件
删除桌面上的文件 -->
task deleteFile(type: Delete) {
//删除系统桌面 delete
delete"C:Usersyiba_zyjDesktopgradledelete"
}
设置默认 Task 任务
设置这个的意思的是指,脚本中我们不调用该 task,设置的 task 也会执行
defaultTasks'clean', 'run'
task clean {
doLast{
println'Default Cleaning!'
}
}
task run {
doLast{
println'Default Running!'
}
}
task other {
doLast{
println"I'm not a default task!"
}
}
> gradle -q
DefaultCleaning!
DefaultRunning!
Task 中使用外部依赖
代码来自官网,build{...} 引入远程仓库和依赖 path,import 导入进来就可以用了
importorg.apache.commons.codec.binary.Base64
build {
// 导入仓库
repositories {
mavenCentral
}
// 添加具体依赖
dependencies {
classpath group: 'commons-codec', name: 'commons-codec', version: '1.2'
}
}
task encode {
doLast {
def byte[] encodedString = newBase64.encode( 'hello worldn'.getBytes)
printlnnewString(encodedString)
}
}
6
Gradle Plugin
理解什么是插件
我们写项目要使用大量的第三方代码,Gradle 作为构建工具,自然要拥有管理第三方代码的能力,要不怎么打包生成最终输出物。因为这些额外的代码要打入最终的生成物中,所以管理第三方代码的功能 Gradle 是必须要有的.
这些第三方代码有些是参与业务代码的,有些则是参与项目构建的:
参与项目构建的第三方代码叫 --> 插件
参与代码逻辑的第三方代码叫 --> 依赖
不管是插件,还是依赖,本质都是一堆类、函数,就是 API,区别是使用的地方不同。
Gradle是一个框架,作为框架,它负责定义流程和规则。而具体的编译工作则是通过插件的方式来完成的。比如编译Java有Java插件,编译Groovy有Groovy插件,编译Android APP有Android APP插件,编译Android Library有Android Library插件。Gradle中每一个待编译的工程都是一个Project,一个具体的编译过程是由一个一个的Task来定义和执行的。一个Project到底包含多少个Task,其实是由编译脚本指定的插件决定。插件是什么呢?插件就是用来定义Task,并具体执行这些Task的东西
插件的作用
Gradle 本身是一个通用的构建系统, 它并不知道你要编译的项目或代码是 Java 还是 C。Java 代码需要 javac 把 .java 编译为 .class,而 C 代码需要 gcc 把 .c 编译为 .o
在介绍 Task 的部分我们说过整个构建过程就是由一个个 Task 任务构成的,一个项目的构建包括很多步奏:代码编译、资源编译、依赖库打包等操作。
基于提取公共、抽象特异的模板思路,Gradle 作为通用项目构建工具,Gradle 封装的是项目构建的公共部分,而针对每种项目的特点和不同,就由每种项目对应的构建插件来接过差异部分的构建。
这些差异要是让 coder 我们自己来做,谁也记不住这么多不同不是,谁没事去背这个,因此插件就诞生了。
apply plugin: 'com.android.application' 是 Android 项目的构建插件。
apply plugin: 'com.android.library' 是 Android 依赖项目的构建插件。
这些插件封装了对应项目的整个构建流程,具体就是说这些插件内部已经定义好了Task有向无环图。
我们只需要在 .gradle 构建脚本中引入插件, 根据项目情况配置几个属性, 即可实现项目的构建。
当然为了完成某个功能,我们也可以把这些代码写成插件存入远程仓库中供大家使用~
Gradle 内置插件
Gradle 内置了很多插件, 比如 java 插件, build-scan 插件, 引入这种插件我们直接通过 Gradle 内置函数就可以, 不用指定 id 和 version
比如这样的就是内置插件:
repositories {
jcenter
}
google、jcenter 明显就是一个函数嘛 (○` 3′○) 我们有看到插件的 group:name:version 了吗,明显没有呀,那为什么我们还能引入这个插件呢?
答案就是 Gradle 已经帮我们写好这块啦 (>▽
gradle 内置插件可以看到都是插件仓库,当然仓库本身也是一种插件就是了,理解起来有点绕
导入插件
插入插件,我们先要导入仓库,也就声明从哪些仓库查找插件和远程依赖,然后再导入插件。
只要我们在根项目的 build.gradle 构建脚本中声明导入的插件,那么所有子项目就都可以使用该插件了
根项目 build.gradle 导入插件
build {
repositories {
}
dependencies {
classpath 'com.android.tools.build:gradle:3.5.0'
}
}
allprojects {
repositories {
jcenter
}
}
子项目使用插件
app build.gradle -->
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
android {
....
defaultConfig {
....
}
buildTypes {
....
}
}
dependencies {
....
}
其中几个 {...} 闭包解释下:
build {...} --> 声明仓库、添加插件专用闭包
repositories {...} --> 设置远程仓库
dependencies {...} --> 添加插件、远程依赖
apply --> 使用插件,插件虽然导入进来了,但是子项目用不用就是个事了,要是不用的话,是不需要打包进来的,所以要主动声明下
配置插件属性
这个是重点,务必要理解 (○` 3′○)
前文说过插件是参与项目构建的第三方代码集合,这些代码不光会参与整个项目构建,同样也需要用户输入一些配置参数,参数配置也是用闭包的方式传入。
插件内部会有对应的方法,接收这些在 build.gradle 脚本中配置的参数。给插件传递配置参数的闭包叫 DSL 代码段,最经典的场景就是 android {...} 了
apply plugin: 'com.android.application'
android {
compileSdkVersion(28)
defaultConfig {
....
}
}
android {...} 这个 DSL 可不是 Gradle 自身的配置,而是来源于 com.android.application 这个插件。大家只要明白这个后面再看 build.gradle 就明白多了,大家把 DSL 当做插件的 init 方法好了,本质上就是这么回事。
7
Gradle 构建过程
下面我说:Gradle 脚本的执行 --> 是指 gradle 编译那些 .gradle 脚本文件,生成对应的对象和 Task 任务
Gradle 的构建过程是通用的,任何由 Gradle 构建的项目都遵循这个过程。
Gradle 构建分为三个阶段,每个阶段都有自己的职责,每个阶段都完成一部分任务,前一阶段的成果是下一阶段继续执行的前提:
Initialization --> 初始化阶段。按顺序执行 init.gradle -> settings.gradle 脚本,生成 Gradle、Setting、Project 对象
Configuration --> 编译阶段,也叫配置阶段。按顺序执行 root build.gradle -> 子项目 build.gradle 脚本,生成 Task 执行流程图
Execution --> 执行阶段。按照 Task 执行图顺序运行每一个 Task,完成一个个步骤,生成最终 APK 文件
看图:
整个构建过程,官方在一些节点都设置有 hook 钩子函数,以便我们在构建阶段添加自己的逻辑进来,影响构建过程。hook 钩子函数可以理解为监听函数,另外也可以正儿八经的设置 Linsener 监听函数。
Initialization 阶段
Initialization 是初始化阶段,一上来会马上把全局的 Gradle 内置对象 new 出来,Gradle 对象中的参数都是本次 gradle 构建的全局属性,通过 Gradle 对象可以干很多事。
比如可以获取 Gradle Home、Gradle User Home 目录
添加全局监听
给所有项目添加设置、依赖等
Initialization 阶段会按照先后顺序运行2个 Groovy 脚本:
Init :创建内置对象 Gradle
Setting :创建内置对象 Setting、每个 module 对应的 Project 对象Configuration 阶段
Initialization 阶段后就是 Configuration 阶段了,Configuration 阶段会执行所有的 build.gradle 脚本,先执行根目录即根项目的构建脚本,再根据子项目之间的依赖关系,挨个执行子项目的构建脚本。
Gradle 中有根项目和子项目之分,不管是什么项目在 Gradle 中都对应一个 Project 对象。根项目只有一个,而子项目可以有多个,android 中即便是 app module 我们经常说的壳工程,其实也是一个子项目。
Gradle 默认的根项目是空的,没有内容的,跟项目只有 .gradle 有实际意义,其他都没用,不要在意。
Gradle 中每一个项目都必须有一个 .gradle 构建脚本,Configuration 阶段会执行所有项目的构建脚本,从跟项目开始一直到把所有参与本次构建的子项目构建脚本都执行完,在这个过程中,会根据 .gradle 构建脚本内容,创建对应的 Project 对象,在后面的环节,1个 Project 对象就对应着1个项目啦,rootProject 表示根项目,Project 表示子项目。
.gradle 脚本里的 DSL 配置块都是 Project 对象的方法而已
然后根据脚本中的配置,生成 Configuration 阶段的最终产物:Task 有向无环图,给下一阶段执行。Configuration 阶段的目的就是根据脚本配置计算出整个构建过程需要的逻辑和流程,可以理解为动态生成代码的过程。有了动态生成的代码,后面才好有东西执行不是 (^-^)
Execution 阶段
Execution 阶段没啥说的了,就是拿到 Configuration 阶段计算出来的 task 执行图,按照顺序一个个跑这些 task 就能完成构建了。
这是 Android 构建流程,其中每一个环节都可以认为是一个 Task(实际每个环节都有多个Task)
大家仔细体会上面这张图,不难的,挨个环节看,整个构建流程就是由这一个个细分的步骤组成的,至于有哪些步骤,哪些先开始,哪些最后执行,这就要看你在项目中导入的是什么插件了。
前文说过,Gradle 是通用构建工具,用于制定规则,详细的每种项目该怎么构建。过程是什么样的,插件负责的就是这些具体的内容、流程。返回搜狐,查看更多