安卓-Gradle浅谈

avatar

作者

大家好,我叫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),扩展的字符串可以是任意表达式,即“ ${ 任意表达式 } ”; 三引号,格式任意,不需要转义字符、指定输出。

示例代码:

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

输出结果:

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) 普通类型参数使用:

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闭包方法输出。

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()

输出结果:

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》 www.groovy-lang.org/differences…

2、闭包基础

闭包,实质上是一段代码块。 这里介绍闭包基础部分,主要包括内容:

  • 闭包概念:闭包的定义、调用
  • 闭包参数:普通参数、隐式参数
  • 闭包返回值:总是有返回值的

(1)定义

定义和调用:参数 ->执行体

//(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

输出结果:

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)使用

举例:字符串与闭包的结合使用

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()

输出结果:

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一致,可修改

这里在类或方法中定义闭包,

def scriptClouser = {
    println "scriptClouser this : " + this
    println "scriptClouser owner : " + owner 
    println "scriptClouser delegate : " + delegate 
}
scriptClouser.call()

输出结果:

scriptClouser this : pkg.character01@5be067de
scriptClouser owner : pkg.character01@5be067de
scriptClouser delegate : pkg.character01@5be067de

2)内部类相关

//定义内部类
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()

输出结果:

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指向会是当前类的某个具体对象。

class Person{
    def classClouser = {...}
    def say(){...}
}
//2、非static方法调用示例
Person innerPerson = new Person()
innerPerson.classClouser.call()
innerPerson.say()

输出结果:

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,示例如下。

//闭包中定义一个闭包,三者不一致
def nestClouser = {
    def innerClouser = {
        println "innerClouser this : " + this  
        println "innerClouser owner : " + owner 
        println "innerClouser delegate : " + delegate   
    }
    //innerClouser.delegate = innerPerson
    innerClouser.call()
}
nestClouser.call()

输出结果:

innerClouser this : pkg.character01@224b4d61
innerClouser owner : pkg.character01$_run_closure1@5ab14cb9
innerClouser delegate : pkg.character01$_run_closure1@5ab14cb9

单独指定delegate的话,三者的输出结果都会不一样:

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指向对象中查询。

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()

输出结果:

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进行全局配置,在文件添加:

println '初始化开始...'

然后是配置、执行阶段的监听。在根目录build.gradle添加:

//配置阶段监听(本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/'
            }
        }
    }
}

还可以添加其他监听:

//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 '执行阶段结束 '
    }
})

执行结果:

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这样,见缝插针式地监听生命周期并执行自定义操作。

...
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)

...
> Task :createTask
task ':createTask' 开始执行...
this group : 37sdk
Task created successfully
task ':createTask' 执行结束...
...

更多:build下面有很多assembleXxx任务,是根据buildType和productFlavor的不同自动创建多个。

  • 创建方式2:通过taskContainer去创建,然后在闭包中配置属性

      this.tasks.create(name:'helloTask2'){
          setGroup('37sdk')
          setDescription('task study')
          setBuildDir('build/outputs/helloTask3/')
          println 'hello task2'
      }
    

可指定的参数类型,见Task.class:

       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{ } );另外,同是执行阶段,不同调用方式的执行顺序会有差别。

示例代码:

// 使用 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"
}

执行结果如下:

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

//注意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'
    }
}

输出结果:

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配置仓库路径

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)应用插件

apply plugin: 'cz.malohlava.visteg'

(3)visteg属性配置,重要是enabled(启用插件)和destination(输出文件路径)

    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

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的版本号、图标、活动主题等内容,以及新增参数如等。掌握了一种可行修改方式后,其他处理也可以依样进行。

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中添加参数。同理,补充权限声明也是同样的方式等

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、修改编译打包路径等。引入方式示例:

apply from: "../libconfig.gradle"

二进制插件:脚本打成jar包等形式,已发布到仓库(maven等),常见的Java插件(生成jar包)、Android插件(生成apk、aar)等。引入方式示例:

apply plugin: 'com.android.application'
apply plugin: 'groovy'
...

根目录build.gradle文件中,标签buildscript可为该项目配置构建相关路径,参数是Closure。dependencies是添加编译依赖项的,repositories是为脚本依赖项配置存储库。他们的配置都是用闭包的形式。

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

implementation-class=com.game.testPlugin.MyPlugin
//提交仓库到本地目录
uploadArchives {
    repositories {
        mavenDeployer {
            pom.groupId = 'com.game.plugin'
            pom.artifactId = 'testPlugin'
            pom.version = '1.0.1'
            repository(url: uri('../LocalRepo'))
        }
    }
}

再者,在根目录build.gradle,提供插件路径

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添加引用,

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生命周期的中间,去完成自己的功能。