Gradle理論與實踐四:自定義Gradle插件

Gradle系列相關文章
1、Gradle理論與實踐一:Gradle入門
2、Gradle理論與實踐二:Groovy介紹
3、Gradle理論與實踐三:Gradle構建腳本基礎
4、Gradle理論與實踐四:自定義Gradle插件
5、Gradle配置subprojects和allprojects的區別:subprojects和allprojects的區別

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'
                    ]
        }
    }

更多設置請移步官網:
https://developer.android.com/studio/build/build-variants

二、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目錄下新建了一個插件類,如下:


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,並依賴於CustomPluginTaskCustomPluginTask中定義了一個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'

最終的文件如下:


插件編寫完成後,在Android Studio的右上角打開Gradle,執行:plugin分組中的publish命令,執行完成後,會在項目根目錄下生成repo倉庫:


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目錄下是否有我們想要的文件:


可以看到在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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章