Android Gradle学习(五):Extension详解

前面我们已经详细讲解了 Gradle 的 Task、Project 等基本用法,现在我们还要学习一个很重要的概念 Extension,它在 Gradle 中几乎随处可见,特别是在 Android 打包配置中。

1. 什么是Extension

我们先来看一段 Android 应用的 Gradle 配置代码:

android {
    compileSdkVersion 26
    defaultConfig {
        applicationId "com.hm.iou.thinapk.demo"
        minSdkVersion 19
        targetSdkVersion 26
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

相信做 Android 应用开发的同学,对这段代码都快看吐了吧。记得当初刚从 eclipse 转到 Android Studio 的时候,看这些配置就像看天书一样,只知道按规定配置就可以了。但是为什么要这样配置?除此外还支持哪些配置?为什么一定要在 android 这个命名空间下配置呢?可以不可以定义自己的特殊配置呢?

上面这个 android 打包配置,就是 Gradle 的 Extension,翻译成中文意思就叫扩展。它的作用就是通过实现自定义的 Extension,可以在 Gradle 脚本中增加类似 android 这样命名空间的配置,Gradle 可以识别这种配置,并读取里面的配置内容。

2. 怎么定义Extension

2.1 ExtensionContainer

一般我们通过 ExtensionContainer 来创建 Extension,这个类与 TaskContainer 命名有点类似,TaskContainer 是用来创建并管理 Task 的,而 ExtensionContainer 则是用来创建并管理 Extension 的。通过 Project 的以下 API 可以获取到 ExtensionContainer 对象:

ExtensionContainer getExtensions()
2.2 简单的Extension
//先定义一个普通的java类,包含2个属性
class Foo {
    int age
    String username

    String toString() {
        return "name = ${username}, age = ${age}"
    }
}
//创建一个名为 foo 的Extension
getExtensions().create("foo", Foo)

//配置Extension
foo {
    age = 30
    username = "hjy"
}

task testExt << {
    //能直接通过 project 获取到自定义的 Extension
    println project.foo
}

上面这个例子中,foo 就是我们自定义的 Extension 了,它里面能配置的属性与类 Foo 中的字段是一致的,在 build.gradle 中可以直接通过 project.foo 来访问。每个 Extension 实际上与某个类是相关联的,在 build.gradle 中通过 DSL 来定义,Gradle 会识别解析并生成一个对象实例,通过该类可以获取我们所配置的信息。

之前有讲过 Project 有个扩展属性是通过 ext 命名空间配置的,可以看到 ext 与这里是类似的,不同的是 ext 可以配置任何键值对的属性值,而这里只能识别我们定义的 Java 类里的属性值。

2.3 ExtensionContainer主要API功能及用法
2.3.1 创建Extension
<T> T create(String name, Class<T> type, Object... constructionArguments)
<T> T create(Class<T> publicType, String name, Class<? extends T> instanceType, Object... constructionArguments)

先来看看后面这个 API 所有参数的含义。

  • publicType:创建的 Extension 实例暴露出来的类类型;
  • name:要创建的Extension的名字,可以是任意符合命名规则的字符串,不能与已有的重复,否则会抛异常;
  • instanceType:该Extension的类类型;
  • constructionArguments:类的构造函数参数值

官方文档里还说明了一个特性,创建的 Extension 对象都默认实现了 ExtensionAware 接口,

The new instance will have been dynamically made ExtensionAware, which means that you can cast it to ExtensionAware.

我们来看一个具体的实例,包含了上面2个 API 的使用:

//父类
class Animal {
    
    String username
    int legs

    Animal(String name) {
        username = name
    }
    
    void setLegs(int c) {
        legs = c
    }

    String toString() {
        return "This animal is $username, it has ${legs} legs."
    }
}

//子类
class Pig extends Animal {
    
    int age
    String owner

    Pig(int age, String owner) {
        super("Pig")
        this.age = age
        this.owner = owner
    }

    String toString() {
        return super.toString() + " Its age is $age, its owner is $owner."
    }

}

//创建的Extension是 Animal 类型
Animal aAnimal = getExtensions().create(Animal, "animal", Pig, 3, "hjy")
//创建的Extension是 Pig 类型
Pig aPig = getExtensions().create("pig", Pig, 5, "kobe")

animal {
    legs = 4    //配置属性
}

pig {
    setLegs 2   //这个是方法调用,也就是 setLegs(2)
}

task testExt << {
    println aAnimal
    println aPig
    //验证 aPig 对象是 ExtensionAware 类型的
    println "aPig is a instance of ExtensionAware : ${aPig instanceof ExtensionAware}"
}

运行 testExt 这个任务,查看结果如下:

This animal is Pig, it has 4 legs. Its age is 3, its owner is hjy.
This animal is Pig, it has 2 legs. Its age is 5, its owner is kobe.
aPig is a instance of ExtensionAware : true
2.3.2 增加Extension

前面的 create() 方法会创建并返回一个 Extension 对象,与之相似的还有一个 add() 方法,唯一的差别是它并不会返回一个 Extension 对象。

void add(Class<T> publicType, String name, T extension)
void add(String name, T extension)

基于前面的这个实例,我们可以换一种写法如下:

getExtensions().add(Pig, "mypig", new Pig(5, "kobe"))
mypig {
    username = "MyPig"
    legs = 4
    age = 1
}
task testExt << {
    def aPig = project.getExtensions().getByName("mypig")
    println aPig
}
2.3.3 查找Extension
Object findByName(String name)
<T> T findByType(Class<T> type)
Object getByName(String name)       //找不到会抛异常
<T> T getByType(Class<T> type)  //找不到会抛异常

这几个 API 很好理解,一个是通过名字去查找,一个是通过类类型去查找。

2.4 嵌套Extension

类似下面这样的配置应该随处可见:

outer {
    
    outerName "outer"
    msg "this is a outer message."

    inner {
        innerName "inner"
        msg "This is a inner message."
    }
    
}

形式上就是外面的 Extension 里面定义了另一个 Extension,这种叫做 nested Extension,也就是嵌套的 Extension。本文开头的 Android 打包配置,就是采用的这种方式。

那怎么创建上面这种 Extension 呢?

class OuterExt {
    
    String outerName
    String msg
    InnerExt innerExt = new InnerExt()

    void outerName(String name) {
        outerName = name
    }

    void msg(String msg) {
        this.msg = msg
    }
    
    //创建内部Extension,名称为方法名 inner
    void inner(Action<InnerExt> action) {
        action.execute(inner)
    }

    //创建内部Extension,名称为方法名 inner
    void inner(Closure c) {
        org.gradle.util.ConfigureUtil.configure(c, innerExt) 
    }

    String toString() {
        return "OuterExt[ name = ${outerName}, msg = ${msg}] " + innerExt
    }

}


class InnerExt {
    
    String innerName
    String msg

    void innerName(String name) {
        innerName = name
    }

    void msg(String msg) {
        this.msg = msg
    }

    String toString() {
        return "InnerExt[ name = ${innerName}, msg = ${msg}]"
    }

}

def outExt = getExtensions().create("outer", OuterExt)

outer {
    
    outerName "outer"
    msg "this is a outer message."

    inner {
        innerName "inner"
        msg "This is a inner message."
    }

}

task testExt << {
    println outExt
}

运行结果如下:

OuterExt[ name = outer, msg = this is a outer message.] InnerExt[ name = inner, msg = This is a inner message.]

这里的关键点在于下面这2个方法的定义,只需要定义任意一个即可:

void inner(Action<InnerExt> action)
void inner(Closure c)

定义在 outer 内部的 inner ,Gradle 解析时实质上会进行方法调用,也就是会执行 outer.inner(...) 方法,而该方法的参数是一个闭包(俗称 Script Block),所以在类 OuterExt 中必须定义 inner(...) 方法。

此外,前面说到创建的 Extension 对象都是实现了 ExtensionAware 接口的,ExtensionAware 接口很简单,只包含一个方法:

ExtensionContainer getExtensions()

所以还有一种方式来创建嵌套的 Extension,只不过这种方式没法自动赋值到 OuterExt 类里的 innerExt 对象:

def innerExt = outExt.getExtensions().create("inner", InnerExt)

3. Android的Extension

先看个 Android 的常规配置,以下是我的项目配置,截图如下所示:

Android 配置

我们重点看看 defaultConfig、productFlavors、signingConfigs、buildTypes 这4个内部 Extension对象是怎么定义的,通过查看源码可以找到一个叫 BaseExtension 的类,里面的相关代码如下:

    private final DefaultConfig defaultConfig;
    private final NamedDomainObjectContainer<ProductFlavor> productFlavors;
    private final NamedDomainObjectContainer<BuildType> buildTypes;
    private final NamedDomainObjectContainer<SigningConfig> signingConfigs;

    public void defaultConfig(Action<DefaultConfig> action) {
        this.checkWritability();
        action.execute(this.defaultConfig);
    }
    
     public void buildTypes(Action<? super NamedDomainObjectContainer<BuildType>> action) {
        this.checkWritability();
        action.execute(this.buildTypes);
    }

    public void productFlavors(Action<? super NamedDomainObjectContainer<ProductFlavor>> action) {
        this.checkWritability();
        action.execute(this.productFlavors);
    }

    public void signingConfigs(Action<? super NamedDomainObjectContainer<SigningConfig>> action) {
        this.checkWritability();
        action.execute(this.signingConfigs);
    }

这与前面介绍的嵌套 Extension 的定义是一致的,这里名为 android 的 Extension 是通过插件来创建的,关于插件的创建在我另一篇文章也有介绍:怎么创建Gradle插件

在 app 的 build.gradle 里我们通常会采用插件 apply plugin: 'com.android.application' ,而在 library module 中则采用插件 apply plugin: 'com.android.library',先来看一张截图:

plugin.jpg

图中类 AppPlugin 就是插件 com.android.application 的实现类,LibraryPlugin 则是插件 com.android.library 的实现类,接着再看看 AppPlugin 里是怎样创建 Extension 的:

public class AppPlugin extends BasePlugin implements Plugin<Project> {
    @Inject
    public AppPlugin(Instantiator instantiator, ToolingModelBuilderRegistry registry) {
        super(instantiator, registry);
    }

    protected BaseExtension createExtension(Project project, ProjectOptions projectOptions, Instantiator instantiator, AndroidBuilder androidBuilder, SdkHandler sdkHandler, NamedDomainObjectContainer<BuildType> buildTypeContainer, NamedDomainObjectContainer<ProductFlavor> productFlavorContainer, NamedDomainObjectContainer<SigningConfig> signingConfigContainer, NamedDomainObjectContainer<BaseVariantOutput> buildOutputs, ExtraModelInfo extraModelInfo) {
        return (BaseExtension)project.getExtensions().create("android", AppExtension.class, new Object[]{project, projectOptions, instantiator, androidBuilder, sdkHandler, buildTypeContainer, productFlavorContainer, signingConfigContainer, buildOutputs, extraModelInfo});
    }

    public void apply(Project project) {
        super.apply(project);
    }

    //省略...
}

在 createExtension() 方法中,可以看到创建了一个名为 android 的 Extension,该 Extension 的类型为 AppExtension,而 AppExtension 的继承结构为 AppExtension -> TestedExtension -> BaseExtension,所以它的实现逻辑大部分都是在 BaseExtension 里实现的。

LibraryExtension 的继承结构与 AppExtension 基本是一致的,有兴趣的可以自己看源码研究研究。

以后当我们不知道 android 里有哪些配置时,除了查看 API 文档以外,还可以直接翻看 BaseExtension 源码,基本上就能清楚了。

系列文章

Android Gradle学习(一):Gradle基础入门
Android Gradle学习(二):如何创建Task
Android Gradle学习(三):Task进阶学习
Android Gradle学习(四):Project详解
Android Gradle学习(五):Extension详解
Android Gradle学习(六):NamedDomainObjectContainer详解
Android Gradle学习(七):Gradle构建生命周期
Android Gradle学习(八):统计Task执行时长
Android Gradle学习(九):一些有用的小技巧

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

推荐阅读更多精彩内容

  • afinalAfinal是一个android的ioc,orm框架 https://github.com/yangf...
    wgl0419阅读 6,263评论 1 9
  • 1、通过CocoaPods安装项目名称项目信息 AFNetworking网络请求组件 FMDB本地数据库组件 SD...
    阳明先生x阅读 15,967评论 3 119
  • 人与人的差距根本不在智商,而是在思维的高度上。整天纠缠于鸡毛蒜皮,就会忽略那些大的人生命题。决定人生上限的,往往不...
    东方新材粉末彩涂板阅读 204评论 0 0
  • 读了好多鸡汤文说挣钱得重要性,有钱得重要性,大多说得非常现实, 无非未来和意外不知道哪一个先来,无非有...
    静水凌波阅读 184评论 0 0
  • 阿菱:这多大的海滩啊,怎么没有人啊! 阿球:我们不是来了吗?你是在缅怀夏日的海滩吧? 阿菱:对,夏日的海滩,嘈杂喧...
    云水坡头阅读 489评论 0 0