作者
大家好,我叫Jack冯;
本人20年硕士毕业于广东工业大学,于2020年6月加入37手游安卓团队;目前主要负责海外游戏发行安卓相关开发。
一、Gradle简介
- Gradle是什么,能做什么?
- Android Studio的默认构建工具,用来构建应用程序。组成部分见下图:
- groovy核心语法:闭包、数据结构等
- build script block:工程build.gradle的脚本
- gradle api:project、task、plugin等
二、Groovy基础
groovy特性:
- 基于JVM的开发语言,执行:groovy源文件==》class字节码==》JVM处理执行;或groovy源文件直接解析执行(类似Script)
- 无缝集成所有的Java类库,但脚本写法比Java更简洁
1、字符串
(1)定义使用
String(java.lang.String) GString(Groovy String),常用定义方式有单引号、双引号、三引号
注意:
单引号和Java的双引号是一样的,内容不能改变;
双引号,支持参数扩展(实现类会变成GString),扩展的字符串可以是任意表达式,即“ ${ 任意表达式 } ”;
三引号,格式任意,不需要转义字符、指定输出。
示例代码:
代码语言:txt复制def str = 'a single ' ' " "string'
def str2 = "a double ' ' " " "
"string "
def str3 = '''a thuple ' ' " "
string'''
println str
println str2
println str3
println str.class
println str2.class
println str3.class
def str4 = "double string : ${str2}"
println str4.class
输出结果:
代码语言:txt复制a single ' ' " "string
a double ' ' " " string
a thuple ' ' " "
string
class java.lang.String
class java.lang.String
class java.lang.String
class org.codehaus.groovy.runtime.GStringImpl
2、扩展
字符串扩展的方法众多,具体来源见下图:
- java.lang.String:Java原有的方法。
- DefaultGroovyMethods:Groovy对所有对象的一个扩展。
- StringGroovyMethods:继承自DefaultGroovyMethods,重写了适用于String使用的方法。下面是截取的部分源码:
package org.codehaus.groovy.runtime;
public class StringGroovyMethods extends DefaultGroovyMethodsSupport {
...
//将字符串的第一个字母大写的简便方法
public static String capitalize(String self){..}
//创建一个新的CharSequence,它与这个字符串相反(向后)
public static CharSequence reverse(CharSequence self){..}
//逐行遍历此字符串。
public static <T> T eachLine(String self, int firstLine, @ClosureParams(value=FromString.class, options={"String","String,Integer"}) Closure<T> closure){..}
//返回在字符串中第一次出现已编译正则表达式时调用闭包的结果。
public static String find(String self, Pattern pattern, @ClosureParams(value=SimpleType.class, options="java.lang.String[]") Closure closure) {..}
...
}
(1) 普通类型参数使用:
代码语言:txt复制def str = "groovy Hello"
//指定长度和填充字符,对已知字符的填充
println str.center(18,'a')
println str.padLeft(18,'a')
//字符串的比较操作符
def str2 = 'Hello'
println str > str2
//获取字符串的index对应值
println str[0]
//获取字符串的一段子串
println str[0..1]
//删掉字串
println str - str2
//字符串反向输出
println str.reverse()
//字符串首字母大写
println str.capitalize()
//字符串是否数字的判断
println str.isNumber()
//字符串转Integer类型/Double类型等
def str3 = "123"
println str3.toInteger()
println str3.toInteger().class
(2)和闭包组合:下面示例就是在闭包(task类型)传入一个字符串参数路径,执行find闭包方法输出。
代码语言:txt复制task findFile{
String path = getRootDir().absolutePath File.separator "build.gradle"
path.find { String filePath ->
File file = new File(filePath)
if (file.exists()){
println "build.gradle in rootDir exists!"
}
}
}
//对比常规闭包的定义和使用
def findFile = {...}
findFile.call()
输出结果:
代码语言:txt复制10:15:29: Executing task 'findFile'...
初始化开始...
> Configure project :
配置阶段完成
> Configure project :Project01
build.gradle in rootDir exists!
> Task :Project01:findFile UP-TO-DATE
gradle执行结束
BUILD SUCCESSFUL in 93ms
10:15:30: Task execution finished 'findFile'.
关于Groovy和Java的异同,除了所述的字符串外,还有自动导入包的方式、方法调用时期差异等,具体可见Groovy文档《 Differences with Java》 http://www.groovy-lang.org/differences.html
2、闭包基础
闭包,实质上是一段代码块。 这里介绍闭包基础部分,主要包括内容:
- 闭包概念:闭包的定义、调用
- 闭包参数:普通参数、隐式参数
- 闭包返回值:总是有返回值的
(1)定义
定义和调用:参数 ->执行体
代码语言:txt复制//(1)闭包定义
def clouser = { String name ->
println "1、println:clouser ${name}"
}
//(2)调用方式
clouser("test")
clouser.call("test call")
//(3)多个参数
def MyClouser = {String names ,int ages ->
println "2、println MyClouser:heloo ${names}, my ages is ${ages} "
}
def names = 'my_clouser'
MyClouser(names,100)
//(4)所有闭包的隐式默认参数it,可以不声明的
def itClouser = {
println "3、println itClouser: hello ${it}"
}
itClouser('it_clouser')
//(5)闭包的返回值,没有return的话,返回null
def returnClouser = {
println "4、println returnClouser:hello ${it}"
// return "hello ${it}"
}
def result = returnClouser('return_clouser')
println "5、println returnClouser result:" result
输出结果:
代码语言:txt复制1、println:clouser test
1、println:clouser test call
2、println MyClouser:heloo my_clouser, my ages is 100
3、println itClouser: hello it_clouser
4、println returnClouser:hello return_clouser
5、println returnClouser result:null
(2)使用
举例:字符串与闭包的结合使用
代码语言:txt复制String str = 'the 2 and 3 is 5'
//1、each的遍历
str.each {
String temp -> print temp.multiply(2)//每个字符拷贝一份,返回值还是str本身
}
println ""
//2、find来查找符合条件的第一个
println str.find {
String s -> s.isNumber()
}
//3、查找所有符合条件的
def list = str.findAll {
String s -> s.isNumber()
}
println list.toListString()
//4、查找是否有符合条件的
def anyresult = str.any {
String s -> s.isNumber()
}
println anyresult
//5、查找是否全部都符合条件
def everyresult = str.every {
String s -> s.isNumber()
}
println everyresult
//6、将小写字母转换为大写
def list2 = str.collect {
it.toUpperCase()
}
println list2.toListString()
输出结果:
代码语言:txt复制tthhee 22 aanndd 33 iiss 55
2
[2, 3, 5]
true
false
[T, H, E, , 2, , A, N, D, , 3, , I, S, , 5]
(3)闭包变量
1) 在介绍闭包委托策略之前,这里先介绍下闭包的三个重要变量。
如果是在类或方法中定义闭包时,三个变量(this、owner、delegate)的值是一样的;
但是在闭包中嵌套定义了闭包,this和owner、delegate指向的值就会不同,如果单独修改delegate变量指向,则三者值都会不一样。
- this,代表闭包定义处的类,不可修改
- owner,代表闭包定义处的类或者对象,不可修改
- delegate,代表任意对象,默认和owner一致,可修改
这里在类或方法中定义闭包,
代码语言:txt复制def scriptClouser = {
println "scriptClouser this : " this
println "scriptClouser owner : " owner
println "scriptClouser delegate : " delegate
}
scriptClouser.call()
输出结果:
代码语言:txt复制scriptClouser this : pkg.character01@5be067de
scriptClouser owner : pkg.character01@5be067de
scriptClouser delegate : pkg.character01@5be067de
2)内部类相关
代码语言:txt复制//定义内部类
class Person{
def static classClouser = {
println "classClouser this : " this
println "classClouser owner : " owner
println "classClouser delegate : " delegate
}
def static say(){
def methodClouser = {
println "methodClouser this : " this
println "methodClouser owner : " owner
println "methodClouser delegate : " delegate
}
methodClouser.call()
}
}
//1、输出person的static方法调用结果,三者都是指向当前的类
Person.classClouser.call()
Person.say()
输出结果:
代码语言:txt复制classClouser this : class pkg.Person
classClouser owner : class pkg.Person
classClouser delegate : class pkg.Person
methodClouser this : class pkg.Person
methodClouser owner : class pkg.Person
methodClouser delegate : class pkg.Person
如果去掉方法的static声明,则输出的person指向会是当前类的某个具体对象。
代码语言:txt复制class Person{
def classClouser = {...}
def say(){...}
}
//2、非static方法调用示例
Person innerPerson = new Person()
innerPerson.classClouser.call()
innerPerson.say()
输出结果:
代码语言:txt复制classClouser this : pkg.Person@5df417a7
classClouser owner : pkg.Person@5df417a7
classClouser delegate : pkg.Person@5df417a7
methodClouser this : pkg.Person@5df417a7
methodClouser owner : pkg.Person@5df417a7
methodClouser delegate : pkg.Person@5df417a7
3)特殊情形:闭包中的闭包
这里,this指向定义闭包的类;owner指向nestClouser的实例对象,delegate指向最近的闭包对象。其中,可以单独指定innerClouser的delegate,示例如下。
代码语言:txt复制//闭包中定义一个闭包,三者不一致
def nestClouser = {
def innerClouser = {
println "innerClouser this : " this
println "innerClouser owner : " owner
println "innerClouser delegate : " delegate
}
//innerClouser.delegate = innerPerson
innerClouser.call()
}
nestClouser.call()
输出结果:
代码语言:txt复制innerClouser this : pkg.character01@224b4d61
innerClouser owner : pkg.character01$_run_closure1@5ab14cb9
innerClouser delegate : pkg.character01$_run_closure1@5ab14cb9
单独指定delegate的话,三者的输出结果都会不一样:
代码语言:txt复制innerClouser this : pkg.character01@7c041b41
innerClouser owner : pkg.character01$_run_closure1@361c294e
innerClouser delegate : pkg.Person@7859e786
(4)委托策略
闭包中委托策略分四种:OWNER_FIRST(默认)、DELEGATE_FIRST、OWNER_ONLY、DELEGATE_ONLY,默认策略表明闭包中的变量、方法等,都是首先从owner指向的对象处寻找。通过改变delegate指向对象和不同的委托策略指定,可以指定闭包优先从哪个对象寻找调用的变量和方法。
下面示例修改委托策略为Closure.DELEGATE_FIRST,可使得优先从delegate指向的对象中寻找同名的变量方法属性,找不到再返回Owner指向对象中查询。
代码语言:txt复制class ClosureOutput{
String name
def method = { "The output of this time is ${name}" }
String toString(){
method.call()
}
}
class ClosureDelegationOutput{
String name
}
def output01 = new ClosureOutput(name:'ClosureOutput')
def output02 = new ClosureDelegationOutput(name:'ClosureDelegationOutput')
println "output01:" output01.toString()
//修改delegate对象,添加委托策略,从delegate开始寻找
output01.method.delegate = output02
output01.method.resolveStrategy = Closure.DELEGATE_FIRST
println "output01:" output01.toString()
输出结果:
代码语言:txt复制output01:The output of this time is ClosureOutput
output01:The output of this time is ClosureDelegationOutput
注意:如果ClosureDelegationOutput方法中没有ClosureOutput方法的同名参数方法,而且修改的委托策略是Closure.DELEGATE_ONLY,会抛出异常groovy.lang.MissingPropertyException。
三、生命周期
1、执行阶段
Gradle的执行流程,主要分为三个阶段
- Initialization初始化阶段:解析整个工程中所有project,构建所有的project对象
- Configuration配置阶段:解析所有project对象的task,构建所有task的依赖图
- Execution执行阶段:执行具体的task及其依赖的task
2、监听示例
为了方便追踪各个阶段的执行情况,在各节点加了日志打印。
首先是初始化阶段,执行settings.gradle进行全局配置,在文件添加:
代码语言:txt复制println '初始化开始...'
然后是配置、执行阶段的监听。在根目录build.gradle添加:
代码语言:txt复制//配置阶段监听(本project)
this.beforeEvaluate { Project project ->
println "$project 配置阶段开始 ..."
}
this.afterEvaluate { Project project ->
println "$project 配置完成 ..."
}
//配置阶段监听(包括其他project)
this.gradle.beforeProject { Project project ->
println " $project 准备配置 ..."
}
this.gradle.afterProject { Project project ->
println " $project 配置结束 ..."
}
//配置完成
gradle.taskGraph.whenReady { TaskExecutionGraph taskGraph ->
println "配置阶段结束,TaskExecutionGraph is ready ..."
if(taskGraph.hasTask(taskZ)) {
lib1.dependsOn taskZ
}
}
//执行阶段的监听
gradle.taskGraph.beforeTask { Task task ->
println " $task 开始执行..."
}
gradle.taskGraph.afterTask { Task task, TaskState state ->
if (state.failure) {
println " $task 执行失败..."
}
else {
println " $task 执行结束..."
}
}
//执行阶段结束后的回调监听,操作个别文件
this.gradle.buildFinished {
println '执行阶段结束'
fileTree('/project01/build/libs/'){ FileTree fileTree ->
fileTree.visit { FileTreeElement fileTreeElement ->
copy {
from fileTreeElement.file
into getRootProject().getBuildDir().path '/testFiletree/'
}
}
}
}
还可以添加其他监听:
代码语言:txt复制//this.gradle.addListener()
//this.gradle.addProjectEvaluationListener()
this.gradle.addBuildListener(new BuildListener() {
@Override
void buildStarted(Gradle gradle) {
println '开始构建'
}
@Override
void settingsEvaluated(Settings settings) {
println 'settings.gradle 中代码执行完毕'
}
@Override
void projectsLoaded(Gradle gradle) {
println '初始化阶段结束'
}
@Override
void projectsEvaluated(Gradle gradle) {
println '配置阶段结束,TaskExecutionGraph is ready ...'
}
@Override
void buildFinished(BuildResult buildResult) {
println '执行阶段结束 '
}
})
执行结果:
代码语言:txt复制11:57:45: Executing task 'clean'...
初始化开始...
> Configure project :
root project 'helloGradle' 配置结束 ...
root project 'helloGradle' 配置完成 ...
> Configure project :buildApp
project ':buildApp' 准备配置 ...
project ':buildApp' 配置结束 ...
> Configure project :Project01
project ':Project01' 准备配置 ...
project ':Project01' 配置结束 ...
> Configure project :project02
project ':project02' 准备配置 ...
project ':project02' 配置结束 ...
配置阶段结束,TaskExecutionGraph is ready ...
> Task :clean UP-TO-DATE
task ':clean' 开始执行...
task ':clean' 执行结束...
> Task :buildApp:clean UP-TO-DATE
task ':buildApp:clean' 开始执行...
task ':buildApp:clean' 执行结束...
> Task :Project01:clean UP-TO-DATE
task ':Project01:clean' 开始执行...
task ':Project01:clean' 执行结束...
> Task :project02:clean UP-TO-DATE
task ':project02:clean' 开始执行...
task ':project02:clean' 执行结束...
执行阶段结束
BUILD SUCCESSFUL in 42ms
4 actionable tasks: 4 up-to-date
11:57:45: Task execution finished 'clean'.
执行完毕,project01/build/libs/下的文件已拷贝到根目录中。
3、拓展
对于生命周期的监听,更多是为了在编译过程或者结束阶段,添加一些自定义操作,例如重命名APK等。对比其他构建工具,没法轻易做到像Gradle这样,见缝插针式地监听生命周期并执行自定义操作。
代码语言:txt复制...
android {
defaultConfig {
applicationId "com.game.demo"
minSdkVersion rootProject.ext.androidMinSdkVersion
targetSdkVersion rootProject.ext.androidTargetSdkVersion
versionCode 35
versionName "1.0.1"
}
//处理apk名称
...
applicationVariants.all { variant ->
variant.outputs.all {
outputFileName = "${defaultConfig.applicationId}-${defaultConfig.versionName}-${variant.buildType.name}.apk"
}
}
...
}
概括其执行流程:
- 首先,settings.gradle是gradle执行的入口,主要获取项目模块的相关信息,初始化。
- 然后,先后配置root.project、子project,配置的过程就是获取build.gradle的参数、task信息。
- 接着,配置结束调用 project.afterEvaluate,它表示所有的模块都已经配置结束,准备进入执行阶段。
- 此时,所谓的有向无环图已经输出,包含 task 及其依赖的 task。在gradle.taskGraph.whenReady{}可以修改task依赖关系等。
- 最后,执行指定的 task 及其依赖的 task。
四、Task
task和project都是Gradle比较重要的概念,task即任务,是构建过程执行的基本工作。Android Studio(Windows环境)可以使用指令“gradlew tasks”查看当前工程的task详细信息。当Gradle API自带task无法满足项目需要时,可以自定义task执行特定操作。例如,在工程的不同模块gradle文件,自定义task,是可以相互调用的。例如在test1.gradle定义了test(),可在test2.gradle中调用,注意执行顺序会有差别(后面在task执行顺序中讲解)。
1、定义使用
- 创建方式1:直接通过task函数构建,创建时填充基本配置。eg:添加task的存放位置37sdk
task createTask(group: '37sdk',description:'task study'){
//在执行阶段输出
doFirst {
println 'this group : ' group
println 'Task created successfully。'
}
}
输出:(执行完毕可以在独立文件夹37sdk管理自定义task)
代码语言:txt复制 ...
Task :createTask
task ':createTask' 开始执行...
this group : 37sdk
Task created successfully
task ':createTask' 执行结束...
...
更多:build下面有很多assembleXxx任务,是根据buildType和productFlavor的不同自动创建多个。
- 创建方式2:通过taskContainer去创建,然后在闭包中配置属性
//task createTask(group: '37sdk',description:'task study'){
this.tasks.create(name:'helloTask2'){
setGroup('37sdk')
setDescription('task study')
setBuildDir('build/outputs/helloTask3/')
println 'hello task2'
}
```
可指定的参数类型,见Task.class:
代码语言:txt复制 public interface Task extends Comparable<Task>, ExtensionAware {
//可指定参数及对应方法
String TASK_NAME = "name";
String TASK_DESCRIPTION = "description";
String TASK_GROUP = "group";
String TASK_TYPE = "type";
String TASK_DEPENDS_ON = "dependsOn";
String TASK_OVERWRITE = "overwrite";
String TASK_ACTION = "action";
String TASK_CONSTRUCTOR_ARGS = "constructorArgs";
void setGroup(@Nullable String var1);
void setDescription(@Nullable String var1);
void setDependsOn(Iterable<?> var1);
void setProperty(String var1, Object var2) throws MissingPropertyException;
...
}注意:若指定task输出目录,调用的是Project的方法,见Project.class: public interface Project extends Comparable<Project>, ExtensionAware, PluginAware {
//gradle默认配置
String DEFAULT_BUILD_FILE = "build.gradle";
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";
//更多的可指定配置
void setBuildDir(Object var1);
void setDescription(@Nullable String var1);
void setGroup(Object var1);
void setVersion(Object var1);
void setStatus(Object var1);
...
//常用方法
Project getRootProject();
File getRootDir();
File getBuildDir();
...
}
2、执行
task的逻辑可运行在配置阶段和执行阶段(应用闭包 doFirst{ } 和 doLast{ } );另外,同是执行阶段,不同调用方式的执行顺序会有差别。
示例代码:
代码语言:txt复制// 使用 Task 在执行阶段进行操作
task myTask3(group: "MyTask", description: "task3") {
doFirst {
// 次执行
println "the current order is 2"
}
println "这是一条运行在配置阶段的,myTask3"
doLast {
// 最后执行
println "the current order is 3"
}
}
// 也可以使用 taskName.doxxx 的方式添加执行任务
myTask3.doFirst {
// 这种方式的最先执行
println "the current order is 1"
}
执行结果如下:
代码语言:txt复制16:37:13: Executing task 'myTask3'...
初始化开始...
> Configure project :
这是一条运行在配置阶段的,myTask3
root project 'helloGradle' 配置完成 ...
project ':buildApp' 配置结束 ...
project ':Project01' 配置结束 ...
project ':project02' 配置结束 ...
配置阶段结束,TaskExecutionGraph is ready ...
> Task :myTask3
task ':myTask3' 开始执行...
the current order is 1
the current order is 2
the current order is 3
task ':myTask3' 执行结束...
执行阶段结束
BUILD SUCCESSFUL in 96ms
1 actionable task: 1 executed
16:37:13: Task execution finished 'myTask3'.
示例2:自定义task去计算执行阶段的耗时,即计算build执行时长,区间:preBuildTask.doFirst--buildTask.doLast
代码语言:txt复制//注意1:为什么运行在this.afterEvaluate 监听去计算build时长?因为是配置结束阶段,依赖蓝图已经输出,可以查找到每一个task
//注意2:保证要找的task已经配置完毕,prebuild是在Android工程里面有
def startBuildTime,endBuildTime
this.afterEvaluate { Project project ->
def preBuildTask = project.tasks.getByName('preBuild')
preBuildTask.doFirst {
startBuildTime = System.currentTimeMillis()
println 'the start time is ' startBuildTime
}
def buildTask = project.tasks.getByName('build')
buildTask.doLast {
endBuildTime = System.currentTimeMillis()
println "the end time is ${endBuildTime - startBuildTime}"
}
}
3、task依赖
task的执行阶段,指定执行顺序有两种方式
- 通过Task的API指定执行顺序(即doFirst、doLast)
- dependsOn强依赖方式
/添加依赖的方式
task taskX{
doLast {
println 'taskX'
}
}
task taskY{
doLast {
println 'taskY'
}
}
taskY.dependsOn(taskX)
task taskZ(dependsOn:[taskX,taskY]){
// dependsOn this.tasks.findAll {
// task ->return task.name.startsWith('lib')
// }
doLast {
println 'taskZ'
}
}
输出结果:
代码语言:txt复制17:13:30: Executing task 'taskZ'...
初始化开始...
> Configure project :
root project 'helloGradle' 配置完成 ...
project ':buildApp' 配置结束 ...
project ':Project01' 配置结束 ...
project ':project02' 配置结束 ...
配置阶段结束,TaskExecutionGraph is ready ...
> Task :taskX
task ':taskX' 开始执行...
taskX
task ':taskX' 执行结束...
> Task :taskY
task ':taskY' 开始执行...
taskY
task ':taskY' 执行结束...
> Task :taskZ
task ':taskZ' 开始执行...
taskZ
task ':taskZ' 执行结束...
执行阶段结束
BUILD SUCCESSFUL in 81ms
3 actionable tasks: 3 executed
17:13:30: Task execution finished 'taskZ'.
执行taskZ,就会提前执行taskX、taskY;类似的,执行taskY也会先执行taskX。依赖的效果,首先执行所依赖的task,再到本task。同比Java,如果类A依赖类B,类B会先被编译,然后才是类A。依赖的目的,在执行阶段添加自己的操作,例如创建lib系列的task任务,当执行到taskZ时,先把lib系列任务先执行,然后才是taskZ自身任务。
4、拓扑图
关于task依赖拓扑图,可以引入插件gradle-visteg,以图的形式输出Task相关依赖,默认生成visteg.dot文件;使用指令dot -Tpng ./visteg.dot -o ./visteg.dot.png,可将其转换图片格式查看。
(1)首先在根目录build.gradle配置仓库路径
代码语言:txt复制buildscript {
repositories {
google()
jcenter()
maven {
url "https://plugins.gradle.org/m2/"
}
}
dependencies {
classpath 'com.android.tools.build:gradle:4.0.2'
classpath 'gradle.plugin.cz.malohlava:visteg:1.0.5'
}
}
(2)应用插件
代码语言:txt复制apply plugin: 'cz.malohlava.visteg'
(3)visteg属性配置,重要是enabled(启用插件)和destination(输出文件路径)
代码语言:txt复制 visteg {
enabled = true
colouredNodes = true
colouredEdges = true
destination = 'build/reports/visteg.dot'
exporter = 'dot'
colorscheme = 'spectral11'
nodeShape = 'box'
startNodeShape = 'hexagon'
endNodeShape = 'doubleoctagon'
}
(4)执行上方示例taskZ,在路径下可以查看dot文件:/build/reports/visteg.dot
代码语言:txt复制digraph compile {
colorscheme=spectral11;
rankdir=TB;
splines=spline;
":app:taskZ" -> ":app:taskX" [colorscheme="spectral11",color=5];
":app:taskZ" -> ":app:taskY" [colorscheme="spectral11",color=5];
":app:taskZ" [shape="hexagon",colorscheme="spectral11",style=filled,color=5];
":app:taskX" [shape="doubleoctagon",colorscheme="spectral11",style=filled,color=5];
":app:taskY" -> ":app:taskX" [colorscheme="spectral11",color=5];
":app:taskY" [shape="box",colorscheme="spectral11",style=filled,color=5];
{ rank=same; ":app:taskZ" }
}
(4)转换命令:dot -Tpng ./visteg.dot -o ./visteg.dot.png
5、应用实例
这里通过脚本操作AndroidManifest.xml文件,去修改APK的版本号、图标、活动主题等内容,以及新增参数如<meta-data/>等。掌握了一种可行修改方式后,其他处理也可以依样进行。
代码语言:txt复制task replaceManifest(group: "gradleTask", description: "replace") {
GPathResult androidManifest = new XmlSlurper().parse("${projectDir}/src/main/AndroidManifest.xml")
//1、修改版本号beta
String versionName = androidManifest['@android:versionName']
//注意:等同于androidManifest['@android:versionName'];另外,如果build.gradle的defaultConfig标签有设定version信息,最后构建优先选择配置文件的指定版本。
if(!versionName.contains('-beta')){
versionName = '-beta'
androidManifest.setProperty('@android:versionName', versionName "")
}
//2、替换图标
//String iconName = androidManifest.application['@android:icon']
def iconName = "@drawable/logo37"
androidManifest.application.setProperty('@android:icon', iconName "")
//3、替换活动主题:按声明activity的顺序ID修改
def activityTheme = androidManifest.application.'activity'[0]['@android:theme']
println "'activity'[0]['@android:theme']=" activityTheme
def newTheme = "@style/Theme.AppCompat.NoActionBar"
androidManifest.application.activity[0].setProperty('@android:theme',newTheme "")
//3、替换主活动主题
def newTheme2 = "@style/Theme.AppCompat.DayNight.DarkActionBar"
androidManifest.application.activity.each{
def isReplaceMainActivityTheme = false
it.children().each {
if(it.name() == "intent-filter"){
it.children().each{
if(it.name()=="action" && it.@"android:name"=="android.intent.action.MAIN"){
isReplaceMainActivityTheme = true
return true
}
}
}
if(isReplaceMainActivityTheme){
return true
}
}
if (isReplaceMainActivityTheme){
it.@"android:theme" = newTheme2
return true
}
}
new File(("${projectDir}/src/main/AndroidManifest.xml")).write(XmlUtil.serialize(androidManifest))
}
除了自定义task这种方式以外,也可以在Gradle生命周期的方法中执行脚本,示例:在AndroidManifest.xml中添加<meta-data/>参数。同理,补充权限声明也是同样的方式<uses-permission/>等
代码语言:txt复制project.afterEvaluate {
android.applicationVariants.all { ApplicationVariant variant ->
String variantName = variant.name.capitalize()
def processManifestTask = project.tasks.getByName("process${variantName}Manifest")
processManifestTask.doLast { pmt ->
String manifestPath = "${projectDir}/src/main/AndroidManifest.xml"
def manifest = file(manifestPath).getText()
def xml = new XmlParser().parseText(manifest)
xml.application[0].appendNode("meta-data", ['android:name': 'com.facebook.sdk.ApplicationId', 'android:value': '@string/facebook_app_id'])
new File(("${projectDir}/src/main/AndroidManifest.xml")).write(XmlUtil.serialize(xml))
}
}
}
五、自定义plugin
plugin本身的新东西并不多,主要是封装的一个体现。Gradle plugin插件,就是将完成特定任务的所有Task都封装到一个插件中,当别人引用这个插件,就可以完成特定的功能。
1、插件类型
脚本插件:实为脚本,作用是可拆分复杂脚本、封装任务,例如拆分配置.gradle、修改编译打包路径等。引入方式示例:
代码语言:txt复制apply from: "../libconfig.gradle"
二进制插件:脚本打成jar包等形式,已发布到仓库(maven等),常见的Java插件(生成jar包)、Android插件(生成apk、aar)等。引入方式示例:
代码语言:txt复制apply plugin: 'com.android.application'
apply plugin: 'groovy'
...
根目录build.gradle文件中,标签buildscript可为该项目配置构建相关路径,参数是Closure。dependencies是添加编译依赖项的,repositories是为脚本依赖项配置存储库。他们的配置都是用闭包的形式。
代码语言:txt复制buildscript {
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.4.1'
//等价于:implementation group: 'com.android.tools.build', name: 'gradle', version: '3.4.1'
}
}
allprojects {
repositories {
google()
jcenter()
}
}
更多插件类型:
- 应用程序插件,插件id为com.android.application,会生成一个APK。
- 库插件,插件id为com.android.library,会生成一个AAR,提供给其他应用程序模块用。
- 测试插件,插件id为com.android.test,用于测试其他的模块。
- feature插件,插件id为com.android.feature,创建Android Instant App时需要用到的插件。
- Instant App插件,插件id为com.android.instantapp,是Android Instant App的入口。
2、插件创建
首先创建module,如果命名为buildSrc,在本地工程中可以直接引入使用自创建的plugin;当然,发布到仓库供给他人使用的话就不用考虑这个命名限制。
这里创建MyPlugin.groovy,实现一个没有任何功能的插件。
- apply方法:插件被引入时需要执行的方法,可以自定义task操作
- Project参数:引入当前插件的project
import org.gradle.api.Plugin
import org.gradle.api.Project
//自定义插件
class MyPlugin implements Plugin<Project>{
@Override
void apply(Project project){
println 'pluginTest...' project.name
}
}
然后在resources/META-INF.gradle-plugins/com.game.plugin.testPlugin.properties
代码语言:txt复制implementation-class=com.game.testPlugin.MyPlugin
代码语言:txt复制//提交仓库到本地目录
uploadArchives {
repositories {
mavenDeployer {
pom.groupId = 'com.game.plugin'
pom.artifactId = 'testPlugin'
pom.version = '1.0.1'
repository(url: uri('../LocalRepo'))
}
}
}
再者,在根目录build.gradle,提供插件路径
代码语言:txt复制buildscript {
repositories {
google()
jcenter()
maven {
url uri('/LocalRepo')//添加依赖仓库
}
}
dependencies {
classpath "com.android.tools.build:gradle:3.4.1"
//依赖插件路径格式classpath '[groupId]:[artifactId]:[version]'
classpath "com.game.plugin:testPlugin:1.0.1"
}
}
在项目模块的build.gradle添加引用,
代码语言:txt复制apply plugin: 'com.game.plugin.testPlugin'
六、总结
1、编写gradle插件,比较重要的是对gradle生命周期的掌握,才能正确地去做自定义操作。生命周期的初始化阶段,完成所有工程的初始化,决定整个项目有多少子项目,重点是解析build.gradle文件;然后是配置阶段,build.gradle的代码基本都是运行在配置阶段,配置结束就开始真正执行task任务逻辑。
2、gradle核心模块的project,是脚本代码的入口,所有脚本代码实际都编写在project的实例中,每一个build.gradle对应一个project的实例,在build.gradle可以定位文件、获取root工程和管理子工程以及管理依赖;task才是真正执行逻辑的角色,可指定执行顺序和依赖,以插入自定义的task来完成特定功能,例如tinker将自己的task挂接到gradle生命周期的中间,去完成自己的功能。