歡迎關注本人公衆號,掃描下方二維碼或搜索公衆號 id: mxszgghtml
![]()
本文基於 Android Gradle plugin 3.0.1java
在前文中筆者闡述道 task 就至關於函數,那麼這篇文章所要介紹的 plugin 就至關於函數庫了。畢竟在 build.gradle
文件中撰寫大量的 task 是確定很差維護的,因此能夠將 tasks 作成 plugin 而後直接 apply 就行了。android
就像在
app/build.gradle
中apply plugin: 'com.android.application'
這樣 appProject 就可使用該 plugin 中的 task 了。git
buildSrc
。src/main/java
改爲 src/main/groovy
新建一個 xxxPlugin.groovy 並實現 Plugin 接口,例如:github
import org.gradle.api.Plugin
import org.gradle.api.Project
class TestPlugin implements Plugin<Project> {
@Override
void apply(Project project) {
project.task('pluginTest') {
doLast {
println 'Hello World'
}
}
}
}
複製代碼
能夠看到,上述 plugin 僅是在 apply()
方法內部建立了一個名爲 pluginTest
的 task。api
因爲 Kotlin/Java 與 groovy 的兼容,因此並不是必定要建立 groovy 文件,也能夠是 xxxPlugin.java/xxxPlugin.kotlin。bash
build.gradle
文件中添加以下信息:apply plugin: TestPlugin微信
至此以後,不妨在命令行調用 pluginTest
task 看看是否有效果——閉包
./gradlew pluginTestapp
> Task :app:testPlugin
Hello from the TestPlugin
隨着項目的急速發展,有朝一日發現有時候不想輸出 Hello World
而是但願這個 pluginTest
task 能夠根據開發者的需求進行配置。
建立一個 xxxExtension.groovy 文件(固然,也能夠用 Java/Kotlin 來寫),實際上就是和 JavaBean 差很少的類,相似以下:
class TestPluginExtension {
String message = 'Hello World'
}
複製代碼
在 Plugin 類中獲取閉包信息,並輸出:
class TestPlugin implements Plugin<Project> {
void apply(Project project) {
// Add the 'testExtension' extension object
def extension = project.extensions.create('testExtension', TestPluginExtension)
project.task('pluginTest') {
doLast {
println extension.message
}
}
}
}
複製代碼
第四行經過 project.extensions.create(String name, Class<T> type, Object... constructionArguments)
來獲取 testExtension
閉包中的內容並經過反射將閉包的內容轉換成一個 TestPluginExtension 對象。
在 build.gradle
中添加一個 testExtension
閉包:
testExtension {
message 'Hello Gradle'
}
複製代碼
在命令行鍵入如下信息:
./gradlew pluginTest
將會看到輸出結果——
> Task :app:pluginTest
Hello Gradle
到目前爲止談及到的東西都仍是一個普通的、不能夠發佈到倉庫的插件,若是想要將插件發佈出去供他人和本身在項目中 apply,須要進行如下步驟將插件變成一個 Project——
更改 build.gradle
文件內容:
apply plugin: 'groovy'
dependencies {
compile gradleApi()
compile localGroovy()
}
複製代碼
此時能夠觀察到 External Libraries 中多出了 gradle-api/gradle-installation-beacon/groovy 庫。其中,gradle 的版本是基於項目下 gradle wrapper 中配置的版本——
![]()
![]()
建立 src/main/resources/META-INF/gradle-plugins/插件名.properties
,例如 src/main/resources/META-INF/gradle-plugins/com.sample.test.properties
,而後將 properities 文件內容改成 implementation-class=Plugin 路徑
,例如 implementation-class=com.sample.test.TestPlugin
。
在 build.gradle
文件中經過 apply plugin: '插件名'
引入插件 —— apply plugin: 'com.sample.test'
。
在命令行鍵入如下信息:
./gradlew pluginTest
將會看到輸出結果——
> Task :app:pluginTest
Hello Gradle
固然,以上僅是告訴各位讀者如何將 plugin 項目化,並未涉及到如何將 plugin 提交到倉庫中,關於 jcenter 倉庫提交方式可借鑑手摸手教你如何把項目提交到 jcenter,其餘倉庫提交方式讀者可自行搜索。
Android 打包過程當中,一個 task 接着一個 task 的執行,每一個 task 都會執行一段特定的事情(例如第一篇文章中提到的幾個 task),因此在 Gradle 插件的開發中,若是是針對打包流程的更改,實際上大部分都是 hook 某一個 task 來達到目的——例如我司的 mess 經過 hook transformClassesAndResourcesWithProguardForDebug
task (Gradle v2.0+ task)來實現對四大組件以及 View 的混淆的;美麗說的 ThinRPlugin 是經過 hook transformClassesWithDexForDebug
(Gradle v2.0+ task)來實現精簡 R.class/R2.class 的。
由於 Android 現有的 task 已經很完善了,因此若是想要達到目的,只須要了解相應的 task 並在其以前或以後作一些操做便可。
爲了示例而示例的簡單例子實在很少,筆者只能拿起上篇文章中的示例——在 app 目錄下建立 pic 文件夾,並添加一個名爲 test 的 png 圖片,hook apk 打包流程將該圖片添加入 apk 的 assets
文件夾。
儘管這看起來真的很沒有卵用。
此次爲了符合實際開發要求,不妨提高必定的難度——僅在 release 包中向 assets
添加圖片,而 debug 包不向 assets
中添加圖片。在實際開發中有不少這樣的需求,例如前文提到的 mess 是對 apk 源碼進行混淆的,那麼平常開發者運行的 debug 包有必要執行該 task 麼?顯然並不須要,應該僅在發佈的時候打 release 包的時候執行該 task 就行了。
那麼如何知道當前 task 是爲 release 服務的呢?簡單的尋找到 name 爲 packageRelease
的 task 是確定不行的,平常開發中項目時常有不少種變體,例如在 app/build.gradle
中輸入如下代碼:
android {
...
flavorDimensions "api", "mode"
productFlavors {
demo {
dimension "mode"
}
full {
dimension "mode"
}
minApi23 {
dimension "api"
minSdkVersion '23'
}
minApi21 {
dimension "api"
minSdkVersion '21'
}
}
複製代碼
此時的變種共有 3 (debug、release、androidTest) * 2(demo、full) * 2(minApi2三、minApi21)共計12種,截圖以下:
那麼如何爲以上全部的 release 變種包的 assets
中都填入圖片呢?
根據官方文檔能夠知道開發者能夠經過 android.applicationVariants.all
獲取到當前全部的 apk 變體,該變體的類型爲 ApplicationVariant
,其父類 BaseVariantOutput
中含 name 字段,該字段實際上就是當前變體的名字,那麼其實只須要判斷該 name 字段是否包含 release 關鍵字便可。
建立 plugin 的基本流程已經在前文中闡述過了,直接進行核心 plugin 的撰寫,HookAssetsPlugin
源碼以下:
import com.android.build.gradle.api.ApkVariantOutput
import com.android.build.gradle.api.ApplicationVariant
import com.android.build.gradle.tasks.PackageApplication
import org.gradle.api.Plugin
import org.gradle.api.Project
class HookAssetsPlugin implements Plugin<Project> {
@Override
void apply(Project project) {
project.afterEvaluate {
project.plugins.withId('com.android.application') {
project.android.applicationVariants.all { ApplicationVariant variant ->
variant.outputs.each { ApkVariantOutput variantOutput ->
if (variantOutput.name.equalsIgnoreCase("release")) {
variantOutput.packageApplication.doFirst { PackageApplication task ->
project.copy {
from "${project.projectDir.absolutePath}/pic/test.png"
into "${task.assets.asPath}"
}
}
}
}
}
}
}
}
}
複製代碼
在第一篇文中就闡述過,只能在 project.afterEvaluate
閉包中才能獲取到當前 project 中的全部 task 。
經過 project.plugins.withId('com.android.application')
確保當前 project 是 Android app project 而不是 Android library project,以此來避免無效操做,畢竟 package task 是 com.android.application
中的 task。
經過 project.android.applicationVariants.all
獲取全部變體信息。
經過觀察 ApplicationVariant
類的父類 BaseVariant
中 outputs 字段可知道該字段表明着當前變體的輸出信息(DomainObjectCollection 類型),BaseVariantOutput
的子類 ApkVariantOutput
中的 packageApplication
即爲上一篇文章中所說的 PackageAndroidArtifact
task 了。
判斷當前變體是不是 release 的變體。(經過 variantOutput.name.equalsIgnoreCase("release")/variant.name.equalsIgnoreCase("release")
都是能夠的。)
hook 步驟4中所說的 PackageAndroidArtifact
task,將圖片複製到 assets
中。
實際上,在平常開發中尋找 task 的方式可能更多的是使用
project.tasks.findByName(name)/project.tasks.getByName(name)
,這樣也更加方便,筆者在 demo 中附帶了此種寫法,源碼戳我。
除了上面提到的 mess(mess 源碼解析) 和 ThinRPlugin (筆者將會在後續的文章中對 ThinRPlugin 的源碼進行解析)之外,筆者瞭解到的還有一些如下知名的 Gradle 插件可供讀者學習:
固然,前面提到的幾個 plugin 有些重量級,輕量級的筆者沒有了解多少,只能推薦mess 源碼解析做者的一個快速生成R2.java中fields的插件和一個快速將指定class打入maindex的插件,對新手瞭解 Gradle plugin 仍是很友好的。
本文實戰模塊的源碼連接:請戳我。
筆者新建了微信羣,若是讀者有問題或者對筆者感興趣,歡迎加入,因爲滿了100人,須要先加筆者的微信,微信備註:入羣。
![]()