Gradle Task的使用

作者: 咖枯 | 来源:发表于2016-11-13 19:37 被阅读11484次

    前言

    我们的项目打包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文档。

    相关文章

      网友评论

      • Ronnie_火老师:不错,学到了一些gradle的用法,谢谢
      • 望山观海:写的挺全面的,感谢作者的总结
      • 妙法莲花1234:最好使用 ./gradlew 即当前项目的 gradle 避免 gradle 版本不合要求情况。当然,前提是当前项目可正常编译运行 :smile:
        咖枯:@追风917 多谢赐教,涨姿势啦😄
      • 妙法莲花1234:玩的很6啊,不错不错 :smile:
      • mrwangyong:不错 加油

      本文标题:Gradle Task的使用

      本文链接:https://www.haomeiwen.com/subject/pzyxpttx.html