前言
Gradle Wrapper
这个东西其实困扰了我很久,对这个东西一直没搞懂,只知道使用 gradlew
这个命令就可以实现 gradle 的功能,但是实际为什么要这样去操作呢?我还是一知半解的,今天花了点时间大概弄明白了为什么会有 Gradle Wrapper 的存在。
我们去百度搜索 gradle wrapper 相关博客时,一般都是这样说, gradle wrapper 可以在用户没有安装 gradle 的情况下去帮用户去下载 gradle ,然后使用 gradlew 就可以操作 gradle 进行项目的构建了。
然后,我还是没有搞明白。
gradle wrapper 组成&作用
一般情况下,我们使用 AndroidStudio 新建一个工程之后,都会以下几个文件
── gradle
── wrapper
── gradle-wrapper.jar
── gradle-wrapper.properties
── gradlew
── gradlew.bat
它们的作用如下:
gradlew 是 shell 脚本(Linux/Mac OS),是一个可执行文件,操作 gradle 命令(windows 是 gradlew.bat)
gradle-wrapper.properties 对 gradle 配置,例如 gradle 下载的地址等
gradle-wrapper.jar 内部都是用 class 字节码
gradlew 下载 gradle 的源代码就写在这里哦,底层是使用 java 的 HttpURLConnection 实现的,有兴趣可以去浏览一下
上面几个文件是 Andorid 开发者最最熟悉的了,但是一问 wrapper
是干嘛用的又不清楚~
到底 wrapper 是什么东西,我只知道新建一个 Android 工程 AS 就自动帮我创建了这些文件,我根本就不需要去搭理它。
然后我打开官方文档,看看 gradle wrapper 的描述
https://docs.gradle.org/current/userguide/gradle_wrapper.html#sec:adding_wrapper
按照文档的描述主要有以下几个步骤
gradle wrapper 的操作流程
假如有一个用户 A 他的电脑装了
Gradle
并且用Gradle
来构建 Android 项目,为了方便其它组员开发(统一 gradle 版本号),他通过 Gradle 命令生成了gradle wrapper
,然后通过 git 将刚才生成的gradle wrapper
上传到版本库进行管理。接下来另一个组员 B 他的电脑没有安装 Gradle ,但是呢,他又要去构建这个项目怎么办?这时
gradle wrapper
就起作用了, B 用户git pull 代码之后,他无需去安装 Gradle ,只需要通过./gradlew build
来构建即可。
这种情况是不是很常见?
接下来,来模拟一下这个流程
用户A开始操作
- 创建一个文件件
GradleDemo
(假设为用户 A 要开发的Android 项目
)
mkdir GradleDemo
cd GradleDemo
- 在终端执行
wrapper
这个任务,生成gradle wrapper
gradle wrapper --gradle-version 4.4 --distribution-type all
- 生成的内容如下
── gradle
── wrapper
── gradle-wrapper.jar
── gradle-wrapper.properties
── gradlew
── gradlew.bat
- 将以上文件通过 git 上传
用户B开始操作
从 git 中拉下用户 A 上传到的代码
操作 gradlew 命令
对于第二点,操作 gradlew 命令(例如: ./gradlew -v),它会有以下几个步骤:
Step 1. 从 gradle 服务器下载 distributions
首先它会检测本地是否有对应 gradle 版本,没有的话就去下载
那去哪里下载呢? 下面的 distributionUrl
就是 distributions 的下载地址,这里指定了要下载的版本为 4.4 ,类型为 all
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip
那你肯定会问,all
表示什么,其实通过 Step3
你就知道,all
表示下载的内容包括了 doc
,sample
,gradle源代码
,gradle class 编译文件
等,
如果你不需要那么多,那你可以将 all
修改为 bin
第一次执行 命令时,它会去下载你配置的 gradle 版本,可能需要翻墙....从下面的日志可以看到,下载的内容会解压到保存到本地目录了
➜ gradle ./gradlew -v
Downloading https://services.gradle.org/distributions/gradle-4.4-all.zip
.......................................................................................................................................................................................................................................................................................
//在这里解压下载回来的 gradle
Unzipping /Users/liaowj/.gradle/wrapper/dists/gradle-4.4-all/4mxuau4c77thx8zlvtz4xiez7/gradle-4.4-all.zip to /Users/liaowj/.gradle/wrapper/dists/gradle-4.4-all/4mxuau4c77thx8zlvtz4xiez7
Set executable permissions for: /Users/liaowj/.gradle/wrapper/dists/gradle-4.4-all/4mxuau4c77thx8zlvtz4xiez7/gradle-4.4/bin/gradle
------------------------------------------------------------
Gradle 4.4
------------------------------------------------------------
Build time: 2017-12-06 09:05:06 UTC
Revision: cf7821a6f79f8e2a598df21780e3ff7ce8db2b82
Groovy: 2.4.12
Ant: Apache Ant(TM) version 1.9.9 compiled on February 2 2017
JVM: 1.8.0_111 (Oracle Corporation 25.111-b14)
OS: Mac OS X 10.14 x86_64
Step 2. 保存起来,并且解压 gradle,保存到 Gradle User Home 中,一般是在 $USER_HOME/.gradle/wrapper/dists
例如我笔记本保存 gradle 的路径如下:
/Users/liaowj/.gradle/wrapper/dists/gradle-4.4-all/4mxuau4c77thx8zlvtz4xiez7/gradle-4.4
- step 3. 然后就可以愉快地使用 gradlew 了
gradle 组成
好了,来看看下载回来的gradle 文件吧~
使用 gradlew 去下载 gradle 之后是一个 zip 包,并且它会帮你解压~
下面就是 gradle-4.4-all.zip 解压的内容了
解压内容如下:
gradle-4.4
bin gradle 可执行文件
docs 当前 gradle 版本相关的文档都在这~
lib 里面问很多 jar 包,这些 jar 包都是 gradle 编译好的 class 代码
samples 是一些 gradle 的示例
src gradle 的源码就在这里啦
... 还有其它...
GRADLE_HOME&GRADLE_USER_HOME
你是不是被这两个东西是不是困扰的许久呢?
看了上面的分析后,gradlew 操作的第一步会将 gradle 下载到指定
的目录下
但是我们会有疑问,这个目录能不能我们开发者来指定呢?
先来看看默认的存储目录:
/Users/liaowj/.gradle/wrapper/dists/
我记得 gradlew 是根据 gradle-wrapper.properties
配置文件来决定去哪里下载 gradle 的,那按道理应该会告诉 gradlew 下载之后保存到哪里吧?
#Sat Aug 24 13:13:34 CST 2019
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip
推敲:从上面的配置信息再结合默认的存储目录可以看出,gradle 下载的路径其实就是
distributionBase/distributionPath
也等价于 GRADLE_USER_HOME/wrapper/dists
那就可以推出 GRADLE_USER_HOME
就是 /Users/liaowj/.gradle/
啦
不信?那从代码来验证一下:
在 build.gradle 中写如下代码:
project.getGradle().gradleHomeDir
project.getGradle().gradleUserHomeDir
执行以下命令:
➜ GradleDemo ./gradlew clean
Starting a Gradle Daemon (subsequent builds will be faster)
<-----
> Configure project :
/Users/liaowj/.gradle/wrapper/dists/gradle-4.4-all/9br9xq1tocpiv8o6njlyu5op1/gradle-4.4
/Users/liaowj/.gradle
那意思就说,我们只要手动去修改 GRADLE_USER_HOME 这个值,那么就可以修改 gradle 的存储目录咯~
那如何去修改呢?跟进去源码可以发现, gradleUserHome 是通过读取环境变量"GRADLE_USER_HOME"来设置
gradleUserHome = System.getenv("GRADLE_USER_HOME");
所以我们只需要在环境变量
中去设置 GRADLE_USER_HOME
就好了
打开vim ~/.bash_profile
添加以下配置信息(我在 Mac OS 平台哈~)
GRADLE_USER_HOME=~/Document/.gradle
export GRADLE_USER_HOME
然后我们来验证一下
从下面的输出日志来看,我们已经修改成功了,我们将
/Users/liaowj/.gradle
修改为了/Users/liaowj/Document/.gradle
➜ GradleDemo ./gradlew clean
Starting a Gradle Daemon (subsequent builds will be faster)
<-----
//因为从新配置了新的存储目录,这里会去下载gradle....
> Configure project :
/Users/liaowj/Document/.gradle/wrapper/dists/gradle-4.4-all/9br9xq1tocpiv8o6njlyu5op1/gradle-4.4
/Users/liaowj/Document/.gradle
对了,上面的输出日志中,还有一个 project.getGradle().gradleHomeDir
这个又是干嘛的?
这个呢,其实就是你实际下载回来的 gradle 的实际目录了,例如我们工程中 wrapper 中配置的是 4.4 版本那么它的路径如下
/Users/liaowj/Document/.gradle/wrapper/dists/gradle-4.4-all/9br9xq1tocpiv8o6njlyu5op1/gradle-4.4
那另一个工程中配置的是 3.5 版本那么它的路径如下:
/Users/liaowj/Document/.gradle/wrapper/dists/gradle-3.5-all/9br9xq1tocpiv8o6njlyu5op1/gradle-3.5
也就是说,/Users/liaowj/Document/.gradle
是固定
的,后面的就是根据 gradle-wrapper 中的配置然后存储在不同的文件夹下了。
所以从这里就看出为什么 Gradle 官方推荐我们使用 gradle wrapper 去构建工程项目了吧~
Gradle 版本和插件版本
classpath 'com.android.tools.build:gradle:3.4.2'
我之前一直没搞明白这两者的关系,到底哪个插件版本对应使用哪个 Gradle 版本呢?
下面是在 Android Gradle Plugin官网 看到的,日后不懂哪个版本对哪个版本就看看这个吧~
Gradle Plugin 简单理解就是将一些公共的功能抽取出来成为插件,增强复用性,插件是基于 gradle 语言去开发的,之所以出现插件版本不匹配对应的 gradle 版本就是因为这个插件使用该 Gradle 版本不具备的 api ,那么你要去升级到对应的版本了,具体就看看上面的表格。
有个 bug, 记录一下~
有一天,我在控制台输入以下命令,它报错了
> gradle build
* What went wrong:
A problem occurred evaluating root project 'GradleDemo'.
> Could not find method google() for arguments [] on repository container of type org.gradle.api.internal.artifacts.dsl.DefaultRepositoryHandler.
* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output.
上面这个这个错误,想必你应该遇过吧~
在 stackoverflow 可以找到答案,这个错误是你使用的 gradle 版本低于 Gradle 4.x+,所以就会有这个问题呀。
但是我的 gradle-wrapper.properties 是已经是使用了 4.4 版本了啊,为啥还...
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStoreath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip
那问题的根源在哪里?
问题是出现在我在本地的环境变量中配置了 gradle ,并且版本号为 gradle-3.5。
GRADLE_HOME=/Users/liaowj/Library/Android/gradle/gradle-3.5
export PATH=$PATH:$GRADLE_HOME/bin
所以我在终端使用 gradle build
的时候使用的是 gradle-3.5
版本,而不是在项目工程下的gradle-wrapper
配置的那个版本号。
其实要验证这个问题也很简单,只要执行 gradle -v 就知道当前的 gradle 版本了
➜ GradleDemo gradle -v
------------------------------------------------------------
Gradle 3.5
------------------------------------------------------------
所以,你需要用 gradlew 去构建,那你如果不想修改环境变量,那么你就要去使用
./gradlew build
通过 gradlew 才是真正去用的是 gradle-wrapper 的指定的版本号。
➜ GradleDemo ./gradlew -v
------------------------------------------------------------
Gradle 4.4
------------------------------------------------------------
结论:在工程项目中,尽量使用 gradlew 来构建(Gradle 官方就是这样推荐的~),这样它会跟你项目配置的版本会是一样的,如果你跟我一样项目用的版本跟环境变量配置的 gradle 版本不一样的话就会这种问题啦~
本文是笔者学习之后的总结,方便日后查看学习,有任何不对的地方请指正。
记录于 2019年8月24号