Gradle系列(2)——如何自定以Gradle 插件

声明:本篇以及系列Gradle相关帖子均是基于gradle-7.3.3版本

1.简介gradle plugin

每一个gradle release版本分3中类型,以7.3.3版本为例:

在wrapper>gradle-wrapper.properties内:

distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip声明使用的gradle版本。

  • 7.3.3-doc,类型只有文档
  • 7.3.3-bin,只有源代码
  • 7.3.3-all ,l既有文档又有源代码

国内 腾讯gradle release镜像

1.1 什么是gradle plugin

gradle plugin是应用在AndroidStudio编译脚本中的插件,是一种用于扩展和定制Gradle构建系统功能的机制。
其中打包了可重复利用的编译逻辑,这些逻辑可以是一些便捷的功能便于我们快速实现编译逻辑,也可以包含特定的逻辑实现特定的功能,包括执行测试,打包,生成文档等等,
gradle plugin提供了构建灵活性,可以分享给不同的人,并在不同的项目中应用;
r如“com.android.application”,“com.android.library”,

1.2 编写语言

DSL(Domain Specific Language)领域特定语言,它是编程语言赋予开发者的一种特殊能力,通过它我们可以编写出一些看似脱离其原始语法结构的代码,从而构建出一种专有的语法结构。

gradle5.0之后开始支持Kotlin

Groovy 是Apache旗下的一种强大的、可选类型的和动态的语言,具有静态类型和静态编译功能,用于Java平台,旨在通过简洁、熟悉和易于学习的语法提高开发人员的生产力。它与任何Java程序顺利集成,并立即为您的应用程序提供强大的功能,包括脚本功能、领域特定语言创作、运行时和编译时元编程以及函数编程。
并且,Groovy可以与Java语言无缝衔接,可以在Groovy中直接写Java代码,也可以在Java中直接调用Groovy脚本,非常丝滑,学习成本很低。

7.0之后,project 下的build.gradle 发生改变,默认只有plugin配置,其它的逻辑挪到setting.gradle

2. gradle 生命周期

  • initialization(初始化)阶段

在这个阶段,Gradle 会读取settings.gradle。这个文件内包含插件管理,插件仓库管理,项目依赖仓库管理,最重要的是要读取module,gradle会为每一个module创建一个project。

  • Configuration (配置)阶段

在这个阶段,Gradle 会读取包含的所有构建脚本,应用插件,使用DSL配置构建,并在最后注册Task。

  • Execution (执行)阶段

Gradle会根据Task的依赖关系按序执行每一个Task。

2.1 监听gradle生命周期

在setting.gradle内:


println("this is settings")
gradle.addBuildListener(object : BuildListener {

    override fun beforeSettings(settings: Settings) {
        super.beforeSettings(settings)
        println("beforeSettings")
    }

    override fun settingsEvaluated(settings: Settings) {
        println("settingsEvaluated")
    }

    override fun projectsLoaded(gradle: Gradle) {
        println("projectsLoaded:rootProject.name:" + gradle.rootProject.name + ",projectNum:" + gradle.rootProject.allprojects.size)
    }

    override fun projectsEvaluated(gradle: Gradle) {
        println("projectsEvaluated:rootProject.name:" + gradle.rootProject.name + ",projectNum:" + gradle.rootProject.allprojects.size)

    }

    override fun buildFinished(result: BuildResult) {
        println("buildFinished")
    }
})


在app module里添加


println("in main module")
android {
    println("android config")

    //...
}


点击同步,看build输出:


this is settings
settingsEvaluated
projectsLoaded:rootProject.name:BoosterApply,projectNum:3

> Configure project :app
-----------------gradle-rocket start-----------
[SignatureConfigImpl]: start apply signature config >>> 
    isApplication:true
    taskName:[]
    configProguardFile buildType:debug
    configProguardFile buildType:release
[VersionConfigImpl]:start apply common-version config >>>
javaVersion:11,each:11
[DependenciesConfigImpl]:start apply dependencies configs >>>
[OtherConfigImpl]:start apply other config >>> 
-----------------gradle-rocket end -----------
booster config
in main module
android config

