Gradle Task的使用

前言

我們的項(xiàng)目打包APK前需要根據(jù)業(yè)務(wù)需要更改AndroidManifest文件內(nèi)容和替換so文件來生成不同的apk扼褪。這樣就需要手動(dòng)來做這些事情以實(shí)現(xiàn)對應(yīng)的需求。

手動(dòng)修改的弊端
1.因?yàn)楦膭?dòng)地方比較多粱栖,所以很容易出錯(cuò)或出現(xiàn)遺漏。
2.改動(dòng)需要時(shí)間闹究,生產(chǎn)效率低下幔崖。
3.對于不熟悉業(yè)務(wù)的人來說渣淤,修改起來比較困惑。

那既然這樣有沒有一種方法价认,通過一些指令來完成這些既繁瑣又容易出錯(cuò)的重復(fù)性手工作業(yè)呢嗅定,這就是今天要介紹的Gradle Task了刻伊。

Task介紹

一個(gè)Task代表一個(gè)構(gòu)建工作的原子操作椒功,例如編譯calsses或者生成javadoc。
Gradle中智什,每一個(gè)待編譯的工程都叫一個(gè)Project。每一個(gè)Project在構(gòu)建的時(shí)候都包含一系列的Task荠锭。比如一個(gè)Android APK的編譯可能包含:Java源碼編譯Task旱眯、資源編譯Task证九、JNI編譯Task、lint檢查Task愧怜、打包生成APK的Task呀页、簽名Task等。插件本身就是包含了若干Task的蓬蝶。
如下就是一個(gè)task的簡單例子:

task hello {
        println 'Hello world!'
}

在AS的Terminal窗口輸入命令

xxx\xxx>gradlew hello

執(zhí)行結(jié)果如下:

Hello world!                           

BUILD SUCCESSFUL     

Total time: 2.163 secs   

更多用法

task myTask
task myTask { configure closure }  // closure是一個(gè)閉包
task myType << { task action }    // <<符號是doLast的縮寫  
task myTask(type: SomeType)   // SomeType可以指定任務(wù)類型,Gradle本身提供有Copy猜惋、Delete、Sync等
task myTask(type: SomeType) { configure closure }
  • 一個(gè)Task包含若干Action著摔。所以缓窜,Task有doFirst和doLast兩個(gè)函數(shù)谍咆,用于添加需要最先執(zhí)行的Action和需要和需要最后執(zhí)行的Action。Action就是一個(gè)閉包卧波。閉包时肿,英文叫Closure港粱,是Groovy中非常重要的一個(gè)數(shù)據(jù)類型或者說一種概念。
  • Task創(chuàng)建的時(shí)候可以通過 type: SomeType 指定Type查坪,Type其實(shí)就是告訴Gradle寸宏,這個(gè)新建的Task對象會(huì)從哪個(gè)基類Task派生偿曙。比如,Gradle本身提供了一些通用的Task望忆,最常見的有Copy 任務(wù)罩阵。Copy是Gradle中的一個(gè)類。當(dāng)我們:task myTask(type:Copy)的時(shí)候稿壁,創(chuàng)建的Task就是一個(gè)Copy Task幽钢。
  • 當(dāng)我們使用 taskmyTask{ xxx}的時(shí)候傅是,花括號就是一個(gè)closure匪燕。
  • 當(dāng)我們使用taskmyTask << {xxx}的時(shí)候喧笔,我們創(chuàng)建了一個(gè)Task對象,同時(shí)把closure做為一個(gè)action加到這個(gè)Task的action隊(duì)列中书闸,并且告訴它“最后才執(zhí)行這個(gè)closure”

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

Type

Copy
將文件復(fù)制到目標(biāo)目錄尼变。此任務(wù)在復(fù)制時(shí)也可以執(zhí)行重命名和過濾文件操作梗劫。它實(shí)現(xiàn)了CopySpec接口截碴,使用CopySpec.from()方法可以指定源文件梳侨,CopySpec.into()方法可以指定目標(biāo)目錄日丹。
例子:

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

//這是個(gè)Ant filter
import org.apache.tools.ant.filters.ReplaceTokens

//這是一個(gè)閉包
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解決我們項(xiàng)目中的問題

替換AndroidManifest文件

task chVer(type: Copy) { // 指定Type為Copy任務(wù)
    from "src/main/manifest/AndroidManifestCopy.xml"  // 復(fù)制src/main/manifest/目錄下的AndroidManifest.xml
    into 'src/main'  // 復(fù)制到指定目標(biāo)目錄
    rename { String fileName -> //在復(fù)制時(shí)重命名文件
        fileName = "AndroidManifest.xml" // 重命名
    }

}

