Gradle随记---深掘

谈到Gradle开发,相信很多人第一反应就是开发Gadle Plugin。这倒也没错,plugin是Gradle提供出来,通过apply(Closure closure)方法就可以轻松注入到编译过程中来。
当然,你可以说就开发一个Task类,然后在gradle脚本中引入这个Task,这也是可行的,只不过看起来就似乎没有那么优雅以及显得封装性不是那么完整。

PluginAware

谈起plugin那不可避免就要提到org.gradle.api.plugins.PluginAware,为什么这样说呢?因为,它的含义是可以apply plugin的所在,换句话说只有实现了PluginAware的对象才可以apply plugin

PluginAware继承关系

从上图可以得知,gradle中有两个子接口继承PluginAware,分别是org.gradle.api.Projectorg.gradle.api.initialization.Settings,当然这两个接口也有分别实现的默认类,是对这两个接口的具体实现,暂不延伸去讲了。
因此可知,有两种类型插件,分别是:

  1. Project[build.gradle]
class ProjectPlugin implements Plugin{

    void apply(Project target){
        ...
    }
}
  1. Settings[settings.gradle]
class SettingsPlugin implements Plugin{

    void apply(Settings target){
        ...
    }
}

由此可以根据不同的需求(例如settings才能添加顶级工程的classpath依赖)开发不同类型的Plugin

PluginManager

对,从字面意思来看-插件管理器。无论是Project还是Settings,都是通过org.gradle.api.plugins.PluginManager来对Plugin来进行具体操作的,其中最核心的方法如下。

void apply(String pluginId);

AppliedPlugin findPlugin(String id);

boolean hasPlugin(String id);

void withPlugin(String id, Action action);

很简单但是也最常用,apply是应用插件,findPluginhasPlugin分别是查找插件获得插件信息以及判断插件是否被应用,而withPlugin是表示某个插件被应用的时候执行相应的Action回调,其中方法中参数无论是id还是pluginId都是指插件的id

Plugin

Structure

Plugin通常是作为Library进行开发,工程结构如下:

Plugin Moudle结构

  • Gradle Plugin需要在resources--METAINF--gradle-plugins目录下面定义一个plugin_id.properties文件,文件里面内容就是具体的Plugin实现类的全路径。为什么要这样做呢,其实这是借鉴了JavaSPI(Service Provider Interface)机制,使得整个Plugin的加载更加优雅和解耦。
  • 另外,当插件用groovykotlin进行混合编码的时候,需要注意的是groovykotlin的源码是由各自的编译task进行编译的,因此在相互引用的时候会出现编译找不到类,所以需要在对应的CompileXXTask中的classPath添加编译后的类路径,由于kotlin是在groovy之前编译的,所以CompileGroovyTask可以添加kotlin的类路径,反之则不行,这意味着只能groovy引用kotlin的代码。
  • 最后Plugin开发常规有两种方式,分别是通过在工程里面定义buildSrc module或者常规的module来进行开发。

至于具体的开发细节,这里就不再细讲了,因为就是在

void apply(Project target){
    //todo something
    ...
}

中开发自己的编译逻辑,具体API可以自行查阅官方文档。

Module

这里单独说一下Plugin开发的Module,绝大多数介绍基本就是说可以用buildSrc或者独立module的方式进行开发,但是两者有什么优缺点,如何进行选择,都没有给出相应的解释,因此在这里做一个简单的总结。
这两种方式具体表现是在:

  1. buildSrc module是在整个Project进行configuration前会对这个module进行编译并且把编译后的jar自动加入到Projectclasspath中,直接就可以进行调试。
  2. 独立module的方式相对会麻烦一点,每次需要通过uploadArchivesjar发布到本地,然后手动在顶级工程的build.gradlejar依赖添加到classpath中。

那么,既然第二种调试方式会更加繁琐,是不是第一种方式最好了。其实也不尽然,这两种方式有着各自的优缺点。

  • 第一种方式因为它是在Project进行configuration前就会进行单独的编译,那么就意味着它是一个独立的module,不能去依赖工程里面其他的module,它在单独进行编译的时候其他的module是找不到的,也就意味着它是拿不到Project里面所有的gradle编译信息的。
  • 第二种作为独立的module进行开发的方式,则可以跟正常的module开发一样引用工程里面的其他module

所以,这两种方式各有取舍,需要按照自己的需求来进行选择。

Extension

