Android Gradle Groovy自動化構建進階篇

klven發表於2019-06-11

Gradle系列分2章
上篇Android Gradle Groovy自動化構建入門篇
下篇Android Gradle Groovy自動化構建進階篇

上篇,我們已經介紹了Gradle的基本語法,接下來讓我們一起學習下Gradle高階知識:構建指令碼,自定義任務,構建生命週期,解決依賴衝突,多專案構建等高階技巧。

接下來,我們先看下gradle的幾個個簡單應用

  • 由於專案越來越大,元件化盛行,有時我們不得不多module統一版本管理。一般做法是:
  1. 在 project 根目錄新建****.gradle 檔案;
  2. 通過apply from引入該配置檔案,然後使用 rootProject.ext引入相關屬性.
//1.比如這裡新建檔案為config.gradle
ext {  
    versions = [  
            sdkMinVersion     : 14,  
            sdkTargetVersion  : 26,  
            ... 
    ]  
   
    depVersion = [  
            appCompatVersion : "26.+",  
            recyclerViewVersion : "26.0.0-alpha1"  
    ]  

    deps = [  
        suport : [  
                appcompat   : "com.android.support:appcompat-v7:${depVersion.appCompatVersion}",  
                recyclerview: "com.android.support:recyclerview-v7:${depVersion.recyclerViewVersion}"  
        ]  
    ]  
}  

// 2. 引入已宣告好的屬性
apply from: 'config/config.gradle' 
android {  
    def versions = rootProject.ext.versions  
    compileSdkVersion versions.sdkCompileVersion  
    buildToolsVersion versions.toolsBuildVersion  
     ...
  }  
  
dependencies {  
    def dependencies = rootProject.ext.deps  
    compile dependencies.suport.appcompat  
}

複製程式碼
  • 在比如我們經常看到的這個錯誤:
Error:Execution failed for task ':test:processDebugManifest'.> Manifest merger failed with multiple errors, see logs
複製程式碼

我們一般的解決方案為:命令列輸入gradlew processDebugManifest --stacktrace.

  • 最後我們在看個簡單的單機執行任務

    -w523

  • 由於開發過程中經常匯入第三方jar包,一不小心就報jar包衝突這,這時我們會執行 gradle app:dependencies 檢視app重複依賴,然後在通過exclude剔除重複的jar。

compile ('com.android.support:design:22.2.1')
            {
                exclude group: 'com.android.support'
            }

複製程式碼

其實當我們點選後Gradle會去尋找當前目錄下的 build.gradle 的檔案,這個檔案是 Gradle 的指令碼檔案,它裡面定義了工程和工程擁有的所有任務等資訊。然後執行相關task。下面我們一起來一步步揭開它的神祕面紗吧。

Gradle 中的工程( Project )和任務( Task )

就像上面的截圖一樣,我們知道,每一個 Gradle 的專案都會包含一個或多個工程,每一個工程又由一個或多個任務組成,一個任務代表了一個工作的最小單元,它可以是一次類的編譯、打一個 JAR 包、生成一份 Javadoc 或者是向倉庫中提交一次版本釋出。

任務的定義和使用

在任務中,我麼可以利用dependsOn定義依賴關係,doFirstdoLast對現有任務增強。 我們還是使用 IDEA 開發工具開啟之前的專案工程,把之前 build.gradle 檔案中所有的內容全部刪除,編寫輸入如下程式碼

task hello {
    doLast {
        println 'Hello world!'
    }
}

task release() {
    doLast {
        println "I'm release task"
    }
}

// 添任務依賴關係
release.dependsOn hello

//對現有的任務增強
// 法方一,在doFirst動作中新增
hello.doFirst {
    println 'Hello doFirst'
}
// 法方二 在doLast動作中新增
hello.doLast {
    println 'Hello doLast'
}
複製程式碼

開啟命令列端終,執行命令:gradle -q release,輸出結果如下:

-w558
另外我們還可以為任務設定屬性,主要通過 ext.myProperty 來初始化值,如下所示

task myTask {
    ext.myProperty = "myValue"
}

task printTaskProperties {
    doLast {
        println myTask.myProperty
    }
}
命令列執行 ➜  gradle -q printTaskProperties
myValue
複製程式碼

當然我們可以對現有任務進行配置:禁用或者重寫。 首先我們定義一個 myCopy 的任務,程式碼如下:

task myCopy(type: Copy) {
   from 'resources'
   into 'target'
   include('**/*.txt', '**/*.xml', '**/*.properties')
}
複製程式碼

類似java有API文件,Gradle也有類似文件,Gradle 中很多其它常用的任務,小夥伴們可以點選檢視, 如果我們想重寫 copy任務可通過overwrite屬性為 true 來現如下所示:

task copy(type: Copy)