替換so文件

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

這樣每次打包APK前執(zhí)行以上任務(wù)就可以自動(dòng)替換文件啦!

問:那如果有多個(gè)任務(wù)需要執(zhí)行是不是要執(zhí)行多次任務(wù)呢哲虾?
答:可以通過多任務(wù)命令調(diào)用一次即可丙躏。

gradlew task1 task2 [...]

問:任務(wù)名太長不想輸入這么多字怎么辦?
答:可以采用簡化操作束凑,但是必須保證可以唯一區(qū)分出該任務(wù)的字符,如:

gradlew cV

問:那我不想每次打包前都輸入命令怎么辦汪诉?
答:可以每次build時(shí)自動(dòng)執(zhí)行自定義任務(wù)废恋。

afterEvaluate {
    tasks.matching {
        // 以process開頭以ReleaseJavaRes或DebugJavaRes結(jié)尾的task
        it.name.startsWith('process') && (it.name.endsWith('ReleaseJavaRes') || it.name.endsWith
                ('DebugJavaRes'))
   }.each { task ->
        task.dependsOn(chVer, chSo)  // 任務(wù)依賴:執(zhí)行task之前需要執(zhí)行dependsOn指定的任務(wù)
    }
}

完整的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"  // 復(fù)制src/main/manifest/目錄下的AndroidManifest.xml
    into 'src/main'  // 復(fù)制到指定目標(biāo)目錄
    rename { String fileName -> //在復(fù)制時(shí)重命名文件
        fileName = "AndroidManifest.xml" // 重命名
    }

}

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

afterEvaluate {
    tasks.matching {
        // 以process開頭以ReleaseJavaRes或DebugJavaRes結(jié)尾的task
        it.name.startsWith('process') && (it.name.endsWith('ReleaseJavaRes') || it.name.endsWith
                ('DebugJavaRes'))
   }.each { task ->
        task.dependsOn(chVer, chSo)  // 任務(wù)依賴:執(zhí)行task之前需要執(zhí)行dependsOn指定的任務(wù)
    }
}


Sync

此任務(wù)與Copy任務(wù)類似扒寄,唯一的區(qū)別是當(dāng)執(zhí)行時(shí)會(huì)復(fù)制源文件到目標(biāo)目錄,目標(biāo)目錄中所有非復(fù)制文件將會(huì)被刪除该编,除非指定Sync.preserve(org.gradle.api.Action)迄本。
例子:

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

// 你可以保護(hù)目標(biāo)目錄已經(jīng)存在的文件课竣。匹配的文件將不會(huì)被刪除置媳。
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

創(chuàng)建ZIP歸檔文件,默認(rèn)壓縮文件類型為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默認(rèn)提供的Task,而在有些時(shí)候赃份,我們希望創(chuàng)建一些具有特定功能的Task寂拆,這時(shí)我們可以自己定義Task抓韩。

在Gradle中,我們有3種方法可以自定義Task谒拴。

(1)在build.gradle文件中定義
Gradle使用的是Groovy代碼尝江,所以在build.gradle文件中,我們便可以定義Task類炭序。

// 需要繼承自DefaultTask
class HelloWorldTask extends DefaultTask {
    // @Optional 表示在配置該Task時(shí),message是可選的苍日。
    @Optional
    String message = 'I am kaku'
    // @TaskAction 表示該Task要執(zhí)行的動(dòng)作,即在調(diào)用該Task時(shí),hello()方法將被執(zhí)行
    @TaskAction
    def hello(){
        println "hello world $message"
    }
}

// hello使用了默認(rèn)的message值
task hello(type:HelloWorldTask)

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

(2)在當(dāng)前工程中定義
當(dāng)項(xiàng)目中自定義Task類型比較多時(shí)相恃,可以將自定義Task寫在buildSrc項(xiàng)目中辜纲。
具體做法為:在項(xiàng)目的根目錄下新建一個(gè)名為buildSrc文件夾,然后依次新建子目錄src/main/groovy,然后可以建自己的包名拦耐,這里以demo.gradle.task為例,依次新建子目錄demo/gradle/task,然后在buildSrc根目錄下新建build.gradle文件杀糯,里面寫入:

 apply plugin: 'groovy'

 dependencies {
    compile gradleApi()
    compile localGroovy()
}

接著在demo.gradle.task包下扫俺,創(chuàng)建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"
    }
}

