Gradle理論與實踐:自定義Gradle外掛
Gradle外掛
Gradle可以認為是一個框架,負責定義流程和規則。而具體的編譯工作則是通過外掛的方式來完成的。比如編譯 Java 有 Java 外掛,編譯 Groovy 有 Groovy 外掛,編譯 Android APP 有 Android APP 外掛,編譯 Android Library 有 Android Library 外掛。在Gradle中一般有兩種型別的外掛, 指令碼外掛 和 二進位制外掛 。使用外掛方式可以使得同一邏輯在專案中複用,也可以針對不同專案做個性化配置,只要外掛程式碼支援即可。
一、Java Gradle外掛
Java外掛引入方式:
- apply plugin: 'java'
Java外掛約定 src/main/java
為我們專案原始碼存放位置; src/main/resources
為資源存放位置; src/test/java
為我們單元測試用例存放目錄; src/test/resources
存放我們單元測試中資源存放位置。
java外掛引入了一個概念叫做SourceSets,通過修改SourceSets中的屬性,可以指定哪些原始檔(或資料夾下的原始檔)要被編譯,哪些原始檔要被排除。Gradle就是通過它實現Java專案的佈局定義。
預設配置:
android { sourceSets { main { manifest.srcFile 'AndroidManifest.xml' java.srcDirs = ['src'] resources.srcDirs = ['src'] aidl.srcDirs = ['src'] renderscript.srcDirs = ['src'] res.srcDirs = ['res'] assets.srcDirs = ['assets'] jniLibs.srcDirs = ['libs'] } }
如果想修改原始碼的目錄以及多個resource的目錄,可以通過下面來設定:
sourceSets { main { java.srcDirs = ['other/java'] res.srcDirs = [ 'src/main/res/', 'src/main/res/extra' ] } }
二、Android Gradle外掛
Android其實就是Gradle的一個第三方外掛,Android Gradle和Android Studio完美無縫搭配的新一代構建系統。
-
APP外掛id :com.android.application
-
Library外掛id:com.android.library
-
Test外掛id:com.android.test
2.1、應用Android Gradle外掛
上面說了Android Gradle是Gradle的一個三方外掛,託管在jcenter上,如果要使用,必須知道他們的外掛id,另外還要配置他們依賴的classpath,在根目錄的build.gradle中配置如下:
buildscript { repositories { //程式碼倉庫 jcenter() } dependencies { //Android Gradle外掛版本 classpath 'com.android.tools.build:gradle:2.3.3' } }
配置程式碼倉庫為jcenter,當編譯專案時,Gradle會去jcenter倉庫中尋找Android Gradle對應版本的依賴,以上配置好後,就可以使用Android Gradle外掛了,在我們app目錄的build.gradle下:
apply plugin: 'com.android.application android { compileSdkVersion 26 buildToolsVersion "26.0.3" defaultConfig { applicationId "org.ninetripods.qrcode" minSdkVersion 14 targetSdkVersion 26 versionCode 1 versionName "1.0" } signingConfigs { release { keyAlias 'xxx' keyPassword 'xxx' storeFile file('xxx.jks') storePassword 'xxx' } } buildTypes { release { minifyEnabled false signingConfig signingConfigs.release proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } }
android{}是Android Gradle外掛提供的一個擴充套件型別,可以讓我們自定義Android Gradle工程。
-
compileSdkVersion: compileSdkVersion告訴Gradle用哪個Android SDK版本編譯應用
1、應用想相容新版本、使用了新版本API,此時就必須使用新版本及以上版本編譯,否則就會編譯報錯;
2、如果使用了新版本的Support Library,此時也必須使用新版本及以上版本編譯
-
buildToolsVersion:構建該Android工程所用構建工具(aapt, dx, renderscript compiler, etc...)的版本,一般google在釋出新的SDK時,會同時釋出對應的buildToolsVersion,更詳細的見: https://stackoverflow.com/questions/24521017/android-gradle-buildtoolsversion-vs-compilesdkversion
PS:Android Studio 3.0以上時,buildToolsVersion不是必須要寫的了。
-
defaultConfig: 預設配置,它是一個ProductFlavor。ProductFlavor允許我們在打多渠道包時根據不同的情況生成不同的APK包。即如果不在ProductFlavor中單獨配置的話,那麼會使用defaultConfig的預設配置。
-
minSdkVersion: 最低支援的Android系統API Level
-
targetSdkVersion: App基於哪個Android版本開發的
-
versionCode: App應用內部版本號,一般用來控制App升級
-
versionName: App應用的版本名稱,即我們釋出的App版本,一般使用者可以看到。
minSdkVersion、targetSdkVersion、compileSdkVersion三者的關係:
minSdkVersion <= targetSdkVersion <= compileSdkVersion。
理想狀態是:
minSdkVersion(lowest possible) <= targetSdkVersion == compileSdkVersion(latest SDK)。
三、自定義Gradle外掛
編寫自定義Gradle外掛原始碼的有下面三個地方:
3.1、Build script
可以在構建指令碼中直接編寫自定義外掛的原始碼。這樣做的好處是外掛可以自動編譯幷包含在構建指令碼的classpath中,不需要再去宣告。然而,這個自定義外掛在構建指令碼之外是不可見的,因此這種方式實現的外掛在構建指令碼之外是不能複用。
舉個例子,在根目錄下的build.gradle中寫入:
//build.gradle class GreetingPlugin implements Plugin<Project> { @Override void apply(Project project) { //新建task hello project.task('hello') { doLast { println 'Hello from the GreetingPlugin' } } } } //引入外掛 apply plugin: GreetingPlugin
執行結果
./gradlew hello Hello from the GreetingPlugin
3.2、buildSrc project
在專案的根目錄下新建一個buildSrc/src/main/groovy的目錄,將自定義外掛的原始碼放入此目錄中,Gradle將負責編譯和測試外掛,並使其在構建指令碼的classpath中可見。這個外掛對於專案中所有的build script都是可見的,但是在構建之外是不可見的,此構建方式不能再其他專案中複用。
舉例:在rootProjectDir/buildSrc/src/main/groovy目錄下新建了一個外掛類,如下:
image.png
GreetingExtensionPlugin.groovy中程式碼如下:
//GreetingExtensionPlugin.groovy package com import org.gradle.api.Plugin import org.gradle.api.Project class GreetingExtensionPlugin implements Plugin<Project> { @Override void apply(Project project) { // Add the 'greeting' extension object def extension = project.extensions.create('greeting', GreetingExtension) // Add a task that uses configuration from the extension object project.tasks.create('buildSrc') { doLast { println "${extension.message} from ${extension.greeter}" println project.greeting } } } } class GreetingExtension { String message String greeter }
定義好的外掛就可以在專案中所有的build.gradle中使用了:
//build.gradle apply plugin: GreetingExtensionPlugin greeting { message = 'hello' greeter = 'GreetingExtensionPlugin' }
執行結果:
./gradlew buildSrc hello from GreetingExtensionPlugin com.GreetingExtension_Decorated@42870556
- 擴充套件屬性:自定義外掛程式碼中有一句
def extension = project.extensions.create('greeting', GreetingExtension)
,可以用來擴充套件屬性,可以理解為往GreetingExtension中新加了一個屬性。建立完成後,可以通過project.greeting來獲取擴充套件屬性的例項。使用這種方式來給外掛傳遞引數。
3.3、Standalone project
上面兩種自定義外掛都只能在自己的專案中使用,如果想在其他專案中也能複用,可以建立一個單獨的專案並把這個專案釋出成一個JAR,這樣多個專案中就可以引入並共享這個JAR。通常這個JAR包含一些外掛,或者將幾個相關的task捆綁到一個庫中,或者是外掛和task的組合, Standalone project建立步驟:
-
在Android Studio的rootProject目錄下新建一個Module,型別隨便選一個就行(如 Android Module),後面會有大的改動。(也可以選擇IDEA來開發,IDEA中可以直接建立groovy元件)
-
清空Module目錄下build.gradle中的所有內容,刪除其他所有檔案
-
在Module中建立
src/main/groovy
的目錄,然後再建立包名資料夾。在main目錄下再新建resources/META-INF/gradle-plugins
目錄,在這個目錄下編寫一個和外掛id名字相同的.properties
檔案,這樣Gradle就可以找到外掛實現了。
舉個例子,下面的程式碼實現了在build目錄中新建個文字檔案,並寫入文字的功能。
1、在 src/main/groovy
下新建 /com/fastgo/plugin
目錄並建立一個名為 CustomPlugin.groovy
的檔案:
package com.fastgo.plugin import org.gradle.api.DefaultTask import org.gradle.api.Project import org.gradle.api.Plugin import org.gradle.api.tasks.TaskAction class CustomPlugin implements Plugin<Project> { @Override void apply(Project project) { project.tasks.create('writeToFile', CustomPluginTask) { destination = { project.greetingFile } doLast { println project.file(destination).text } } } } class CustomPluginTask extends DefaultTask { def destination File getDestination() { //建立路徑為destination的file project.file(destination) } @TaskAction def greet() { def file = getDestination() file.parentFile.mkdirs() //向檔案中寫入文字 file.write('hello world') } }
上面的程式碼中在外掛的 apply(Project project)
中建立了名為 writeToFile
的Task,並依賴於 CustomPluginTask
。 CustomPluginTask
中定義了一個 destination
路徑,並通過 project.file(destination)
建立建立一個路徑為 destination
的檔案,並往檔案中寫入文字。
注意:別忘了引入 package com.fastgo.plugin
,否則最後生成後會提示找不到外掛。
2、建立 resources/META-INF/gradle-plugins/com.fastgo.plugin.properties
檔案,並在檔案裡寫入:
implementation-class=com.fastgo.plugin.CustomPlugin
3、在 build.gradle
中寫入:
plugins { id 'groovy' id 'maven-publish' id 'maven' } dependencies { implementation gradleApi() implementation localGroovy() } repositories { mavenCentral() } group = 'com.fastgo' version = '1.0-test' publishing { repositories { maven { url = uri("$rootDir/repo") } } publications { maven(MavenPublication) { from components.java } } }
本例中是通過 url = uri("$rootDir/repo")
將程式碼打包到maven的本地倉庫中,如果想上傳到遠端,換成遠端連結即可。通過設定GroupId、ArtifactId、Version來保證外掛的唯一性。
-
GroupId:
group = 'com.fastgo'
-
ArtifactId:
plugin
-
Version:
version = '1.0-test'
最終的檔案如下:
image.png
外掛編寫完成後,在Android Studio的右上角開啟Gradle,執行 :plugin
分組中的 publish
命令,執行完成後,會在專案根目錄下生成repo倉庫:
image.png
4、在專案根目錄的 build.gradle
中引用外掛:
buildscript { repositories { maven { url = uri("$rootDir/repo") } ------其他------- } dependencies { classpath 'com.android.tools.build:gradle:2.3.3' classpath 'com.fastgo:plugin:1.0-test' } } //通過外掛id找到外掛 apply plugin: 'com.fastgo.plugin' ext.greetingFile="$buildDir/hello.txt"
5、驗證效果
mqdeMacBook-Pro:AndroidStudy mq$ ./gradlew -q writeToFile hello world
執行writeToFile的task輸出了我們寫入的文字,再看看build目錄下是否有我們想要的檔案:
image.png
可以看到在 build
目錄下生成了 hello.txt
檔案並往裡面寫入了設定的文字,說明我們編寫的外掛被成功引入了。
四、原始碼地址
上述例子原始碼已上傳至: https://github.com/crazyqiang/AndroidStudy
五、引用
【1】 https://docs.gradle.org/current/userguide/custom_plugins.html
【2】 https://docs.gradle.org/4.1/userguide/publishing_maven.html
【3】 https://docs.gradle.org/current/javadoc/org/gradle/api/Project.html
image
image