寫給 Android 開發者的 Gradle 系列(三)撰寫 plugin

歡迎關注本人公衆號,掃描下方二維碼或搜索公衆號 id: mxszgghtml

本文基於 Android Gradle plugin 3.0.1java

前言

前文中筆者闡述道 task 就至關於函數,那麼這篇文章所要介紹的 plugin 就至關於函數庫了。畢竟在 build.gradle 文件中撰寫大量的 task 是確定很差維護的,因此能夠將 tasks 作成 plugin 而後直接 apply 就行了。android

就像在 app/build.gradleapply plugin: 'com.android.application' 這樣 appProject 就可使用該 plugin 中的 task 了。git

準備工做

  1. 新建一個 Android 項目。
  2. 新建一個 java library module,該 module 必須命名爲 buildSrc
  3. src/main/java 改爲 src/main/groovy

基本實現

  1. 新建一個 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

  1. 既然 plugin 已經就這麼簡單的實現了,那麼如何應用到實際項目中呢?在 build.gradle 文件中添加以下信息:

apply plugin: TestPlugin微信

至此以後,不妨在命令行調用 pluginTest task 看看是否有效果——閉包

./gradlew pluginTestapp

> Task :app:testPlugin

Hello from the TestPlugin

擴展

隨着項目的急速發展,有朝一日發現有時候不想輸出 Hello World 而是但願這個 pluginTest task 能夠根據開發者的需求進行配置。

  1. 建立一個 xxxExtension.groovy 文件(固然,也能夠用 Java/Kotlin 來寫),實際上就是和 JavaBean 差很少的類,相似以下:

    class TestPluginExtension {
      String message = 'Hello World'
    }
    複製代碼
  2. 在 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 對象。

  3. build.gradle 中添加一個 testExtension 閉包:

    testExtension {
     message 'Hello Gradle'
    }
    複製代碼
  4. 在命令行鍵入如下信息:

./gradlew pluginTest

將會看到輸出結果——

> Task :app:pluginTest

Hello Gradle

項目化

到目前爲止談及到的東西都仍是一個普通的、不能夠發佈到倉庫的插件,若是想要將插件發佈出去供他人和本身在項目中 apply,須要進行如下步驟將插件變成一個 Project——

  1. 更改 build.gradle 文件內容:

    apply plugin: 'groovy'
    
    dependencies {
        compile gradleApi()
        compile localGroovy()
    }
    複製代碼

    此時能夠觀察到 External Libraries 中多出了 gradle-api/gradle-installation-beacon/groovy 庫。其中,gradle 的版本是基於項目下 gradle wrapper 中配置的版本——

    這裏寫圖片描述
    這裏寫圖片描述

  2. 建立 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

  3. build.gradle 文件中經過 apply plugin: '插件名' 引入插件 —— apply plugin: 'com.sample.test'

  4. 在命令行鍵入如下信息:

./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}"
                }
              }
            }
          }
        }
      }
    }
  }
}
複製代碼
  1. 第一篇文中就闡述過,只能在 project.afterEvaluate 閉包中才能獲取到當前 project 中的全部 task 。

  2. 經過 project.plugins.withId('com.android.application') 確保當前 project 是 Android app project 而不是 Android library project,以此來避免無效操做,畢竟 package task 是 com.android.application 中的 task。

  3. 經過 project.android.applicationVariants.all 獲取全部變體信息。

  4. 經過觀察 ApplicationVariant 類的父類 BaseVariant 中 outputs 字段可知道該字段表明着當前變體的輸出信息(DomainObjectCollection 類型),BaseVariantOutput 的子類 ApkVariantOutput 中的 packageApplication 即爲上一篇文章中所說的 PackageAndroidArtifact task 了。

  5. 判斷當前變體是不是 release 的變體。(經過 variantOutput.name.equalsIgnoreCase("release")/variant.name.equalsIgnoreCase("release") 都是能夠的。)

  6. hook 步驟4中所說的 PackageAndroidArtifact task,將圖片複製到 assets 中。

實際上,在平常開發中尋找 task 的方式可能更多的是使用 project.tasks.findByName(name)/project.tasks.getByName(name),這樣也更加方便,筆者在 demo 中附帶了此種寫法,源碼戳我

後續

除了上面提到的 messmess 源碼解析) 和 ThinRPlugin (筆者將會在後續的文章中對 ThinRPlugin 的源碼進行解析)之外,筆者瞭解到的還有一些如下知名的 Gradle 插件可供讀者學習:

固然,前面提到的幾個 plugin 有些重量級,輕量級的筆者沒有了解多少,只能推薦mess 源碼解析做者的一個快速生成R2.java中fields的插件一個快速將指定class打入maindex的插件,對新手瞭解 Gradle plugin 仍是很友好的。

本文實戰模塊的源碼連接:請戳我

筆者新建了微信羣,若是讀者有問題或者對筆者感興趣,歡迎加入,因爲滿了100人,須要先加筆者的微信,微信備註:入羣。

相關文章
相關標籤/搜索