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<Project>{

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

    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<? super AppliedPlugin> 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> T create(String name, Class<T> 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<DefaultConfig> action)

public void buildTypes(Action<? super NamedDomainObjectContainer<BuildType>> action)

public void compileOptions(Action<CompileOptions> action)
...

public void sourceSets(Action<NamedDomainObjectContainer<AndroidSourceSet>> 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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,080评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,422评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,630评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,554评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,662评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,856评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,014评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,752评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,212评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,541评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,687评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,347评论 4 331
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,973评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,777评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,006评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,406评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,576评论 2 349

推荐阅读更多精彩内容

  • 说明 本文主要介绍和Gradle关系密切、相对不容易理解的配置,偏重概念介绍。部分内容是Android特有的(例如...
    jzj1993阅读 15,599评论 1 62
  • 在 Android Studio 构建的项目中,基于 Gradle 进行项目的构建,同时使用 Android DS...
    Ant_way阅读 7,331评论 0 16
  • 谈起Gradle开发,自然就需要先从最基础的两个配置文件build.gradle以及settings.gradle...
    Kael_Huang阅读 1,302评论 3 2
  • 前言 从2013年Google推出Android Studio(后面以AS简称)开始,到现在已经历经3年,版本也发...
    dfqin阅读 1,828评论 1 3
  • Android中Gradle实用指南 Gradle让Android中的依赖管理、库管理、渠道管理以及一些动态地编译...
    Rayhaha阅读 6,037评论 2 53