一、gradle是什么
1、gradle是一个自动化构建工具。
gradle是通过组织一系列task来最终完成自动化构建的,所以task是gradle里最重要的概念。以生成一个可用的apk为例,整个过程要经过资源的处理,javac编译,dex打包,apk打包,签名等等步骤,每个步骤就对应到gradle里的一个task。
2、gradle使用groovy或者kotlin编写。
groovy是DSL。那么什么是DSL?DSL也就是Domain Specific Language的简称,也就是领域特定语言,是为了解决某一类任务专门设计的计算机语言。DSL使用简单,定义比较简洁。
3、无论是基于groovy还是kotlin编写,均是基于jvm的语言。
因此本质上都是面对对象的,面向对象的特点是一切皆为对象,所以,在gradle里,.gradle脚本的本质就是类的定义,一些配置项的本质都是方法调用,参数是后面的{}闭包。比如build.gradle对应Project类,buildScript对应Project.buildScript方法。
二、gradle项目分析
首先看一下gradle的项目层次,下面是一张示例图:
可结合gradle打包流程(一)进行查看。
1、settings.gradle
settings.gradle是负责配置项目的脚本。
对应Settings类,gradle构建过程中,会根据settings.gradle生成Settings的对象,其中有几个主要的方法:
- include(projectPaths)
- includeFlat(projectNames)
- project(projectDir)
一般在项目里见到的引用子项目模块的方法,就是使用include,这样引用,子模块位于根目录下一级
include ':app'
如果是多个项目,则类似于
include ':app', ':library'
如果想指定子模块的位置,可以使用project方法去获取Project对象,设置其projectDir参数
include ':app'
project(':app').projectDir = new File('./app')
2、rootproject/build.gradle
build.gradle负责整体项目的一些配置,对应的是Project类。
gradle构建的时候,会根据build.gradle生成Project对象,所以在build.gralde里写的DSL,其实都是Project接口的一些方法,Project其实是一个接口,真正的实现类是DefaultProject。Project其中有几个主要的方法:
- buildscript //配置脚本的classpath
- allprojects //配置项目及其子项目
- respositories //配置仓库地址,后面的依赖都会去这里配置的地址查找
- dependencies //配置项目的依赖
接上面的介绍gradle项目层级的示例图对应的项目UI举例,其rootproject/build.gradle的内容如下:
// Top-level build file where you can add configuration options common to all sub-projects/modules.
apply from:"config.gradle" //引入config.gradle
buildscript { //配置项目的classpath
repositories { //项目的仓库地址,会按顺序依次查找
google()
jcenter()
}
dependencies { //项目的依赖
classpath "com.android.tools.build:gradle:4.1.2"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects { //子项目的配置
repositories {
google()
jcenter()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
3、module/build.gradle
build.gradle是模块的配置,对应的也是Project类。
作为模块级build.gradle与顶层build.gradle相比,是差不多的,但是我们也可以看到一个明显的区别,就是在模块级build.gradle中,引用了一个插件 apply plugin: 'com.android.application'或者apply plugin: 'com.android.library'或者apply plugin: 'java-library' 等等。比如说apply plugin: 'com.android.application',后面的android dsl就是application插件的extension。
其中有几个主要方法:
- compileSdkVersion //编译sdk版本,也就是指定gradle编译应用时用的Android API级别
- defaultConfig //默认的配置
- buildTypes //编译类型,一般有debug和release
- productFlavor //产品变种
我们以项目UI中的app module的build.gradle来看,
plugins {
id 'com.android.application'
} //引入android gradle插件
android { //配置android gradle plugin需要的内容
compileSdkVersion 30
buildToolsVersion "30.0.3"
defaultConfig { //默认配置
applicationId "com.zdj.uidemo"
minSdkVersion 23
targetSdkVersion 30
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes { //编译类型
release { //release版本
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions { //指定java版本
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
dependencies { //模块需要的依赖
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'com.google.android.material:material:1.1.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'androidx.navigation:navigation-fragment:2.2.2'
implementation 'androidx.navigation:navigation-ui:2.2.2'
implementation 'androidx.recyclerview:recyclerview:1.1.0'
testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
if (rootProject.ext.isModule.toBoolean()) {
implementation project(':phone')
} else {
implementation project(':zdjuilibrary')
}
}
4、依赖
新配置 | 弃用配置 | 行为 | 作用 |
implementation | compile | 依赖项在编译时对模块可用,并且仅在运行时对模块的消费者可用。对于大型多项目构建,使用implementation而不是api/compile可以显著缩短构建时间,因为它可以减少构建系统需要重新编译的项目量。大多数应用和测试模块都应使用此配置。 | api只会暴露给直接依赖的模块,使用此配置,在模块修改以后,只会重新编译直接依赖的模块,间接依赖的模块不需要改动。 |
api | compile | 依赖项在编译时对模块可用,并且在编译时和运行时还对模块的消费者可用。此配置的行为类似于compile(现在已弃用),一般情况下,应当仅在库模块中使用它。应用模块应使用implementation,除非想要将其API公开给单独的测试模块。 | api会暴露给间接依赖的模块,使用此配置,在模块修改以后,模块的直接依赖和间接依赖的模块都需要重新编译。 |
compileOnly | provided | 依赖项仅在编译时对模块可用,并且在编译或运行时对其消费者不可用。此配置的行为类似于provided(现在已弃用)。 | 只在编译期间依赖模块,打包以后运行时不会依赖,可以用来解决一些库冲突的问题。 |
runtimeOnly | apk | 依赖项仅在运行时对模块及其消费者可用。此配置的行为类似于apk(现在已弃用)。 | 只在运行时依赖模块,编译时不依赖。 |
5、flavor
几个概念:flavor,dimension,variant。
在android gradle plugin 3.x之后,每个flavor必须对应一个dimension,可以理解为flavor的分组,然后不同dimension里的flavor两两组和形成一个variant。
举例如下:
flavorDimensions "size", "color"
productFlavors {
big {
dimension "size"
}
small {
dimension "size"
}
red {
dimension "color"
}
green {
dimension "color"
}
}
那么生成的variant对应的就是bigRed,bigGreen,smallRed,smallGreen。
每个variant可以对应的使用variantImplementation来引入特定的依赖,比如:
bigRedImplementation,只有在编译bigRed variant的时候才会引入。
三、gradle wrapper
gradlew/gradlew.bat这个文件用来下载特定版本的gradle然后执行的,就不需要开发者在本地再安装gradle了。这样做的好处是:开发者在本地安装gradle,会碰到不同项目使用不同版本的gradle无法处理的问题,那么使用wrapper就很好地解决了这个问题。可以在不同项目里使用不同的gradle版本。
gradle/wrapper/gradle-wrapper.properties 是一些gradlewrapper的配置,其中用的比较多的就是distributionUrl,可以执行gradle的下载地址和版本。
gradle/wrapper/gradle-wrapper.jar 是gradlewrapper运行需要的依赖包。
四、gradle生命周期及回调
gradle构建分为三个阶段:
初始化阶段、配置阶段、执行阶段。
初始化阶段:这个阶段主要做的事情是有哪些项目需要被构建,然后为对应的项目创建Project对象。
配置阶段:这个阶段主要做的事情是对上一步创建的项目进行配置,这时候会执行build.gralde脚本,并且会生成要执行的task。
执行阶段:这个阶段主要做的事情就是执行task,进行主要的构建工作。
gradle在构建过程中,会提供一些回调接口,方便在不同的阶段做一些事情,主要的接口有下面几个:
gradle.addBuildListener(new BuildListener() {
@Override
void buildStarted(Gradle gradle) {
println('构建开始')
// 这个回调一般不会调用,因为我们注册的时机太晚,注册的时候构建已经开始了,是 gradle 内部使用的
}
@Override
void settingsEvaluated(Settings settings) {
println('settings 文件解析完成')
}
@Override
void projectsLoaded(Gradle gradle) {
println('项目加载完成')
gradle.rootProject.subprojects.each { pro ->
pro.beforeEvaluate {
println("${pro.name} 项目配置之前调用")
}
pro.afterEvaluate{
println("${pro.name} 项目配置之后调用")
}
}
}
@Override
void projectsEvaluated(Gradle gradle) {
println('项目解析完成')
}
@Override
void buildFinished(BuildResult result) {
println('构建完成')
}
})
gradle.taskGraph.whenReady {
println("task 图构建完成")
}
gradle.taskGraph.beforeTask {
println("每个 task 执行前会调这个接口")
}
gradle.taskGraph.afterTask {
println("每个 task 执行完成会调这个接口")
}
五、自定义task
默认创建task,继承自DefaultTask。
task myTask {
println 'myTask in configuration'
doLast {
println 'myTask in run'
}
}
class MyTask extends DefaultTask {
@Input Boolean myInputs
@Output
@TaskAction
void start() {
}
}
tasks.create("mytask").doLast {
}
Task的一些重要方法分类如下:
- Task行为:Task.doFirst, Task.doLast
- Task依赖顺序:Task.dependsOn, Task.mustRunAfter, Task.shouldRunAfter, Task.finalizedBy
- Task的分组描述:Task.group, Task.decription
- Task是否可用:Task.enabled
- Task输入输出:Task.inputs, Task.outputs, gradle会比较task的inputs和outputs来决定task是否是最新的,如果inputs和outputs没有变化,则认为task是最新的,task就会跳出不执行。
- Task是否执行:可以通过指定Task.upToDateWhen = false来强制task执行Task.upToDateWhen
比如说我们要指定Task之间的依赖顺序,可以这样写:
task task1 {
doLast {
println('task2')
}
}
task task2 {
doLast {
println('task2')
}
}
task1.finalizedBy(task2)
task1.dependsOn(task2)
task1.mustRunAfter(task2)
task1.shouldRunAfter(task2)
task1.finalizedBy(task2)
六、Android Transform
android gradle plugin 提供了transform api用来在class文件转成dex过程中对class进行处理,可以理解为一种特殊的Task,因为transform最终也会转化为Task去执行。
要实现transform需要继承com.android.build.api.tranform.Transform并实现其方法,实现了Transform以后,要想应用,就调用project.android.registerTransform()
public class MyTransform extends Transform {
@Override
public String getName() {
// 返回transform的名称,最终的名称会是transformClassesWithMyTransformForDebug这种形式
return "MyTransform";
}
@Override
public Set<QualifiedContent.ContentType> getInputTypes() {
/**
返回需要处理的数据类型,有下面几种类型可选
public static final Set<ContentType> CONTENT_CLASS = ImmutableSet.of(CLASSES);
public static final Set<ContentType> CONTENT_JARS = ImmutableSet.of(CLASSES, RESOURCES);
public static final Set<ContentType> CONTENT_RESOURCES = ImmutableSet.of(RESOURCES);
public static final Set<ContentType> CONTENT_NATIVE_LIBS = ImmutableSet.of(NATIVE_LIBS);
public static final Set<ContentType> CONTENT_DEX = ImmutableSet.of(ExtendedContentType.DEX);
public static final Set<ContentType> DATA_BINDING_ARTIFACT = ImmutableSet.of(ExtendedContentType.DATA_BINDING);
*/
return TransformManager.CONTENT_CLASS;
}
@Override
public Set<? super QualifiedContent.Scope> getScopes() {
/**
返回需要处理内容的范围,有下面几种类型
PRIOJECT(1),只处理项目的内容
SUB_PROJECTS(4),只处理子项目
EXTERNAL_LIBRARIES(16),只处理外部库
TESTED_CODE(32),只处理当前variant对应的测试代码
PROVIDED_ONLY(64),处理依赖
@Deprecated
PROJECT_LOCAL_DEPS(2)
@Deprecated
SUB_PROJECTS_LOCAL_DEPS(8);
*/
return Sets.immutableEnumSet(QualifiedContent.Scope.PROJECT);
}
@Override
public boolean isIncremental() {
//是否增量,如果返回true,TransformInput会包括一份修改的文件列表,返回false,会进行全量编译,删除上一次的输出内容
return false;
}
@Override
void transform(TransformInvocation transformInvocation) throws TransformException, InterruptedException, IOException {
//在这里处理class
super.transform(transformInvocation)
//在transform里,如果没有任何修改,也要把input的内容输出到output,否则会报错
for (TransformInput input : transformInvocation.inputs) {
input.directoryInputs.each { dir ->
//获取对应的输出目录
File output = transformInvocation.outputProvider.getContentLocation(dir.name, dir.contentTypes, dir.scopes, Format.DIRECTORY)
dir.changedFiles //增量模式下修改的文件
dir.file //获取输入的目录
FileUtils.copyDirectory(dir.file, output) //input内容输出到output
}
input.jarInputs.each { jar ->
//获取对应的输出jar
File output = transformInvocation.outputProvider.getContentLocation(jar.name, jar.contentTypes, jar.scopes, Format.JAR)
jar.file //获取输入的jar文件
FileUtils.copyFile(jar.file, output) //input内容输出到output
}
}
}
}
//注册transform
android.registerTransform(new MyTransform())
在transform中的处理,一般会涉及到class文件到修改,操纵字节码的工具一般是javasist和asm居多。
七、手写plugin
gradle的插件可以看作是一系列task的集合。
在android工程的app module/build.gradle脚本里,第一行就是apply plugin: 'com.android.application' ,这个就是引入android gradle插件,插件里有android打包相关的task。
那么我们现在看看,如何实现一个自己的plugin呢?
1、初始化工程
(1)、在android studio中创建一个java module
(2)、在src/main目录下创建groovy目录,然后创建自己的包名和插件类
(3)、在src/main目录下创建resources/META-INFO/gradle-plugins目录,创建plugins.properties文件,文件内容为:
implementation-class=com.zdj.plugin.MyPlugin //我们自己的插件类
(4)、修改build.gradle文件
apply plugin: 'groovy'
apply plugin: 'java'
buildscript {
repositories {
mavenLocal()
google()
jcenter()
}
}
repositories {
mavenLocal()
google()
jcenter()
}
dependencies {
compile gradleApi()
compile localGroovy()
compile 'com.android.tools.build:gradle:3.0.1'
}
java {
sourceCompatibility = JavaVersion.VERSION_1_7
targetCompatibility = JavaVersion.VERSION_1_7
}
2、创建Plugin
在刚才创建的插件类里,我们写插件的代码。插件类继承Plugin,并实现apply接口,apply就是在build.gradle里apply plugin 'xxx'的时候要调用的接口了。
package com.zdj.plugin
import org.gradle.api.Plugin
import org.gradle.api.Project
/**
* 在这个类中写插件的代码。
* 首先继承Plugin,然后实现apply接口。
* apply就是在build.gradle里apply plugin 'xxx'的时候要调用的接口。
*/
class MyPlugin implements Plugin<Project> {
@Override
void apply(Project target) {
println("apply my plugin")
}
}
3、创建插件的task
我们再定义一个类MyTask,继承自DefaultTask。
package com.zdj.plugin
import org.gradle.api.DefaultTask
import org.gradle.api.tasks.TaskAction
/**
* 定义一个类MyTask,继承自DefaultTask。
*/
class MyTask extends DefaultTask {
@TaskAction
void action() {
println("my task run")
}
}
然后在plugin中注册这个task。
package com.zdj.plugin
import org.gradle.api.Plugin
import org.gradle.api.Project
/**
* 在这个类中写插件的代码。
* 首先继承Plugin,然后实现apply接口。
* apply就是在build.gradle里apply plugin 'xxx'的时候要调用的接口。
*/
class MyPlugin implements Plugin<Project> {
@Override
void apply(Project target) {
println("apply my plugin")
//注册task
target.tasks.create("mytask", MyTask.class)
}
}
4、本地安装插件
我们已经开发好了一个简单的插件,那么如何使用呢?
我们首先需要在build.gradle中引入maven插件,并且配置install相关的属性。
apply plugin: 'maven'
install {
repositories.mavenInstaller {
pom.version = '0.0.1' //配置插件版本号
pom.artifactId = 'myplugin' //配置插件标识
pom.groupId = 'com.zdj.plugin' //配置插件组织
}
}
之后执行./gradlew install便会把插件安装在本地maven仓库。
本地安装插件执行./gradlew install命令是可能会报错could not find tools.jar问题。我遇到了这个问题。
我的解决办法是:先查看JDK的目录路径(通过命令 /usr/libexec/java_home -V),然后手动复制tools.jar(通过命令sudo cp)。
5、使用我们的插件
在使用的地方引入我们插件的classpath
classpath 'com.zdj.plugin:myplugin:0.0.1'
最后,附上两个重要的查询地址:
1、
(gradle dsl查询地址)
2、
https://developer.android.com/reference/tools/gradle-api
(android gradle plugin dsl查询地址)