前言
我們的項目打包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"
}
}
最終目錄結構如下:
(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文檔。