概述

Gradle是一个基于Apache AntApache 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语言,groovyjava都是基于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的构建过程分为三部分: 初始化阶段、配置阶段 和 执行阶段。其构建流程如下图所示:

配置gradle国内镜像源_android

初始化阶段

在这个阶段中,会读取根工程中的 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. 而在配置阶段执行的代码通常来说都会包括以下三个部分的内容,如下所示:

  1. build.gralde 中的各种语句。
  2. 闭包。
  3. 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方法,控制是否传递依赖。configurationsproject中方法,他们都是类Configuration中的方法。projectconfigurations方法。

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 projectbuild 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标签下,我们可以看到。descriptiontask添加描述,相当于task的注释,在打印task时会展示出来(如下图)。

配置gradle国内镜像源_配置gradle国内镜像源_02

// 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打包多个变体