一、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 {  //配置项目的classpathrepositories {  //项目的仓库地址,会按顺序依次查找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 30buildToolsVersion "30.0.3"defaultConfig {  //默认配置applicationId "com.zdj.uidemo"minSdkVersion 23targetSdkVersion 30versionCode 1versionName "1.0"testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"}buildTypes {  //编译类型release {  //release版本minifyEnabled falseproguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'}}compileOptions {  //指定java版本sourceCompatibility JavaVersion.VERSION_1_8targetCompatibility 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() {@Overridevoid buildStarted(Gradle gradle) {println('构建开始')// 这个回调一般不会调用,因为我们注册的时机太晚,注册的时候构建已经开始了,是 gradle 内部使用的}@Overridevoid settingsEvaluated(Settings settings) {println('settings 文件解析完成')}@Overridevoid projectsLoaded(Gradle gradle) {println('项目加载完成')gradle.rootProject.subprojects.each { pro ->pro.beforeEvaluate {println("${pro.name} 项目配置之前调用")}pro.afterEvaluate{println("${pro.name} 项目配置之后调用")}}}@Overridevoid projectsEvaluated(Gradle gradle) {println('项目解析完成')}@Overridevoid 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@TaskActionvoid 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 {@Overridepublic String getName() {// 返回transform的名称,最终的名称会是transformClassesWithMyTransformForDebug这种形式return "MyTransform";}@Overridepublic 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;}@Overridepublic Set<? super QualifiedContent.Scope> getScopes() {/**返回需要处理内容的范围,有下面几种类型PRIOJECT(1),只处理项目的内容SUB_PROJECTS(4),只处理子项目EXTERNAL_LIBRARIES(16),只处理外部库TESTED_CODE(32),只处理当前variant对应的测试代码PROVIDED_ONLY(64),处理依赖@DeprecatedPROJECT_LOCAL_DEPS(2)@DeprecatedSUB_PROJECTS_LOCAL_DEPS(8);*/return Sets.immutableEnumSet(QualifiedContent.Scope.PROJECT);}@Overridepublic boolean isIncremental() {//是否增量,如果返回true,TransformInput会包括一份修改的文件列表,返回false,会进行全量编译,删除上一次的输出内容return false;}@Overridevoid transform(TransformInvocation transformInvocation) throws TransformException, InterruptedException, IOException {//在这里处理classsuper.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 ->//获取对应的输出jarFile 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_7targetCompatibility = JavaVersion.VERSION_1_7
}

2、创建Plugin 

在刚才创建的插件类里,我们写插件的代码。插件类继承Plugin,并实现apply接口,apply就是在build.gradle里apply plugin 'xxx'的时候要调用的接口了。

package com.zdj.pluginimport org.gradle.api.Plugin
import org.gradle.api.Project/*** 在这个类中写插件的代码。* 首先继承Plugin,然后实现apply接口。* apply就是在build.gradle里apply plugin 'xxx'的时候要调用的接口。*/
class MyPlugin implements Plugin<Project> {@Overridevoid apply(Project target) {println("apply my plugin")}
}

3、创建插件的task 

我们再定义一个类MyTask,继承自DefaultTask。

package com.zdj.pluginimport org.gradle.api.DefaultTask
import org.gradle.api.tasks.TaskAction/*** 定义一个类MyTask,继承自DefaultTask。*/
class MyTask extends DefaultTask {@TaskActionvoid action() {println("my task run")}
}

然后在plugin中注册这个task。

package com.zdj.pluginimport org.gradle.api.Plugin
import org.gradle.api.Project/*** 在这个类中写插件的代码。* 首先继承Plugin,然后实现apply接口。* apply就是在build.gradle里apply plugin 'xxx'的时候要调用的接口。*/
class MyPlugin implements Plugin<Project> {@Overridevoid apply(Project target) {println("apply my plugin")//注册tasktarget.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 Version 7.3.3

(gradle dsl查询地址)

2、

https://developer.android.com/reference/tools/gradle-api

(android gradle plugin dsl查询地址)

gradle打包流程(二)--- 进一步理解gradle相关推荐

  1. gradle打包流程(一)--- 整体把控

    前言: 关于gradle打包流程,大的可以分为以下几个方面: 一.对gralde打包流程的整体把控. 二.对gradle的进一步理解,这其中包括:gradle到底是什么,gradle的项目层次结构,g ...

  2. Android Ant 和 Gradle 打包流程和效率对照

