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閱讀 230,247評論 6 543
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,520評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,362評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,805評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,541評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,896評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,887評論 3 447
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,062評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,608評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,356評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,555評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,077評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,769評論 3 349
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,175評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,489評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,289評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,516評論 2 379

推薦閱讀更多精彩內容