Android Gradle Groovy自动化构建进阶篇

Gradle系列分2章
上篇Android Gradle Groovy自动化构建入门篇
下篇Android Gradle Groovy自动化构建进阶篇

Android Gradle Groovy自动化构建进阶篇

上篇,我们已经介绍了Gradle的基本语法,接下来让我们一起学习下Gradle高级知识:构建脚本,自定义任务,构建生命周期,解决依赖冲突,多项目构建等高阶技巧。

接下来,我们先看下gradle的几个个简单应用

  • 由于项目越来越大,组件化盛行,有时我们不得不多module统一版本管理。一般做法是:
  1. 在 project 根目录新建****.gradle 文件;
  2. 通过apply from引入该配置文件,然后使用 rootProject.ext引入相关属性.
//1.比如这里新建文件为config.gradle
ext {  
    versions = [  
            sdkMinVersion     : 14,  
            sdkTargetVersion  : 26,  
            ... 
    ]  
   
    depVersion = [  
            appCompatVersion : "26.+",  
            recyclerViewVersion : "26.0.0-alpha1"  
    ]  

    deps = [  
        suport : [  
                appcompat   : "com.android.support:appcompat-v7:${depVersion.appCompatVersion}",  
                recyclerview: "com.android.support:recyclerview-v7:${depVersion.recyclerViewVersion}"  
        ]  
    ]  
}  

// 2. 引入已声明好的属性
apply from: 'config/config.gradle' 
android {  
    def versions = rootProject.ext.versions  
    compileSdkVersion versions.sdkCompileVersion  
    buildToolsVersion versions.toolsBuildVersion  
     ...
  }  
  
dependencies {  
    def dependencies = rootProject.ext.deps  
    compile dependencies.suport.appcompat  
}

  • 在比如我们经常看到的这个错误:
Error:Execution failed for task ':test:processDebugManifest'.> Manifest merger failed with multiple errors, see logs

我们一般的解决方案为:命令行输入gradlew processDebugManifest --stacktrace.

  • 最后我们在看个简单的单机执行任务


    -w523
  • 由于开发过程中经常导入第三方jar包,一不小心就报jar包冲突这,这时我们会执行
    gradle app:dependencies 查看app重复依赖,然后在通过exclude剔除重复的jar。

compile ('com.android.support:design:22.2.1')
            {
                exclude group: 'com.android.support'
            }

其实当我们点击后Gradle会去寻找当前目录下的 build.gradle 的文件,这个文件是 Gradle 的脚本文件,它里面定义了工程和工程拥有的所有任务等信息。然后执行相关task。下面我们一起来一步步揭开它的神秘面纱吧。

Gradle 中的工程( Project )和任务( Task )

就像上面的截图一样,我们知道,每一个 Gradle 的项目都会包含一个或多个工程,每一个工程又由一个或多个任务组成,一个任务代表了一个工作的最小单元,它可以是一次类的编译、打一个 JAR 包、生成一份 Javadoc 或者是向仓库中提交一次版本发布。

任务的定义和使用

在任务中,我么可以利用dependsOn定义依赖关系,doFirstdoLast对现有任务增强。
我们还是使用 IDEA 开发工具打开之前的项目工程,把之前 build.gradle 文件中所有的内容全部删除,编写输入如下代码

task hello {
    doLast {
        println 'Hello world!'
    }
}

task release() {
    doLast {
        println "I'm release task"
    }
}

// 添任务依赖关系
release.dependsOn hello

//对现有的任务增强
// 法方一,在doFirst动作中添加
hello.doFirst {
    println 'Hello doFirst'
}
// 法方二 在doLast动作中添加
hello.doLast {
    println 'Hello doLast'
}

打开命令行端终,执行命令:gradle -q release,输出结果如下:

-w558

另外我们还可以为任务设置属性,主要通过 ext.myProperty 来初始化值,如下所示

task myTask {
    ext.myProperty = "myValue"
}

task printTaskProperties {
    doLast {
        println myTask.myProperty
    }
}
命令行执行 ➜  gradle -q printTaskProperties
myValue

当然我们可以对现有任务进行配置:禁用或者重写。
首先我们定义一个 myCopy 的任务,代码如下:

task myCopy(type: Copy) {
   from 'resources'
   into 'target'
   include('**/*.txt', '**/*.xml', '**/*.properties')
}

类似java有API文档,Gradle也有类似文档,Gradle 中很多其它常用的任务,小伙伴们可以点击查看
如果我们想重写 copy任务可通过overwrite属性为 true 来现如下所示:

task copy(type: Copy)

