在上一篇文章《神策 Android 全埋點插件介紹》中,咱們瞭解到神策 Android 插件實際上是自定義的 Gradle 插件。Gradle 是一個專一於靈活性和性能的開源自動化構建工具,而插件的做用在於打包模塊化的、可重用的構建邏輯。能夠經過插件實現特定的邏輯,並打包起來分享給別人使用。例如:神策 Android 全埋點插件正是經過插件在編譯時對特定函數進行處理,從而實現控件點擊和 Fragment 頁面瀏覽全埋點的採集。java
本文咱們會先針對 Gradle 的基礎知識做必定的介紹,再舉例說明如何實現一個自定義的 Gradle 插件。這裏須要注意的是:文中採用 ./gradlew 去執行 Gradle 的命令,若是是 Windows 用戶的話須要改爲 gradlew.bat。android
Gradle 有兩個重要的概念:Project 和 Task,本節將會介紹它們各自的做用以及之間的關係。api
Project 是與 Gradle 交互中最重要的 API,咱們能夠經過 Android Studio 的項目結構來理解 Project 的含義,如圖 2-1 所示:bash
圖 2-1 Android Studio 項目結構圖閉包
圖 2-1 是寫做過程當中使用到的一個項目(名爲 BlogDemo),包含 app 和 plugin 這兩個 Module。這裏不論是 「項目」 仍是 「Module」 在構建時都會被 Gradle 抽象成 Project 對象。它們的主要關係是:app
一、Android Studio 結構中的項目至關於一個父 Project,而一個項目中全部的 Module 都是該父 Project 的子 Project;maven
二、每一個 Project 都會對應一個 build.gradle 配置文件,所以使用 Android Studio 建立一個項目的時候在根目錄下有一個 build.gradle 文件,在每一個 Module 的目錄下又各有一個 build.gradle 文件;ide
三、Gradle 是經過 settings.gradle 文件去進行多項目構建,從圖 2-1 中也能夠看出項目之間的關係。模塊化
父 Project 對象能夠獲取到全部的子 Project 對象,這樣就能夠在父 Project 對應的 build.gradle 文件中作一些統一的配置,例如:管理依賴的 Maven 中心庫:函數
...allprojects { repositories { google() jcenter() }}...複製代碼
Project 在構建過程當中會執行一系列的 Task。Task 的中文翻譯是 「任務」,它的做用其實也就是抽象了一系列有意義的任務,用 Gradle 官方的話說就是:Each task perform some basic work。例如:當你點擊 Android Studio 的 Run 按鈕的時候,Android Studio 會把項目編譯、運行,實際上這個過程就是執行了一系列的 Task 來完成的。可能包含:編譯 Java 源碼的 Task、編譯 Android 資源的 Task、編譯 JNI 的 Task、混淆的 Task、生成 Apk 文件的 Task、運行 App 的 Task 等。也能夠在 Android Studio 的 Build Output 看到真正運行的是哪些 Task,如圖 2-2 所示:
圖 2-2 Android Studio Build 輸出日誌
從圖中右側咱們能夠看到,Task 由兩個部分組成:Task 所在的 Module 名和 Task 的名稱。在運行 Task 的時候,也須要按照這樣的方式去指定一個 Task。
另外,能夠自定義實現本身的 Task,咱們來建立一個最簡單的 Task:
// add to build.gradletask hello { println 'Hello World!'}複製代碼
這段代碼的含義是建立了一個名爲 「hello」 的 Task,想要單獨執行該 Task 的話,能夠在 Android Studio 的 Terminal 中輸入 「./gradlew hello」,執行後就能夠看到控制檯輸出 「Hello World!」。
Plugin 和 Task 從它們的做用來看其實區別不大,都是把一些業務邏輯封裝起來,Plugin 適用的場景是打包須要複用的編譯邏輯(即把一部分編譯邏輯模塊化出來)。能夠自定義 Gradle 插件,實現必要的邏輯後把它發佈到遠程倉庫或者打成本地 JAR 包分享出去。這樣,後續想要再次使用它或者想分享給別人使用的時候,就能夠直接引用遠程倉庫的包或者引用本地的 JAR 包。
最多見的 Plugin 應該就是 Android 官方提供的 Android Gradle Plugin。能夠在項目主 Module 的 build.gradle 文件第一行看到:「apply plugin: 'com.android.application'」,這便是 Android Gradle Plugin。「com.android.application」 指的是 plugin id,該插件的做用是幫助你生成一個可運行的 APK 文件。
插件還能夠讀取寫在 build.gradle 文件中的配置。主 Module 的 build.gradle 文件中會有一個名爲 「android」 的塊,塊中定義了一些屬性,例如:App 支持的最低系統版本、App 的版本號等。你能夠把這裏的 「android」android 塊類比成數據類或者基類,定義的屬性類比成類的成員變量。Android Gradle Plugin 在運行時能夠拿到 「android」 塊實例化的對象,進而根據對象的屬性值運行不一樣的編譯邏輯。
Gradle 插件有三種實現方式,分別爲 Build script、buildSrc project 和 Standalone project:
一、Build script 會把邏輯直接寫在 build.gradle 文件中,Plugin 只對當前 build.gradle 文件可見;
二、buildSrc project 是將邏輯寫在 rootProjectDir/buildSrc/src/main/java(最後一個路徑文件夾也能夠是 groovy 或 kotlin,主要取決於你用什麼語言去實現自定義插件) 目錄下,Plugin 只對當前項目生效;
三、Standalone project 是將邏輯寫在獨立項目裏,能夠直接編譯後把 JAR 包發佈到遠程倉庫或者本地。
基於本文的寫做目的,這裏咱們主要講解 Standalone project,即獨立項目的 Gradle 插件。
一個獨立項目的 Gradle 插件大體結構如圖 3-1 所示:
圖 3-1 Gradle 插件項目目錄示意圖
在 main 文件夾下分爲 groovy 文件夾與 resources 文件夾:
其中,resources 文件夾下是固定格式的 META-INF/gradle-plugins/XXXX.properties,XXXX 就表明之後使用插件時須要指定的 plugin id。
目前 Android Studio 對於 Gradle 插件開發的支持不夠好,不少 IDE 本能夠完成的工做都須要咱們手動完成,例如:
一、Android Studio 不可以直接新建 Gradle 插件的 Module,只能先新建一個 Java Library 類型的 Module,再把多餘的文件夾刪除;
二、新建類默認是新建 Java 的類,新建的文件名後綴是 「.java」,想要新建 Groovy 語法的類須要手動新建一個後綴爲 「.groovy」 的文件,而後添加上 package、class 聲明;
三、resources 整個都須要手動建立,文件夾名須要注意拼寫;
四、刪除掉 Module 的 build.gradle 所有內容,新加上 Gradle 插件開發須要的 Gradle 插件、依賴等。
在寫插件的代碼以前,咱們須要對 build.gradle 作些修改,以下所示:
apply plugin:
'groovy'
apply plugin:
'maven'
dependencies {
implementation gradleApi()
implementation localGroovy()
}
uploadArchives{
repositories.mavenDeployer {
//本地倉庫路徑,以放到項目根目錄下的 repo 的文件夾爲例
repository(url: uri(
'../repo'
))
//groupId ,自行定義
pom.groupId =
'com.sensorsdata.myplugin'
//artifactId
pom.artifactId =
'MyPlugin'
//插件版本號
pom.version =
'1.0.0'
}
}
這裏主要分爲三部份內容:
一、apply 插件:應用 'groovy' 插件是由於咱們的項目是使用 Groovy 語言開發的,'maven' 插件在後面發佈插件時會用到;
二、dependencies:聲明依賴;
三、uploadArchive:這裏是一些 maven 相關的配置,包括髮布倉庫的位置、groupId、artifactId、版本號,這裏爲了調試方便把位置選在項目根目錄下的 repo 文件夾。
作好以上準備以後,就能夠開始源碼的編寫。Gradle 插件要求入口類須要實現 org.gradle.api.Plugin 接口,而後在實現方法 apply 中實現本身的邏輯:
package com.sensorsdata.pluginclass MyPlugin implements Plugin<Project>{ @Override void apply(Project project) { println 'Hello,World!' }}複製代碼
在這裏的示例中,apply 方法就是咱們整個 Gradle 插件的入口方法,做用相似於各類語言的 main 方法。apply 方法的入參類型 Project 在第二節中已經進行了解釋,這裏再也不贅述。因爲 Plugin 類和 Project 類有很是多的同名類,在導入的時候必定注意選擇 org.gradle.api 包下的類。
最後,還須要作一項準備工做:Gradle 插件並不會自動尋找入口類,而是要求開發者把入口類的類名寫在 resources/META-INF/gradle-plugins/XXXX.properties 裏,內容格式爲 「implementation-class=入口類的全限定名」,此處示例項目的配置以下所示:
// com.sensorsdata.plugin.propertiesimplementation-class=com.sensorsdata.plugin.MyPlugin複製代碼
完成編寫插件的全部內容後,在終端執行
./gradlew uploadArchive複製代碼
就能夠發佈插件。在上一小節編寫插件的 build.gradle 文件中提早配置好了發佈到 maven 倉庫相關的配置,所以咱們這裏執行該命令後,在項目根目錄下就會出現 repo 文件夾,文件夾中包含打包後的 JAR 文件。
使用插件主要分別兩個步驟:
(1)聲明插件
聲明插件須要在 Project 級別的 build.gradle 文件中完成,在 build.gradle 文件中有一個塊叫作 buildscript,buildscript 塊又分爲 repositories 塊和 dependencies 塊。repositories 塊用來聲明須要引用的依賴所在的遠程倉庫地址,dependencies 塊用來聲明具體引用的依賴。這裏使用剛剛發佈到本地 repo 文件夾 JAR 包爲例,參考代碼以下:
buildscript { repositories { maven{ // 剛剛咱們把插件發佈到了根目錄下面的 repo 文件夾 url 'repo' } } dependencies { // classpath '$group_id:$artifactId:$version' classpath 'com.sensorsdata.myplugin:MyPlugin:1.0.0' }}複製代碼
(2)應用插件
應用插件須要在 Module 級別的 build.gradle 文件中完成:
// apply plugin: 'plugin id'apply plugin: 'com.sensorsdata.plugin'複製代碼
完成上述步驟以後,在每次編譯的時候均可以在編譯日誌中看到插件輸出的 「Hello,World!」。
若是但願插件的功能更加靈活的話,通常會預留一些可配置的參數,就像能夠在主 Module 的 「android」 塊配置編譯的 Android SDK 版本、Build-Tools 版本等。「android」 塊的這個配置就是 Gradle 的 Extension,下面咱們來作一個自定義的 Extension。
建立一個用於 Extension 的類很是簡單:只須要新建一個普通的類,類中定義的屬性就是 Extension 能夠接收的配置。它不須要繼承任何類,也不須要實現任何接口,以下所示:
class MyExtension{ public String nam = "name" public String sur = "surname"}複製代碼
能夠經過 ExtensionContainer 來建立和管理 Extension,ExtensionContainer 對象能夠經過 Project 對象的 getExtensions 方法獲取:
def extension = project.getExtensions().create('myExt',MyExtension)project.afterEvaluate { println("Hello from " + extension.toString())}複製代碼
上面的代碼片斷能夠直接複製到 apply 方法中或者放在 build.gradle 文件中使用。這裏使用到了 create 方法來建立 Extension,咱們來看下 create 方法的定義:
<T> T create(String name, Class<T> type, Object... constructionArguments);複製代碼
一、name:表明要建立的 Extension 的名字,例如:build.gradle 中名爲 「android」 的塊,Android Gradle 插件在建立這個 Extension 的時候 name 就須要填 「android」。Extension 的 name 不能和已有的重複,例如: Android Gradle 插件建立的 Extension name 爲 「android」,那麼其它 Extension name 就不能夠再使用 「android」;
二、type:該 Extension 的類類型,這裏的類就是上一小節建立的類,注意類的屬性名與 Extension 中的屬性名須要一致;
三、constructionArguments:類的構造函數參數值。
使用 create 方法以後,你可能會火燒眉毛的在下一行當即打印出 Extension 對象的值,不過這麼作的話你會發現 Extension 對象打印出來的值並不對。不論你在 build.gradle 中怎麼配置,Extension 對象就是讀不到值。具體緣由能夠回顧下這裏的示例,你會發現示例裏打印的邏輯寫在了 afterEvaluate 方法中。這裏的寫法跟插件的生命週期有很大的關係,咱們將在下一節中介紹 Gradle 插件的生命週期。
官方對於 Gradle 構建的生命週期的定義:Gradle 的核心是一種基於依賴的語言,用 Gradle 的術語來講這意味着你可以定義 Task 和 Task 之間的依賴關係。Gradle 會保證這些 Task 按照依賴關係的順序執行而且每一個 Task 只會被執行一次,這些 Task 根據依賴關係構成了一個有向無環圖。Gradle 在執行任何 Task 以前都會用內部的構建工具去完成這樣這樣一個圖,這就是 Gradle 的核心。這種設計使得不少本來不可能的事情成爲可能。
每次 Gradle 構建都須要通過三個不一樣的階段:
一、初始化階段:Gradle 是支持單項目和多項目構建的,所以在初始化階段,Gradle 會根據 settings.gradle 肯定須要參與構建的項目,併爲每一個項目建立一個 Project 實例。Android Studio 的項目和 Module 對 Gradle 來講都是項目;
二、配置階段:在這個階段會配置 Project 對象,而且全部項目的構建腳本都會被執行。例如:Extension 對象、Task 對象等都是在這個階段被放到 Project 對象裏;
三、執行階段:通過了配置階段,此時全部的 Task 對象都在 Project 對象中。而後,會根據終端命令指定的 Task 名字從 Project 對象中尋找對應的 Task 對象並執行。
Gradle 提供了不少生命週期的監聽方法,用來在特定的階段執行特定的任務。這裏選取了部分回調方法,按照執行順序關係畫了一份 Gradle 生命週期流程簡圖,如圖 4-1 所示:
圖 4-1 Gradle 生命週期流程簡圖
圖中的生命週期回調方法裏,屬於 Project 有 project.beforeEvaluate 和 project.afterEvaluate,它們的觸發時機分別是在 Project 進行配置前和配置結束後。在以前的示例中,正是使用了這裏的 afterEvaluate,因爲方法的最後一個參數是閉包因此寫法優化成了 afterEvaluate{}。
使用 create 建立的對象,在對應 Project 還沒配置完成的時候打印出來的值天然是不正確的,須要在配置完成後才能正確獲取到寫在 build.gradle 中的 Extension 值。由於直接寫在 apply 方法裏的邏輯是在配置階段執行的,因此會出現這種狀況。
本文首先對 Gradle 的基礎知識作了必定的介紹,包含 Project 與 Task,而後重點講解了自定義 Gradle 插件從建立到使用的詳細過程。但願可以爲編寫自定義的 Gradle 插件提供必定的幫助。
顧鑫
神策數據 | SDK 技術顧問
我是顧鑫,神策數據 Android 技術顧問。神策數據是我就任的第一家公司,他很是的棒~學習中我喜歡作 Android 相關開發,也喜歡接觸新興技術,但願在開源社區能與你共同窗習、共同進步。
本文著做權歸「神策數據開源社區」全部,商業轉載請聯繫咱們得到受權;非商業轉載請註明出處,並附上神策數據開源社區服務號二維碼。