> Configure project :lib-support
-----------------gradle-rocket start-----------
[SignatureConfigImpl]: start apply signature config >>> 
    isApplication:false
    project is not app,Booster won`t apply signature
    configProguardFile buildType:debug
    configProguardFile buildType:release
[VersionConfigImpl]:start apply common-version config >>>
javaVersion:11,each:11
[DependenciesConfigImpl]:start apply dependencies configs >>>
[OtherConfigImpl]:start apply other config >>> 
-----------------gradle-rocket end -----------
projectsEvaluated:rootProject.name:BoosterApply,projectNum:3

> Task :prepareKotlinBuildScriptModel UP-TO-DATE
buildFinished

BUILD SUCCESSFUL in 2s

从输出可以看出,上述明显分为3个阶段:

第一阶段读取Settings,然后加载project,这个时候出现3个project(两个子module+rootProject);

第二阶段就是读取module里的build.gradle,配置对应的project。可以看到,在上述先后配置两个module project。"gradle-rocket"是一个插件。

第三阶段,开始执行task,最后buildFinish(此处我只是同步项目,不是打包,但是都是一样的)。

我们的插件就是要在configure阶段做我们的事情。

3.gradle plugin实现

3.1 集成方式

例如我在项目编译完成之后提示编译完成:

在module build.gradle内



class GreetingPlugin:Plugin<Project>{

    override fun apply(target: Project) {
        println("this is my greetingPlugin !")
        //eg:添加一个依赖
        target.dependencies.add("api","com.google.code.gson:gson:2.10")
        //eg:配置完成输入一段日志
        target.afterEvaluate{
            println("Greeting plugin ,afterEvaluate")
        }
    }
}
apply<GreetingPlugin>()


在这个plugin中,做了两件事:添加一个依赖,配置完成输出一段日志。

输出结果:


this is settings
settingsEvaluated
projectsLoaded:rootProject.name:BoosterApply,projectNum:3

> Configure project :app

this is my greetingPlugin !
Greeting plugin ,afterEvaluate

> Configure project :lib-support

projectsEvaluated:rootProject.name:BoosterApply,projectNum:3

> Task :prepareKotlinBuildScriptModel UP-TO-DATE
buildFinished

BUILD SUCCESSFUL in 4s


可以看到输出了我们插件的日志。

然后我们使用gradle app:dependencies命令查看依赖是否添加成功:


+--- project :app (*)
+--- androidx.test:runner:1.5.2

+--- com.android.support.test.espresso:espresso-core:3.0.2 -> androidx.test.espresso:espresso-core:3.1.0-alpha3

+--- androidx.test.ext:junit:1.1.3

+--- androidx.appcompat:appcompat:1.4.1

...

+--- com.google.code.gson:gson:2.10
+--- androidx.databinding:viewbinding:7.1.2
|    \--- androidx.annotation:annotation:1.0.0 -> 1.3.0
+--- com.alibaba:fastjson:2.0.31
...
\--- androidx.lifecycle:lifecycle-common:{strictly 2.4.0} -> 2.4.0 (c)



可以看到打印的依赖树(删减部分)中有在插件中添加的gson。

这种实现插件的方式简单快捷,但会导致项目的配置文件臃肿,不易阅读维护,同时也不方便项目之间共享

3.2 buildSrc

复杂的构建逻辑适合封装在单独的插件中,而且自定义任务和插件的实现也不应该放在build.gradle中。这些逻辑应该自成一体,独立存放,所以buildSrc应用而生,buildSrc就非常的方便了,它在单个项目中使用非常的方便。

buildSrc是一个特殊的included module,无需显式的声明在setting.gradle中;一个项目只能存在一个且必须存在根目录中,gradle会自动识别编译这个目录;buildSrc的执行顺序优先于childProject。

接下来就是实现:

3.2.1 初始化buildSrc
  • 创建buildSrc/main 目录

  • 创建 buildSrc/build.gradle.kts(当前dsl我是用的是kotlin,也可以使用groovy),添加以下内容:


plugins{
//    `kotlin-dsl`
//    `groovy`
    `java-gradle-plugin`
}

java {
    sourceCompatibility = JavaVersion.VERSION_11
    targetCompatibility = JavaVersion.VERSION_11
}

然后同步之后就可以看到:

![buildSrc tree][1]

注意:Gradle6.4以前需要依赖gradleApi(),之后的版本默认提供支持

3.2.2 创建开发目录以及plugin

在上一步应用的plugins中,有3中方案:

  • groovy
  • kotlin-dsl
  • java-library

三种plugin对应我们插件的3种实现语言,选择某一种语言,就要在main下创建对应的目录。

eg:我们当前选择java语言,那么使用java-library,随后就需要在创建buildSrc/main/src/java/目录

加入测试类:



public class JavaPlugin implements Plugin<Project> {
    @Override
    public void apply(Project target) {
        System.out.println("hello from javaPlugin ");
    }
}


eg:如果使用kotlin语言,即kotlin-dsl,就需要创建buildSrc/main/src/kotlin/目录

加入测试类:


class GreetingPlugin :Plugin<Project> {
    override fun apply(target: Project) {
        println("hello from kotlin plugin !")
    }
}

3.2.3 绑定plugin
  • 首先需要确定你的插件id,插件id:eg:com.example.plugin

  • 创建buildSrc/main/resources/META-INF/gradle-plugins/目录

  • 在该目录创建以你的id命名的文件:com.example.plugin.properties,

内容为:implementation-class=JavaPlugin

此处JavaPlugin对应你的插件的类名称

3.2.4 使用你的插件

在其它module build.gradle内:



plugins {
    id("com.android.library")
    //---应用我的插件
    id("com.example.plugin")
}

android {
    namespace = "com.avatr.support"
    compileSdk = 31
    defaultConfig {

        targetSdk = 31
        minSdk = 28
    }
}

java {
    sourceCompatibility = JavaVersion.VERSION_11
    targetCompatibility = JavaVersion.VERSION_11
}

dependencies {
    //使用我的插件内定义的三方库
    implementation(Libs.appCompat)
}

到这一步,我们插件已经很强大了,可以给项目的不同module使用,不同module都可以复用相同的逻辑,减少了很多不必要的配置

但是!还是不符合我们团队化使用,不能够在项目&&团队之间共享。

插入一个版本三方库管理方案:

便捷的版本管理方案

相信大家都碰到一个场景,一个项目里有多个module,module需要进行sdk版本管理,三方库管理,java版本等版本管理,每次创建一个新的module都需要花点时间配置一下。

通常的方案是项目根目录创建一个config.gradle,在ext里面统一声明这些内容,然后在build.gradle apply from config.gradle ,然后就可以引用ext里的内容。

在7.0以后,官方推出了一个版本管理方案,Version Catalogs,是一种新的依赖管理方式,其中一种是通过.toml文件定义所有依赖项和版本信息。这个方法的一个优点是能够集中管理所有依赖的版本,减少版本冲突的可能性。步骤如下

  • 在项目的根目录下创建一个名为dependencies.toml的.toml文件,定义依赖项。

# dependencies.toml

[dependencies]
appCompat = "com.android.support:appcompat-v7:28.0.0"
firebaseCore = "com.google.firebase:firebase-core:20.0.0"
# 添加更多依赖...

  • 在主项目的settings.gradle.kts文件中,指定Version Catalogs的位置:

// settings.gradle.kts

dependencyResolutionManagement {
    // 指定Version Catalogs的位置
    versionCatalogs {
        create("dependencies") {
            from("${rootProject.projectDir}/dependencies.toml")
        }
    }
}

  • 在module的build.gradle.kts文件中引用Version Catalogs,并使用其中的依赖项:

// build.gradle.kts

dependencies {
    // 使用Version Catalogs中的依赖项
    implementation dependencies.appCompat
    implementation dependencies.firebaseCore
    // 添加更多依赖...
}

这种方案优点非常明显,使用简单,非常适用于大型多module项目,轻松管理依赖,减少版本冲突,并且可以做到检测升级;小小的缺点就是需要一点小小的学习成本

我们的buildSrc也可以实现三方库版本管理,而且跟为方便。

以我们java语言为例

在buildSrc/src/main/java加入一个依赖管理类


public interface Libs {
    String appCompat = "androidx.appcompat:appcompat-resources:1.6.1";
}


gradle给的支持非常强大,你在编辑后面的库信息时,gradle可以自动提示;

  • 使用插件的三方库

在module的build.gradle


dependencies {
    implementation(Libs.appCompat)
}

这两步,非常简单由直观!

3.3 单独的plugin项目

第二种方案相较于第一种方案,已经灵活便捷很多了,但是这种方案仍然有弊端,它并不是一个独立的个体,而是依附在项目里,依赖项目整体的编译,这种方案适用于单个项目多个module的情况。

我们的目标是最大化的通用性,不仅要实现module之间,更是要在项目和团队之间共享,因此第二种方案还是不够,所以我们介绍第三种这种方案,也是官方推荐的方案,也是大家都采用的方案。

在单独的gradle plugin项目专注于plugin开发,不仅可以进行以上两种方案的实现,更是可以将插件单独发布(虽然第二种方案也可以),但是它具有独立性,专一性。

这一段不进行详细的讲解,详细的讲解将在实战片段讲解。

4.gradle plugin

4.1 初始化项目

初始化的方式可以用手动创建空项目,然后创建名为plugin的module,然后进行后续配置,但是Gradle 为gradle plugin开发提供了更为便捷的方案:

在你的workSpace打开终端:
在这里插入图片描述

4.2 明确需求

  • sdk、java版本配置

通常每一个module创建都会有相同的android sdk版本,java版本配置。

  • 公共的库依赖

例如单元测试,appCompat,网络等等

  • 特性配置

例如viewBinding等等。

4.3 实现sdk版本配置

抽象一个config类:


public interface IRocketConfig {

    /**
     * 
     * @param project 当前module
     * @param rootExtension 当前module应用的plugin:com.android.library/com.android.application
     */
    void applyConfig(Project project, IRootExtension rootExtension);
}


sdk版本配置实现类


public class VersionConfigImpl implements IRocketConfig {
    private static final String TAG = "[VersionConfigImpl]:";

    @Override
    public void applyConfig(Project project, IRootExtension extension, BoosterPlatformConfig platformConfig) {
        System.out.println(TAG + "start apply common-version config >>>");

        //设置sdk版本号
        extension.setCompileSdkVersion(33);
        extension.getDefaultConfig().setMinSdkVersion(28);
        extension.getDefaultConfig().setTargetSdkVersion(33);
        extension.getDefaultConfig().testInstrumentationRunner("androidx.test.runner.AndroidJUnitRunner");
        //java 版本
        extension.getCompileOptions().setSourceCompatibility(JavaVersion.VERSION_11);
        extension.getCompileOptions().setTargetCompatibility(JavaVersion.VERSION_11);

    }
}


4.4 实现依赖配置

  • 实现一个三方库管理类
public interface RocketLibs {

    //------------------------------------------Base-------------------------------------------

    String androidTestRunner = "androidx.test:runner:" + LibsVersion.androidTestRunner;
    String jUnit = "junit:junit:" + LibsVersion.jUnit;
    String extjUnit = "androidx.test.ext:junit:" + LibsVersion.extjUnit;

    //------------------------------------------View-------------------------------------------

    String recyclerView = "androidx.recyclerview:recyclerview:" + LibsVersion.recyclerView;
    String appCompat = "androidx.appcompat:appcompat:" + LibsVersion.appCompat;
}



public interface LibsVersion {

    String androidTestRunner = "1.5.2";
    String espressoCore = "3.0.2";
    String jUnit = "4.12";

    String recyclerView = "1.3.0";
    String appCompat = "1.4.1";

 }

  • 实现三方库配置类:
public class DependenciesConfigImpl implements IRocketConfig Config {
    private static final String TAG = "[DependenciesConfigImpl]:";

    @Override
    public void applyConfig(Project project, IRootExtension rootExtension) {
        System.out.println(TAG + "start apply dependencies configs >>>");
        DependencyHandler dependencies = project.getDependencies();
        dependencies.add("androidTestImplementation", RocketrLibs.androidTestRunner);
        dependencies.add("androidTestImplementation", RocketLibs.espressoCore);
        dependencies.add("androidTestImplementation", RocketLibs.extjUnit);
        dependencies.add("testImplementation", BoosterLibs.jUnit);
//        dependencies.add("api", RocketLibs.commonUtilsAndroid);
        dependencies.add("api", RocketLibs.appCompat);
//        dependencies.add("api", RocketLibs.okHttp3);
//        dependencies.add("api", RocketLibs.recyclerView);
    }
}

4.5 特性配置


public class FeatureConfigImpl implements IRocketConfig {
    private static final String TAG = "[OtherConfigImpl]:";

    @Override
    public void applyConfig(Project project, IRootExtension extension, BoosterPlatformConfig config) {
        System.out.println(TAG + "start apply other config >>> ");
        //使用ViewBinding
        if (config != null && config.featureConfig != null) {
            extension.getViewBinding().setEnabled(true);
        }
    }
}

4.6 应用配置

在Plugin内应用上述的实现类

    @Override
    public void apply(Project project) {


        //找到BaseExtension,主要确定module是Application还是Library
        final RootExtensionDetector rootExtensionDetector = new RootExtensionDetector(project);
        final IRootExtension rootExtension = rootExtensionDetector.getRootExtension();

		//签名配置
        IRocketConfig signatureConfig = new SignatureConfigImpl();
        signatureConfig.applyConfig(project, rootExtension, mAllPlatformConfig);
		//sdk版本配置
        IRocketConfig versionConfig = new VersionConfigImpl();
        versionConfig.applyConfig(project, rootExtension, mAllPlatformConfig);
		//依赖配置
        IRocketConfig dependenciesConfig = new DependenciesConfigImpl();
        dependenciesConfig.applyConfig(project, rootExtension, mAllPlatformConfig);
		//特性配置
        IRocketConfig otherConfig = new FeatureConfigImpl();
        otherConfig.applyConfig(project, rootExtension, mAllPlatformConfig);
		//flavor配置
        IRocketConfig flavorConfig = new FlavorConfigImpl();
        flavorConfig.applyConfig(project, rootExtension, mAllPlatformConfig);

    }

以上是应用全部实现我们插件需求的配置类,不再一一展开

4.7 发布插件

我之前的一篇帖子讲过maven-publish 如何发布项目到maven

在plugin的builde.gradle内


//公司仓库地址
val mavenBaseUrl = "http://maven.example.company"
//发布到本地
val isPublish2Local = true;

//绑定plugin
gradlePlugin {
    // kotlin版本
    val gradleBooster by plugins.creating {
        id = "gradle-booster"
        implementationClass = "com.example.RoecktPlugin"
    }
}

//注册发布任务
publishing {
    repositories {
        //仓库配置
        maven {
            url = uri(
                //发布到本地
                if (isPublish2Local) {
                    "../../WilsonMaven/Dependency"
                } else {
                    //发布到远程仓库
                    "$mavenBaseUrl /repository/cockpit-snapshots/"
                }
            )
            //发布到公司maven需要配置密码
            if (!isPublish2Local) {
                credentials {
                    username = avatrMavenUserName
                    password = avatrMavenUserPW
                }
            }
        }
    }
    //发布版本信息
    publications {
        create<MavenPublication>("booster") {
            groupId = "com.example.company"
            //插件id,和上面绑定的插件一致
            artifactId = "gradle-rocket"
            //插件版本号
            version = "0.0.0"
            from(components["java"])
        }

    }
}



然后同步之后,右侧gradle task里面会出现“publish”的task,双击运行这个task,提示发布成功可以查看

查看配置的目录下:
在这里插入图片描述

5 使用插件

  • 其它应用内根目录build.gradle(或setting.gradle)引入插件仓库


allprojects {
    //给项目配置
    repositories {
        google()
        mavenCentral()
        maven {
            allowInsecureProtocol = true
            url = "../WilsonMaven/Dependency"
        }
    }
}

buildscript {
    //给gradle配置插件仓库
    repositories {

        maven {
            allowInsecureProtocol = true
            url = "../WilsonMaven/Dependency"
        }

    }

    dependencies {
        classpath("com.android.tools.build:gradle:7.1.2")
        //引入插件
        classpath group: "com.example.company", name: "gradle-rocket", version: "0.0.0"
    }
}

classpath group: "com.example.company", name: "gradle-rocket", version: "0.0.0" 就是引入了我们的插件,可以看出来一个完整的插件包括groupname(对应插件id),version 和插件配置的内容是一一对应的。

  • 在项目的app mudle 应用插件

import com.example.company.libs.RocketLibs

plugins {
    id("com.android.application")
    //应用插件
    id("gradle-rocket")
}

android {
    namespace = "com.example.company.app"
    defaultConfig {
        applicationId = "com.example.company.app"
        versionCode = 1
        versionName = "1.0"
    }

    dependencies {
        //使用插件内的三方库
        implementation(RocketLibs.fastJson)
    }

}


  • library级别module配置:
plugins {
    id("com.android.library")
    //---应用我的插件
    id("gradle-rocket")
}

android {
    namespace = "com.example.support"
}


是不是十分简洁!!!

我们的插件内已经帮我们做了sdk版本配置,java版本配置,签名混淆配置等等我们需要一一去配置的东西


到此,做为一个平台化的gradle插件,已经实现了很多便捷的功能,但是各位大佬仍然可以展开你的想想实现更多的配置,上面我还提到了签名配置 渠道包配置,由于代码量比较大,我并未贴代码,各位可以自行实现,如果有需要可以找我要代码。

扩展:可以看到上述实例代码,很多配置都是写死的,假如不同的团队有不同的配置怎么办呢,是不是可以本地配置一个json文件,然后插件初始化的时候读取进来,根据文件里的配置来配置项目呢,嘿嘿嘿

下一篇讲如何自定义扩展

参考:

gradle官方文档

gradle系列精讲

  • 15
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值