Gradle Task的使用

前言

我们的项目打包APK前需要根据业务需要更改AndroidManifest文件内容和替换so文件来生成不同的apk。这样就需要手动来做这些事情以实现对应的需求。

手动修改的弊端
1.因为改动地方比较多,所以很容易出错或出现遗漏。
2.改动需要时间,生产效率低下。
3.对于不熟悉业务的人来说,修改起来比较困惑。

那既然这样有没有一种方法,通过一些指令来完成这些既繁琐又容易出错的重复性手工作业呢,这就是今天要介绍的Gradle Task了。

Task介绍

一个Task代表一个构建工作的原子操作,例如编译calsses或者生成javadoc。
Gradle中,每一个待编译的工程都叫一个Project。每一个Project在构建的时候都包含一系列的Task。比如一个Android APK的编译可能包含:Java源码编译Task、资源编译Task、JNI编译Task、lint检查Task、打包生成APK的Task、签名Task等。插件本身就是包含了若干Task的。
如下就是一个task的简单例子:

task hello {
        println 'Hello world!'
}

在AS的Terminal窗口输入命令

xxx\xxx>gradlew hello

执行结果如下:

Hello world!                           

BUILD SUCCESSFUL     

Total time: 2.163 secs   

更多用法

task myTask
task myTask { configure closure }  // closure是一个闭包
task myType << { task action }    // <<符号是doLast的缩写  
task myTask(type: SomeType)   // SomeType可以指定任务类型,Gradle本身提供有Copy、Delete、Sync等
task myTask(type: SomeType) { configure closure }
  • 一个Task包含若干Action。所以,Task有doFirst和doLast两个函数,用于添加需要最先执行的Action和需要和需要最后执行的Action。Action就是一个闭包。闭包,英文叫Closure,是Groovy中非常重要的一个数据类型或者说一种概念。
  • Task创建的时候可以通过 type: SomeType 指定Type,Type其实就是告诉Gradle,这个新建的Task对象会从哪个基类Task派生。比如,Gradle本身提供了一些通用的Task,最常见的有Copy 任务。Copy是Gradle中的一个类。当我们:task myTask(type:Copy)的时候,创建的Task就是一个Copy Task。
  • 当我们使用 taskmyTask{ xxx}的时候,花括号就是一个closure。
  • 当我们使用taskmyTask << {xxx}的时候,我们创建了一个Task对象,同时把closure做为一个action加到这个Task的action队列中,并且告诉它“最后才执行这个closure”

Task的API文档:https://docs.gradle.org/current/dsl/org.gradle.api.Task.html

Type

Copy
将文件复制到目标目录。此任务在复制时也可以执行重命名和过滤文件操作。它实现了CopySpec接口,使用CopySpec.from()方法可以指定源文件,CopySpec.into()方法可以指定目标目录。
例子:

task copyDocs(type: Copy) {
    from 'src/main/doc'
    into 'build/target/doc'
}

//这是个Ant filter
import org.apache.tools.ant.filters.ReplaceTokens

//这是一个闭包
def dataContent = copySpec {
    from 'src/data'
    include '*.data'
}

task initConfig(type: Copy) {
    from('src/main/config') {
        include '**/*.properties'
        include '**/*.xml'
        filter(ReplaceTokens, tokens: [version: '2.3.1'])
    }
    from('src/main/config') {
        exclude '**/*.properties', '**/*.xml'
    }
    from('src/main/languages') {
        rename 'EN_US_(.*)', '$1'
    }
    into 'build/target/config'
    exclude '**/*.bak'

    includeEmptyDirs = false

    with dataContent
}

Copy的API文档:https://docs.gradle.org/current/dsl/org.gradle.api.tasks.Copy.html

使用Copy解决我们项目中的问题

替换AndroidManifest文件

task chVer(type: Copy) { // 指定Type为Copy任务
    from "src/main/manifest/AndroidManifestCopy.xml"  // 复制src/main/manifest/目录下的AndroidManifest.xml
    into 'src/main'  // 复制到指定目标目录
    rename { String fileName -> //在复制时重命名文件
        fileName = "AndroidManifest.xml" // 重命名
    }

}

