概述
Gradle
是一个基于Apache Ant
和Apache Maven
概念的项目自动化建构工具。它使用一种基于Groovy
的特定领域语言来声明项目设置,而不是传统的XML。当前其支持的语言限于Java、Groovy和Scala,计划未来将支持更多的语言。
Gradle可以做哪些事呢
差异管理
多渠道打包,根据渠道的不同实现差异化(例如,不同的签名文件,不同的icon,不同的服务器地址)等。
依赖管理
我们的应用可以依赖不同的jar
, library
. 你当然可以通过将.jar/library工程下载到本地再copy到你的工程中。但是随着依赖增加还有各种更新,维护起来复杂性可想而知。有一个中央仓库的东西,在这个仓库里你可以找到所有你能想要的依赖包。而你所做的只需要指定一下坐标即可。例如:implementation'com.squareup.picasso:picasso:2.3.3'
通过这种方式,我们可以很方便的实现依赖的装载卸载。
常用的依赖方式有:implementation
api
compileOnly
这几种区别:传送门 在依赖管理中,有时需要根据不同的环境,引入不同aar
包,可以参考 传送门项目部署
自动将你的输出(.jar,.apk,.war…)上传到指定仓库,自动部署…
Gradle学习
学习Gradle
,需要学习,groovy
语言,Gradle DSL
学习,Android Plugin DSL
学习,Gradle task
学习。
groovy语言
Gradle
基于groovy
语言,groovy
与java
都是基于jvm
,理解相对容易些,而且日常开发不用学习那么多 教程。
Gradle DSL
常用的 build.gradle
, Gradle DSL 传送门
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.3.0'
classpath 'com.novoda:bintray-release:0.4.0'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
jcenter()
maven {url 'https://dl.bintray.com/calvinning/maven'}
}
}
那么buildscript
中的 repositories 和allprojects
的 repositories 的作用和区别是什么呢?
1、 buildscript
里是gradle脚本执行所需依赖,分别是对应的maven库和插件
2、 allprojects
里是项目本身需要的依赖,比如我现在要依赖我自己maven库的toastutils库,那么我应该将maven {url 'https://dl.bintray.com/calvinning/maven'}
写在这里,而不是buildscript中,不然找不到。
ext
属性
ext
定义
ext {
compileSdkVersion = 25
buildToolsVersion = "26.0.0"
minSdkVersion = 14
targetSdkVersion = 22
appcompatV7 = "com.android.support:appcompat-v7:$androidSupportVersion"
//建了一个map,且名字叫做android。
android = [
compileSdkVersion: 23,
buildToolsVersion: "23.0.2",
minSdkVersion : 14,
targetSdkVersion : 22,
]
}
根据ext
属性的官方文档,ext属性是ExtensionAware
类型的一个特殊的属性,本质是一个Map类型的变量,ExtentionAware
接口的实现类为Project, Settings, Task, SourceSet等,ExtentionAware
可以在运行时扩充属性,而这里的ext,就是里面的一个特殊的属性而已。
ext
使用
访问变量 通过rootProject.ext.compileSdkVersion
方式。对于数组类型rootProject.ext.android[compileSdkVersion]
这种方式。
使用ext
属性的优势。ext
属性可以伴随对应的ExtensionAware对象在构建的过程中被其他对象访问,例如你在rootProject
中声明的ext
中添加的内容,就可以在任何能获取到rootProject
的地方访问这些属性,而如果只在rootProject/build.gradle
中用def
来声明这些变量,那么这些变量除了在这个文件里面访问之外,其他任何地方都没办法访问。
注意,如果ext是自己定义的gradle文件,如(denpendies.gradle, config.gradle)需要自己手动导入,可以在根‘build.gradle’的buildscript中添加,
apply from: 'dependencies.gradle'
gradle.properties
文件
详细介绍,传送门gradle.properties
里面定义的属性是全局的,可以在各个模块的build.gradle
里面直接引用. 且可以在java
代码中访问。
注意:在gradle.properties中定义的属性默认是
String
类型的,如果需要int
类型,需要添加XXX as int
后缀。
第一步,在gradle.properties 定义,如下:
# Java 代码使用
DEFAULT_NICK_NAME=maolegemi
DEFAULT_NUMBER=10086
# xml文件调用
USER_NAME=wangdandan
TEXT_SIZE=20sp
TEXT_COLOR=#ef5350
第二步, 在build.gradle
中配置
android {
...
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
debug{
// Java代码调用, 就会在BuildConfig.java 生成文件,注入这边变量,见下面java使用
buildConfigField "String", "defaultNickName", DEFAULT_NICK_NAME
buildConfigField "Integer", "defaultNumber", DEFAULT_NUMBER
// xml布局文件调用,就会在gradleResValues.xml 注入这些变量。在代码中,就可以访问
resValue "string", "user_name", "${USER_NAME}"
resValue "dimen", "text_size", "${TEXT_SIZE}"
resValue "color", "text_color", "${TEXT_COLOR}"
}
}
}
第三步,在代码中使用(引用 BuildConfig.java 和 gradleResValues.xml)
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
System.out.println("defaultNickName:" + BuildConfig.defaultNickName);
System.out.println("defaultNickName.type:" + BuildConfig.defaultNickName.getClass().getSimpleName());
}
gradle.properties
定义的变量(值均为String
,其他类型需要转换),可以在gradle
文件中,直接使用。
// 将变量转化为boolean
isSingleRun.toBoolean()
// 将变量转化为int
buildCount as int
Android Plugin DSL
常用的build.gradle
, Android Plugin DSL传送门
apply plugin: 'com.android.application'
android {
compileSdkVersion 27
defaultConfig {
applicationId "com.joker.cliptest"
minSdkVersion 21
// ...
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support:appcompat-v7:27.1.1'
// 导入包,但移除 'com.github.bumptech.glide'
implementation(rootProject.ext.GlideTransformations) {
exclude group: 'com.github.bumptech.glide'
}
// ...
}
在android闭包下,也可以指定代码目录。
sourceSets {
main {
// 指定manifest的目录
manifest.srcFile 'src/xxx/AndroidManifest.xml'
// 指定java的目录
java.srcDirs = ['src/main/xxx/java', 'src/main/java', 'src/single/java']
//指定资源文件目录
res.srcDirs = ["src/main/res", "src/main/res-play", "src/main/res-shop"]
// 指定so库的存放位置
jniLibs.srcDirs = ["libs"]
}
}
在android.defaultConfig
闭包下,可以指定 manifestPlaceholders
来动态替换 AndroidManifest.xml
中变量。参考传送门。
多渠道打包productFlavors
也是android plugin dsl的内容,关于多渠道打包,这里不单独介绍,看这篇内容就可以了。传送门
//多渠道打包,在此配置
productFlavors {
xiaomi {
resValue("string", "app_name", "小米ktLearn")
}
huawei {
resValue("string", "app_name", "华为ktLearn")
}
}
打包并上传到maven
仓库
是maven
插件的闭包。需要导入maven
插件。
apply plugin: 'maven'
uploadArchives {
def NEXUS_URL = "http://xxx"
configuration = configurations.archives
repositories {
mavenDeployer {
pom.project {
pom.groupId = GROUP_ID
pom.artifactId = ARTIFACT_ID
pom.version = VERSION
pom.packaging = 'aar'
}
repository(url: NEXUS_URL) {
authentication(userName: getRepositoryUsername(), password: getRepositoryPassword())
}
}
}
}
Gradle 构建生命周期
Gradle
的构建过程分为三部分: 初始化阶段、配置阶段 和 执行阶段。其构建流程如下图所示:
初始化阶段
在这个阶段中,会读取根工程中的 setting.gradle 中的 include 信息,确定有多少工程加入构建,然后,会为每一个项目(build.gradle 脚本文件)创建一个个与之对应的 Project 实例,最终形成一个项目的层次结构。
此外,我们也可以在settings.gradle
文件中,指定其他project的位置,这样就可以将其他外部工程的module导入到当前的工程之中了。
if(useLocal) {
File projectFile = new File('../picBook/app')
include ':picBook'
project(':picBook').projectDir = projectFile
}
配置阶段
执行各项目下的 build.gradle 脚本,完成 Project 的配置,与此同时,会构造 Task 任务依赖关系图以便在执行阶段按照依赖关系执行 Task. 而在配置阶段执行的代码通常来说都会包括以下三个部分的内容,如下所示:
- build.gralde 中的各种语句。
- 闭包。
- Task 中的配置段语句。
注意,执行任何 Gradle 命令,在初始化阶段和配置阶段的代码都会被执行。
执行阶段
Gradle 根据各个任务 Task 的依赖关系来创建一个有向无环图。Gradle 构建系统通过调用 gradle <任务名> 来执行相应的各个任务。
Project
Project API
对应org.gradle.api
包下,Project.java
的方法。
getAllprojects
获取当前工程下所有的project
实例(含当前工程), 在不同的build.gradle
下调用返回不一样。在根build.gradle
下会返回所有的project
.getSubprojects
获取当前工程下所有子工程的project
实例,返回的set
除了不包含当前工程,其他同getAllprojects
.getParent
获取当前project
的父类.如果我们在根工程中使用它,返回为null
.getRootProject
返回根工程的project
实例。project
指定工程的实例。有几种重载方法。
我们常用的
implementation project(':xbase')
还有一种加闭包的重载,可对工程进行配置。
//扩展ext方法
ext.getRealDep = { projectName ->
def aarName = rootProject.ext.project[projectName]
if (aarName) {
//如果在ext.project 配置了,使用配置的仓库路径
println "\n use aar [$projectName] "
return aarName
} else {
//没有在ext.project 配置,则使用当前路径的工程
Project project = project(":" + projectName)
if (project != null && project.getProjectDir() != null
&& project.getProjectDir().exists()) {
println "\nready to handle project [$projectName] real dependency"
return project
}
}
throw IllegalArgumentException("no project found for :$projectName")
}
allprojects
用于配置当前project
及其旗下的每一个子project
.
allprojects {
repositories {
maven { url "https://jitpack.io" }
maven { url 'https://repo.rdc.aliyun.com/repository/xxxx/'
//一般私有仓库,需要验证用户密码这样设置
credentials {
username 'namexxx'
password 'pwdxxx'
}
}
maven { url 'https://dl.bintray.com/umsdk/release' }
google()
jcenter()
}
}
subprojects
统一配置当前 project 下的所有子 project
subprojects {
//当前 project 旗下的子 project 是不是库,如果是库才有必要引入 publishToMaven 脚本
if (project.plugins.hasPlugin("com.android.library")) {
apply from: '../publishToMaven.gradle'
}
}
project属性
在project.java
接口中,仅仅定义了七个属性
public interface Project extends Comparable<Project>, 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
关键字让我们有能力去定义自身所需要的扩展属性。有了它便可以对我们工程中的依赖进行全局配置。
见上面ext使用
和gradle.properties
下定义扩展属性。
文件相关的API
获取路径
//getRootDir返回file指向根目录
getRootDir().absolutePath
//getBuildDir返回file指向 '根目录/build'
getBuildDir().absolutePath
//getProjectDir返回file指向 当前工程目录, 如'根目录/xlist'
getProjectDir().absolutePath
文件相关
//返回一个File
def mFile = file(pathxxx)
// 文件的拷贝
copy {
from file("build/outputs/apk")
into getRootProject().getBuildDir().path + "/apk/"
exclude {
//排除不需要拷贝的文件
}
rename {
//对拷贝过来的文件进行重命名
}
}
//fileTree方法
// 用法1
implementation fileTree(dir: 'libs', include: ['*.jar'])
// 用法2
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/"
}
}
}
其他
关于app module下的依赖,除了可以添加依赖,也可以移除, 需要用到 exclude
,另外还有一个transive
方法,控制是否传递依赖。configurations
是project
中方法,他们都是类Configuration
中的方法。project
有configurations
方法。
if (local_reader.toBoolean())
configurations {
implementation {
exclude group: 'com.tdhLearn.base', module: 'core'
exclude group: 'com.tdhLearn.base', module: 'push'
transitive false
}
}
exec
执行外部命令。
Task
Gradle task
与我们是最为紧密的。日常开发中开发者难免会进行 build/clean project
、build apk
等操作。实际上这些按钮的底层实现都是通过 Gradle task
来完成的,只不过 IDE
使用 GUI
降低开发者们的使用门槛。
task的使用
我们可以在任意一个build.gradle
文件中定义Task
task tian {
//这段代码执行在配置阶段
println "exec tian task"
//doFirst, doLast执行在 gradle执行阶段
doFirst {
println "~~~tian task start"
}
doLast {
println "~~~tian task end"
}
}
// 任务依赖 tian
task fang(dependsOn:"tian") {
doLast {
println "~~~fang task end"
}
}
执行
# 根目录下的task
./gradlew task :tian
# 在其他module下的task
./gradlew task :app:tianThird
Task的属性
task
组的概念,会对task
进行分组,在gradle
标签下,我们可以看到。description
为task
添加描述,相当于task
的注释,在打印task
时会展示出来(如下图)。
// group定义组,description为task添加描述。
task tianSec(group: "TianTask", description: "this is my test task") {
println "~~~tianSec configure"
}
属性扩展
我们也可以 使用 ext 给 task 自定义需要的属性
task tian(group: "TianTask") {
//扩展task的属性
ext.good = "hello tian"
}
//定义 组&描述
task tianSec(group: "TianTask") {
doLast {
//访问tian的扩展属性
println "~~~I can get goodValue $tian.good"
}
}
task类型
使用type属性来直接使用一个已有的task
类型。
// 删除根目录下的build文件
task clean(type: Delete) {
delete rootProject.buildDir
}
// 将doc复制到 build/target目录下
task copyDocs(type: Copy) {
form 'src/main/doc'
into 'build/target/doc'
}
// 执行时复制源文件到目标目录,然后从目标目录删除所有非复制文件
task syncFile(type: Sync) {
from 'src/mian/doc'
into 'build/target/doc'
}
每个 task 都会经历 初始化、配置、执行 这一套完整的生命周期流程。
参考资料
Android 开发者的 Gradle 系列Android多渠道打包深度探索Gradle自动化构建技术android gradle依赖:implementation 和compile的区别使用gradle打包多个变体