    一.Ant 打包:(下载ant.配置环境变量就不说了) 1.进入命令行模式,并切换到项目文件夹.运行例如以下命令为ADT创建的项目加入ant build支持: android update proje ...

  3. Jenkins+Gradle+Git+Pyger+二维码搭建Android自动打包平台

    1.下载jenkins war包 从Jenkins官方网站下载最新的war包,然后在war的目录下打开cmd,执行命令:java -jar jenkins.war. 看到以下信息,就表示jenkins ...

  4. 【SDK接入篇】【1】Unity的internal 与 gradle打包

    一.简单说说internal 与 gradle打包的区别 **internal: unity内置,仅需Android SDK支持,不能导出工程** (适用于仅使用Unity开发的项目) **Gradl ...

  5. Android应用开发编译框架流程与IDE及Gradle概要

    1 背景 建议阅读本文之前先阅读<Android Studio入门到精通>和<Groovy脚本基础全攻略>及<Gradle脚本基础全攻略>三篇博客作为背景知识,这样 ...

  6. gradle打包web jar_Gradle构建SpringBoot并打包可运行的jar配置

    使用Gradle构建项目,继承了Ant的灵活和Maven的生命周期管理,不再使用XML作为配置文件格式,采用了DSL格式,使得脚本更加简洁. 构建环境: jdk1.6以上,此处使用1.8 Gradle ...

  7. Android Studio Gradle打包实践之多渠道+版本号管理

    上次介绍了 Android Studio的安装.配置和基本使用 .这次讲一下Android Studio用到的打包工具Gradle. Gradle 是一种构建项目的框架,兼容Maven.Ant,为Ja ...

  8. Android中清单文件引入配置参数,Android 使用gradle打包的各种配置

    原标题:Android 使用gradle打包的各种配置 在AS中利用gradle打包,可以高效并且自由地配置各种参数,发布不同的版本.关于配置gradle文件的一些做法,总结为如下. 一.替换Andr ...

  9. 深入理解gradle编译-Android进阶篇

    2019独角兽企业重金招聘Python工程师标准>>> 9/27/2016 4:23:23 PM 深入理解gradle编译-Android进阶篇 导读 本文旨在介绍Gradle构建的 ...

最新文章

  1. 检测线程是否存活代码!
  2. |9 其他(linux特定的), 用来存放内核例行程序的文档.,Linux下的帮助命令
  3. @value 静态变量_Linux运维工程师从基础到进阶:Shell变量知识梳理
  4. 编程体系结构(07):JavaEE之Web开发
  5. linux安装trac+svn+apache+wike,apache+svn+trac安装及配置2
  6. win10系统无法自动修复启动解决方案
  7. WindowsXP、Windows2003本地密码清除方法
  8. Python社区发现—Louvain—networkx和community
  9. Building Worlds In Unreal 学习笔记——20-23 程序化植被/草Billboard材质/实时虚拟纹理(RVT)的使用
  10. [思考] 闲时随笔一篇
  11. vs2019,C#,MySQL创建图书管理系统3(管理员相关页面的布局和设计实现,图书显示,图书添加)
  12. 微信支付提示参数错误
  13. 卡塔兰数用于求解不同形态的二叉树的数目,题目选自CS61A2021 LAB9 Q3: Number of Trees
  14. 数据库设计的六个阶段
  15. Conda安装失败:Solving environment: failed with initial frozen solve. Retrying with flexible solve.
  16. C/C++交通处罚单处理系统
  17. 你了解Android LMK机制么?
  18. 2D网络游戏开发(网络篇)(五)
  19. 网络电话显示服务器拒绝,云安全日报201223:思科IP电话发现拒绝服务漏洞 需要尽快升级...
  20. 怎样将cad布局导出来_CAD布局如何导出模型图?

热门文章

  1. 物理磁盘监控工具--scrutiny
  2. AutoSAR系列讲解(实践篇)7.5-OS原理进阶(上)
  3. 前端开发自动生成html,专业前端开发人员推荐的几款CSS 3代码生成工具
  4. 震惊世界的中国秘方——留着有用
  5. 用 ChatGPT 辅助学好机器学习
  6. MoreLikeThis 相似检索
  7. java 字符串 压缩_用JAVA实现字符串压缩算法
  8. 实训记录(二)——分镜的制作!
  9. 扫段攻击来袭,DDoS防御面临新挑战
  10. 计算机相关的职称考试有那些,全国计算机职称考试的六大考试策略