前言
我們的項(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)如下:
(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文檔真慢。