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执行时长

推荐阅读更多精彩内容

  • afinalAfinal是一个android的ioc,orm框架 https://github.com/yangf...
    wgl0419阅读 3,400评论 1 8
  • 人与人的差距根本不在智商,而是在思维的高度上。整天纠缠于鸡毛蒜皮,就会忽略那些大的人生命题。决定人生上限的,往往不...
    东方新材粉末彩涂板阅读 81评论 0 0
  • 读了好多鸡汤文说挣钱得重要性,有钱得重要性,大多说得非常现实, 无非未来和意外不知道哪一个先来,无非有...
    静水凌波阅读 70评论 0 0
  • 阿菱:这多大的海滩啊,怎么没有人啊! 阿球:我们不是来了吗?你是在缅怀夏日的海滩吧? 阿菱:对,夏日的海滩,嘈杂喧...
    云水坡头阅读 237评论 0 0
  • 夜深人静花睡了 露重凉透人寐了 虫鸣乐,蛙声倦 花亮彩,昙艳淡 响彻长空歌已歇 舞尽韶华花亦谢 红妆卸,素颜起 人...
    苏曼兮阅读 102评论 0 3