task copy(overwrite: true) {
    doLast {
        println('overwrite the copy.')
    }
}
命令列執行 
> gradle -q copy
overwrite the copy.
複製程式碼

最後我們看下如何禁用某些任務。直接看程式碼吧

copy.enabled = false
複製程式碼

實戰篇

接下來我們來自定義外掛,有三種方式來編寫

  1. 在我們構建專案的 build.gradle 指令碼中直接編寫,這種方式的好處是外掛會自動被編譯載入到我們的 classpath 中,但是它有很明顯的侷限性,就是除了在包括它的指令碼外別的地方無法複用。

  2. 在我們構建專案的rootProjectDir/buildSrc/src/main/groovy 目錄下編寫,Gradle 會自動編譯到當前專案的 classpath 中,該專案下所有編譯指令碼都可以使用,但是除了當前專案之外的都無法複用。

  3. 以單獨的工程方式編寫,這個工程最終編譯釋出為一個 JAR 包,它可以在多個專案或不同的團隊中共享使用。

接下來我們一步步把按照第三種方式寫個Demo吧。

首先使用IDEA新建gradle 工程選擇groovy(跟上面一樣就不細說了),然後按照下面截圖新建src/main/groovy/你的包名,接著在resources 目錄下建立 META-INF/gradle-plugins 資料夾,在其中新建 hello.properties 檔案,敲黑板注意此處檔名,就是以後使用時要用的名字。此處是hello,所以我們得按照這樣引入apply plugin: 'hello',裡面像這樣輸入外掛的全路徑名字:implementation-class=org.gradle.HelloPlugin

-w1092

接下來分 2 步編寫程式碼:

  1. 繼承自DefaultTask的,使用TaskAction進行標註,這樣 Gradle 就會在任務執行的時候預設呼叫它
  2. 然後通過實現Plugin介面來實現自定義外掛類,實現apply(Project project) 方法。

按照步驟,首先我們新建MyTask.groovy檔案,裡面僅僅是簡單宣告瞭一個成員變數,然後列印。

class MyTask extends DefaultTask {
    String input = 'hello from MyTask'

    @TaskAction
    def greet() {
        println input
    }
}

複製程式碼

然後我們新建Hello.groovy檔案.我們向這個plugin新增了一個hello任務,我們知道gradle中可以配置引數比如:defaultConfig {} ndk {}等,其實gradle是使用 extension objects來現實給外掛傳參,具體實現看下面程式碼的註釋:

class Hello implements Plugin<Project> {
    @Override
    void apply(Project project) {
        // 向extension container儲存para引數,並應用給HelloPluginExtension
        project.extensions.create("para", HelloPluginExtension)
        // 向project物件新增hello任務
        project.task('hello',type:MyTask) {
            input = 'Hello Plugin input!'
            doLast {
                println "${project.para.first}${project.para.last}"
            }
        }
    }
}
複製程式碼

接下來發布工程到本地倉庫,供其他專案使用,在build.gradle中輸入

//使用 maven-publish 外掛先發布到本地
apply plugin: 'maven-publish'
publishing{
    publications {
        mavenJava(MavenPublication) {
            from components.java

            groupId 'org.gradle'
            artifactId 'customPlugin'
            version '1.0-SNAPSHOT'

        }
    }

    repositories{
        maven {
            // change to point to your repo, e.g. http://my.org/repo
            url "../repo"
        }
    }
}
複製程式碼

點選publish任務,看到成功釋出工程到本地倉庫../repo中了。

-w1215
最後用 IDEA 開發工具新建立一個 Gradle 工程來驗證我們的外掛。看到右側gradle任務中多了我們新增的hello任務,點選檢視成功輸出了Hello world。
-w1086

補充及常見問題

檔案樹是有層級結構的檔案集合,一個檔案樹它可以代表一個目錄結構或一 ZIP 壓縮包中的內容結構。使用Project.fileTree(java.util.Map)建立,可以使用過慮條件來包含或排除相關檔案。

// 指定目錄建立檔案樹物件
FileTree tree = fileTree(dir: 'src/main')
// 給檔案樹物件新增包含指定檔案
tree.include '**/*.java'
//andoid中使用檔案樹
implementation fileTree(include: ['*.jar'], dir: 'libs')
複製程式碼

多module的編譯配置

注意 settings.gradle引入的module才會參與編譯include ':app', ':plugin_common', ':plugin_gallery',可以在跟guild.gradle 中統一設定公共行為;比如下圖新增一個hello任務。

-w1615

一個工程的路徑為:以冒號(: 它代表了根工程)開始,再加上工程的名稱。例如“:common”。 一個任務的路徑為:工程路徑加上任務名稱,例如“:common:hello”. 比如:僅僅執行 gradle :plugin_common:hello

原始碼下載

相關文章