task copy(overwrite: true) {
    doLast {
        println('overwrite the copy.')
    }
}
命令行执行 
> gradle -q copy
overwrite the copy.

最后我们看下如何禁用某些任务。直接看代码吧

copy.enabled = false

实战篇

接下来我们来自定义插件,有三种方式来编写

  1. 在我们构建项目的 build.gradle 脚本中直接编写,这种方式的好处是插件会自动被编译加载到我们的 classpath 中,但是它有很明显的局限性,就是除了在包括它的脚本外别的地方无法复用。

  2. 在我们构建项目的rootProjectDir/buildSrc/src/main/groovy 目录下编写,Gradle 会自动编译到当前项目的 classpath 中,该项目下所有编译脚本都可以使用,但是除了当前项目之外的都无法复用。

  3. 以单独的工程方式编写,这个工程最终编译发布为一个 JAR 包,它可以在多个项目或不同的团队中共享使用。

接下来我们一步步把按照第三种方式写个Demo吧。

首先使用IDEA新建gradle 工程选择groovy(跟上面一样就不细说了),然后按照下面截图新建src/main/groovy/你的包名,接着在resources 目录下建立 META-INF/gradle-plugins 文件夹,在其中新建 hello.properties 文件,敲黑板注意此处文件名,就是以后使用时要用的名字。此处是hello,所以我们得按照这样引入apply plugin: 'hello',里面像这样输入插件的全路径名字:implementation-class=org.gradle.HelloPlugin

-w1092

接下来分 2 步编写代码:

  1. 继承自DefaultTask的,使用TaskAction进行标注,这样 Gradle 就会在任务执行的时候默认调用它
  2. 然后通过实现Plugin接口来实现自定义插件类,实现apply(Project project) 方法。

按照步骤,首先我们新建MyTask.groovy文件,里面仅仅是简单声明了一个成员变量,然后打印。

class MyTask extends DefaultTask {
    String input = 'hello from MyTask'

    @TaskAction
    def greet() {
        println input
    }
}

然后我们新建Hello.groovy文件.我们向这个plugin添加了一个hello任务,我们知道gradle中可以配置参数比如:defaultConfig {} ndk {}等,其实gradle是使用 extension objects来现实给插件传参,具体实现看下面代码的注释:

class Hello implements Plugin<Project> {
    @Override
    void apply(Project project) {
        // 向extension container保存para参数,并应用给HelloPluginExtension
        project.extensions.create("para", HelloPluginExtension)
        // 向project对象添加hello任务
        project.task('hello',type:MyTask) {
            input = 'Hello Plugin input!'
            doLast {
                println "${project.para.first}${project.para.last}"
            }
        }
    }
}

接下来发布工程到本地仓库,供其他项目使用,在build.gradle中输入

//使用 maven-publish 插件先发布到本地
apply plugin: 'maven-publish'
publishing{
    publications {
        mavenJava(MavenPublication) {
            from components.java

            groupId 'org.gradle'
            artifactId 'customPlugin'
            version '1.0-SNAPSHOT'

        }
    }

    repositories{
        maven {
            // change to point to your repo, e.g. http://my.org/repo
            url "../repo"
        }
    }
}

点击publish任务,看到成功发布工程到本地仓库../repo中了。


-w1215

最后用 IDEA 开发工具新创建一个 Gradle 工程来验证我们的插件。看到右侧gradle任务中多了我们添加的hello任务,点击查看成功输出了Hello world。


-w1086

补充及常见问题

文件树是有层级结构的文件集合,一个文件树它可以代表一个目录结构或一 ZIP 压缩包中的内容结构。使用Project.fileTree(java.util.Map)创建,可以使用过虑条件来包含或排除相关文件。

// 指定目录创建文件树对象
FileTree tree = fileTree(dir: 'src/main')
// 给文件树对象添加包含指定文件
tree.include '**/*.java'
//andoid中使用文件树
implementation fileTree(include: ['*.jar'], dir: 'libs')

多module的编译配置

注意 settings.gradle引入的module才会参与编译include ':app', ':plugin_common', ':plugin_gallery',可以在跟guild.gradle 中统一设置公共行为;比如下图添加一个hello任务。

-w1615

一个工程的路径为:以冒号(: 它代表了根工程)开始,再加上工程的名称。例如“:common”。
一个任务的路径为:工程路径加上任务名称,例如“:common:hello”.
比如:仅仅执行 gradle :plugin_common:hello

gradle :plugin_common:dependencies

源码下载

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,937评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,503评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,712评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,668评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,677评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,601评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,975评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,637评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,881评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,621评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,710评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,387评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,971评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,947评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,189评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,805评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,449评论 2 342

推荐阅读更多精彩内容