替换so文件

task chSo(type: Copy) {
    from "src/main/jniLibs/test"   // 复制test文件夹下的所有so文件
    into "src/main/jniLibs/armeabi-v7a" //复制到armeabi-v7a文件夹下
}

这样每次打包APK前执行以上任务就可以自动替换文件啦!

问:那如果有多个任务需要执行是不是要执行多次任务呢?
答:可以通过多任务命令调用一次即可。

gradlew task1 task2 [...]

问:任务名太长不想输入这么多字怎么办?
答:可以采用简化操作,但是必须保证可以唯一区分出该任务的字符,如:

gradlew cV

问:那我不想每次打包前都输入命令怎么办?
答:可以每次build时自动执行自定义任务。

afterEvaluate {
    tasks.matching {
        // 以process开头以ReleaseJavaRes或DebugJavaRes结尾的task
        it.name.startsWith('process') && (it.name.endsWith('ReleaseJavaRes') || it.name.endsWith
                ('DebugJavaRes'))
   }.each { task ->
        task.dependsOn(chVer, chSo)  // 任务依赖:执行task之前需要执行dependsOn指定的任务
    }
}

完整的build.gradle代码:

apply plugin: 'com.android.application'

android {

    compileSdkVersion 23
    buildToolsVersion "23.0.3"


    defaultConfig {
        applicationId "com.skr.voip"
        minSdkVersion 15
        targetSdkVersion 19
    }

    buildTypes {

        debug {
            minifyEnabled false
        }

        release {
            minifyEnabled true
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
        }
    }
}

dependencies {
    //    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.android.support:support-v4:23.4.0'
    ...
   
}

task chVer(type: Copy) {
    from "src/main/manifest/AndroidManifestCopy.xml"  // 复制src/main/manifest/目录下的AndroidManifest.xml
    into 'src/main'  // 复制到指定目标目录
    rename { String fileName -> //在复制时重命名文件
        fileName = "AndroidManifest.xml" // 重命名
    }

}

task chSo(type: Copy) {
    from "src/main/jniLibs/test"   // 复制test文件夹下的所有文件
    into "src/main/jniLibs/armeabi-v7a" //复制到armeabi-v7a文件夹下
}

afterEvaluate {
    tasks.matching {
        // 以process开头以ReleaseJavaRes或DebugJavaRes结尾的task
        it.name.startsWith('process') && (it.name.endsWith('ReleaseJavaRes') || it.name.endsWith
                ('DebugJavaRes'))
   }.each { task ->
        task.dependsOn(chVer, chSo)  // 任务依赖:执行task之前需要执行dependsOn指定的任务
    }
}


Sync

此任务与Copy任务类似,唯一的区别是当执行时会复制源文件到目标目录,目标目录中所有非复制文件将会被删除,除非指定Sync.preserve(org.gradle.api.Action)。
例子:

task syncDependencies(type: Sync) {
    from 'my/shared/dependencyDir'
    into 'build/deps/compile'
}

// 你可以保护目标目录已经存在的文件。匹配的文件将不会被删除。
task sync(type: Sync) {
    from 'source'
    into 'dest'
    preserve {
        include 'extraDir/**'
        include 'dir1/**'
        exclude 'dir1/extra.txt'
    }
}

Sync的API文档:https://docs.gradle.org/current/dsl/org.gradle.api.tasks.Sync.html

Zip

创建ZIP归档文件,默认压缩文件类型为zip。
例子:

task zip(type: Zip) {
    from 'src/dist'
    into('libs') 
}

Zip的API文档:https://docs.gradle.org/current/dsl/org.gradle.api.tasks.bundling.Zip.html

更多Tpye可参考Task的API文档

自定义Task

上面介绍的都是Gradle默认提供的Task,而在有些时候,我们希望创建一些具有特定功能的Task,这时我们可以自己定义Task。

在Gradle中,我们有3种方法可以自定义Task。

(1)在build.gradle文件中定义
Gradle使用的是Groovy代码,所以在build.gradle文件中,我们便可以定义Task类。