Plugin开发肯定也会需要用到外界的数据,那怎么得到所要的数据呢?前面提到org.gradle.api.Plugin接口的void apply(T target)方法,对应的泛型对象在gradle中分别是ProjectSettings,那么可以通过Property进行数据的传递。那还有没有更加优雅的方式呢?没错,那就是Extension

ExtensionContainer

通过ProjectgetExtensions方法获得ExtensionContainer对象,然后通过create方法来创建并且添加对应的Extension

 T create(String name, Class type, Object... constructionArguments);

方法对应的参数分别是extension的名称、用来装载的class以及构造对应实例的时候所需要传入的参数。
另外ExtensionContainer还有findByTypegetByType等方法,可以自行查阅org.gradle.api.plugins.ExtensionContainer
那么,标准的extension创建的方法如下:

  1. 定义一个类似于Bean的Extension的装载类
class SampleExtension{
    String id = "" 
}
  1. 插件中创建对应的Extension
void apply(Project target){
    target.extensions.create("sample", Sample)
}
  1. 在对应apply Pluginbuild.gradle中传值
sample{
    id = "smapleExtension_test"
}
  1. 插件中读取extension
project.afterEvaluate{
    SampleExtension sampleExtension = project.sample
    //打印出来就是传值smapleExtension_test
    println "sampleExtension.id-----${sampleExtension.id}"
}

有几个关键点需要注意下:

  • afterEvaluate,也就是project完成整个配置评估后,才会把配置的值装载到对应的extension类中,因此这个时候才能拿到对的值。
  • build.gradle进行extension传值,闭包名称必须和create方法中传入的name保持一致。
  • 获取对应的extension对象,直接通过project.sample也就是闭包名称即可。

另外,我们也可以通过相同的方式在自己的插件中去获取别的插件对应的extension。例如:

project.android.registerTransform("...")

这是Plugin Transform开发中必须要用到的,就是通过project.android拿到对应的extension类,这里就是com.android.build.gradle.BaseExtension对象,然后调用其public void registerTransform(@NonNull Transform transform, Object... dependencies)方法。

Andoird Extension

Android开发中,用到最多就是Gradle提供出来的三个插件:

  1. com.android.build.gradle.AppPlugin,对应app module中的apply plugin: 'com.android.application'
  2. com.android.build.gradle.LibraryPlugin,对应aar module中的apply plugin: 'com.android.library'
  3. org.gradle.api.plugins.JavaPlugin,对应java module中的apply plugin: 'java'

当然,如果用到了kotlin,那么还需要用到对应的kotlin plugin
这里,着重说一下AppPlugin以及LibraryPlugin这两个插件,这两个插件对应的extension分别是com.android.build.gradle.AppExtension以及com.android.build.gradle.LibraryExtension,继承于com.android.build.gradle.TestedExtension
TestedExtension是用于android的test module,继承于所有android插件的基础extension也就是com.android.build.gradle.BaseExtension
里面包含的主要方法有:

public void compileSdkVersion(int apiLevel)

public void buildToolsVersion(String version)

public void defaultConfig(Action action)

public void buildTypes(Action> action)

public void compileOptions(Action action)
...

public void sourceSets(Action> action)

是不是感觉很熟悉,再来看下常见的build.gradle中的配置:

android {
    compileSdkVersion "${compile_sdk_version}" as Integer
    buildToolsVersion "${build_tools_version}"

    defaultConfig {
        minSdkVersion "${min_sdk_version}" as Integer
        targetSdkVersion "${target_sdk_version}" as Integer
        versionCode Integer.valueOf(System.env.BUILD_NUMBER ?: "1")
        versionName "${version}"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_7
        targetCompatibility JavaVersion.VERSION_1_7
    }
    sourceSets {
        main {
            java {
                include '**/*.java'
                include '**/*.kt'
            }
        }
    }
}

应该发现了android闭包里面所有的配置选项对应的就是上述extension中的方法,compileSdkVersionbuildToolsVersiondefaultConfigcompileOptions以及sourceSets都是一一对应的。
另外defaultConfig闭包配置选项对应的是com.android.build.gradle.internal.dsl.DefaultConfig类属性,compileOptions闭包配置对应的就是com.android.build.gradle.internal.CompileOptions,其他以此类推。
由此得知,android{}闭包里面有什么配置选项,直接查看对应的AppExtensionLibraryExtension中的方法和属性就好了,其他插件也是如此。

尾结

至此,Plugin的开发介绍就到此为止了,在这章介绍了Plugin的由来、开发module差异以及最重要的Extension。好了,下章来了解下Gradle中另外一个重要的部分publishing

你可能感兴趣的:(Gradle随记---深掘)