最終目錄結(jié)構(gòu)如下:

Paste_Image.png

(3)在單獨(dú)的項(xiàng)目中定義
當(dāng)自定義的Task需要能夠提供給其他項(xiàng)目中使用時(shí),可以通過聲明依賴的方式引入Task倦挂。
具體做法為: 創(chuàng)建一個(gè)項(xiàng)目,將(2)中的buildSrc目錄下的內(nèi)容copy到新建項(xiàng)目中方援,然后將該項(xiàng)目生成的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')
    }
}

執(zhí)行 gradlew uploadArchives ,所生成的jar文件將被上傳到上級目錄的lib(../lib)文件夾中送火。

在使用該HelloWorldTask時(shí),客戶端的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'
}

// 每一個(gè)自定義的Plugin都需要實(shí)現(xiàn)Plugin<T>接口
class DateAndTimePlugin implements Plugin<Project> {
    //該接口定義了一個(gè)apply()方法坚俗,在該方法中,我們可以操作Project猖败,
    //比如向其中加入Task速缆,定義額外的Property等恩闻。
    void apply(Project project) {
        project.extensions.create("dateAndTime", DateAndTimePluginExtension)
        //每個(gè)Gradle的Project都維護(hù)了一個(gè)ExtenionContainer艺糜,
        //我們可以通過project.extentions進(jìn)行訪問
        //比如讀取額外的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中定義了一個(gè)名為dateAndTime的extension
//并向其中加入了2個(gè)Property,分別為timeFormat和dateFormat
class DateAndTimePluginExtension {
    String timeFormat = "MM/dd/yyyyHH:mm:ss.SSS"
    String dateFormat = "yyyy-MM-dd"
}

至此基礎(chǔ)的Gradle Task使用就介紹完了尉剩,深入的請自行查閱相關(guān)API文檔真慢。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末边涕,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子功蜓,更是在濱河造成了極大的恐慌,老刑警劉巖宠蚂,帶你破解...
    沈念sama閱讀 218,682評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異求厕,居然都是意外死亡著隆,警方通過查閱死者的電腦和手機(jī)呀癣,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來项栏,“玉大人浦辨,你說我怎么就攤上這事×鞒辏” “怎么了币厕?”我有些...
    開封第一講書人閱讀 165,083評論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長旦装。 經(jīng)常有香客問我,道長摊滔,這世上最難降的妖魔是什么阴绢? 我笑而不...
    開封第一講書人閱讀 58,763評論 1 295
  • 正文 為了忘掉前任艰躺,我火速辦了婚禮,結(jié)果婚禮上描滔,老公的妹妹穿的比我還像新娘棒妨。我一直安慰自己含长,他們只是感情好券腔,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,785評論 6 392
  • 文/花漫 我一把揭開白布拘泞。 她就那樣靜靜地躺著,像睡著了一般陪腌。 火紅的嫁衣襯著肌膚如雪辱魁。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,624評論 1 305
  • 那天染簇,我揣著相機(jī)與錄音,去河邊找鬼强岸。 笑死,一個(gè)胖子當(dāng)著我的面吹牛蝌箍,可吹牛的內(nèi)容都是我干的青灼。 我是一名探鬼主播,決...
    沈念sama閱讀 40,358評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼诚镰,長吁一口氣:“原來是場噩夢啊……” “哼垫言!你這毒婦竟也來了蒂教?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,261評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎贷币,沒想到半個(gè)月后击胜,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體役纹,經(jīng)...
    沈念sama閱讀 45,722評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年促脉,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了辰斋。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,030評論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡宫仗,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出旁仿,到底是詐尸還是另有隱情,我是刑警寧澤枯冈,帶...
    沈念sama閱讀 35,737評論 5 346
  • 正文 年R本政府宣布毅贮,位于F島的核電站,受9級特大地震影響滩褥,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜炫加,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,360評論 3 330
  • 文/蒙蒙 一瑰煎、第九天 我趴在偏房一處隱蔽的房頂上張望俗孝。 院中可真熱鬧,春花似錦驹针、人聲如沸烘挫。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,941評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽其垄。三九已至苛蒲,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間绿满,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,057評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留漏健,地道東北人嚎货。 一個(gè)月前我還...
    沈念sama閱讀 48,237評論 3 371
  • 正文 我出身青樓蔫浆,卻偏偏與公主長得像,于是被迫代替她去往敵國和親瓦盛。 傳聞我的和親對象是個(gè)殘疾皇子洗显,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,976評論 2 355

推薦閱讀更多精彩內(nèi)容