// 需要继承自DefaultTask
class HelloWorldTask extends DefaultTask {
    // @Optional 表示在配置该Task时,message是可选的。
    @Optional
    String message = 'I am kaku'
    // @TaskAction 表示该Task要执行的动作,即在调用该Task时,hello()方法将被执行
    @TaskAction
    def hello(){
        println "hello world $message"
    }
}

// hello使用了默认的message值
task hello(type:HelloWorldTask)

// 重新设置了message的值
task helloOne(type:HelloWorldTask){
   message ="I am a android developer"
}

(2)在当前工程中定义
当项目中自定义Task类型比较多时,可以将自定义Task写在buildSrc项目中。
具体做法为:在项目的根目录下新建一个名为buildSrc文件夹,然后依次新建子目录src/main/groovy,然后可以建自己的包名,这里以demo.gradle.task为例,依次新建子目录demo/gradle/task,然后在buildSrc根目录下新建build.gradle文件,里面写入:

 apply plugin: 'groovy'

 dependencies {
    compile gradleApi()
    compile localGroovy()
}

接着在demo.gradle.task包下,创建HelloWorldTask.groovy文件,将(1)中的HelloWorldTask部分代码粘贴过来。

import org.gradle.api.DefaultTask
import org.gradle.api.tasks.Optional
import org.gradle.api.tasks.TaskAction

class HelloWorldTask extends DefaultTask {
    @Optional
    String message = 'I am kaku'

    @TaskAction
    def hello() {
        println "hello world $message"
    }
}

最终目录结构如下:

Paste_Image.png

(3)在单独的项目中定义
当自定义的Task需要能够提供给其他项目中使用时,可以通过声明依赖的方式引入Task。
具体做法为: 创建一个项目,将(2)中的buildSrc目录下的内容copy到新建项目中,然后将该项目生成的jar文件上传到repository中。
build.gradle如下:

apply plugin: 'groovy'
apply plugin: 'maven'
version = '1.0'
group = 'skr'
archivesBaseName = 'hellotask'

repositories.mavenCentral()

dependencies {
    compile gradleApi()
    compile localGroovy()
}

uploadArchives {
    repositories.mavenDeployer {
        repository(url: 'file:../lib')
    }
}

执行 gradlew uploadArchives ,所生成的jar文件将被上传到上级目录的lib(../lib)文件夹中。

在使用该HelloWorldTask时,客户端的build.gradle文件需要做以下配置:

buildscript {
    repositories {
        maven {
            url 'file:../lib'
        }

    }

    dependencies {
        classpath group: 'skr', name: 'hellotask', version: '1.0'
    }
}


task hello(type: HelloWorldTask)

自定义Plugin

与自定义Task相似,也是3种定义方式,只是代码不一样:

apply plugin: DateAndTimePlugin

dateAndTime {
    timeFormat = 'HH:mm:ss.SSS'
    dateFormat = 'MM/dd/yyyy'
}

// 每一个自定义的Plugin都需要实现Plugin<T>接口
class DateAndTimePlugin implements Plugin<Project> {
    //该接口定义了一个apply()方法,在该方法中,我们可以操作Project,
    //比如向其中加入Task,定义额外的Property等。
    void apply(Project project) {
        project.extensions.create("dateAndTime", DateAndTimePluginExtension)
        //每个Gradle的Project都维护了一个ExtenionContainer,
        //我们可以通过project.extentions进行访问
        //比如读取额外的Property和定义额外的Property等。
        project.task('showTime') << {
            println "Current time is " + new Date().format(project.dateAndTime.timeFormat)
        }

        project.tasks.create('showDate') << {
            println "Current date is " + new Date().format(project.dateAndTime.dateFormat)
        }
    }
}

//向Project中定义了一个名为dateAndTime的extension
//并向其中加入了2个Property,分别为timeFormat和dateFormat
class DateAndTimePluginExtension {
    String timeFormat = "MM/dd/yyyyHH:mm:ss.SSS"
    String dateFormat = "yyyy-MM-dd"
}

至此基础的Gradle Task使用就介绍完了,深入的请自行查阅相关API文档。

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

推荐阅读更多精彩内容