Android Gradle学习(六):NamedDomainObjectContainer详解

第一次看到 NamedDomainObjectContainer 的时候,让我迷惑了好一阵子,这到底是个什么玩意?现在让我们来揭开它神秘的面纱,看看它到底是什么,有什么作用。

1. NamedDomainObjectContainer的使用场景

前面在讲解 Gradle Extension 的时候,说到名为 android 的 Extension 是由 BaseExtension 这个类来实现的,里面对 buildTypes 是这样定义的:

private final NamedDomainObjectContainer<BuildType> buildTypes;

buildTypes 就是 NamedDomainObjectContainer 类型的,先来看看 buildTypes 在 Android 中是怎么使用的,下面这段代码应该都很熟悉了,它定义了 debug、relase 两种打包模式:

android {

    buildTypes {
        release {
            // 是否开启混淆
            minifyEnabled true
            // 开启ZipAlign优化
            zipAlignEnabled true
            //去掉不用资源
            shrinkResources true
            // 混淆文件位置
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            // 使用release签名
            signingConfig signingConfigs.hmiou
        }

        debug {
            signingConfig signingConfigs.hmiou
        }
    }
}

当我们新建一个项目的时候,默认会有 debug、release 这2个配置,那么问题来了:debug、release 能不能修改为其他名字?能不能增加其他的名字来配置,比方说我想增加一个测试包配置 test ?还有就是 release 里面都能配置哪些属性呢?

我来说下结果,如果不确定的,可以实际验证一下:

  1. debug、release 是可以修改成其他名字的,你可以替换成你喜欢的名字;
  2. 你可以增加任意不同名字的配置,比如增加一个开发版本的打包配置 dev ;
  3. 可配置的属性可参考接口:com.android.builder.model.BuildType ;

可以看到它是非常灵活的,你可以根据不同的场景定义不同的配置,每个不同的命名空间都会生成一个 BuildType 配置。要实现这样的功能,必须使用 NamedDomainObjectContainer 类型。

2. NamedDomainObjectContainer是什么

顾名思义就是命名领域对象容器,它的主要功能有:

  • 它能够通过DSL(在Gradle脚本中)创建指定 type 的对象实例;
  • 指定的 type 必须有一个 public 构造函数,且必须带有一个 String name 的参数,type 类型的领域对象必须有名为“name”的属性;
  • 它是一个实现了 SortedSet 接口的容器,所以所有领域对象的 name 属性值都必须是唯一的,在容器内部会用 name 属性来排序;

来看看官方文档里的说明:

A named domain object container is a specialisation of NamedDomainObjectSet that adds the ability to create instances of the element type.

Note that a container is an implementation of SortedSet, which means that the container is guaranteed to only contain elements with unique names within this container. Furthermore, items are ordered by their name.

3. 怎么创建NamedDomainObjectContainer

NamedDomainObjectContainer 需要通过 Project.container(...) API 来创建,其定义为:

<T> NamedDomainObjectContainer<T> container​(Class<T> type)
<T> NamedDomainObjectContainer<T> container​(Class<T> type, NamedDomainObjectFactory<T> factory)
<T> NamedDomainObjectContainer<T> container​(java.lang.Class<T> type, Closure factoryClosure

来看个具体的实例:

//这是领域对象类型定义
class TestDomainObj {
    
    //必须定义一个 name 属性,并且这个属性值初始化以后不要修改
    String name

    String msg

    //构造函数必须有一个 name 参数
    public TestDomainObj(String name) {
        this.name = name
    }

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

    String toString() {
        return "name = ${name}, msg = ${msg}"
    }
}

//创建一个扩展
class TestExtension {

    //定义一个 NamedDomainObjectContainer 属性
    NamedDomainObjectContainer<TestDomainObj> testDomains

    public TestExtension(Project project) {
        //通过 project.container(...) 方法创建 NamedDomainObjectContainer 
        NamedDomainObjectContainer<TestDomainObj> domainObjs = project.container(TestDomainObj)
        testDomains = domainObjs
    }

    //让其支持 Gradle DSL 语法
    void testDomain(Action<NamedDomainObjectContainer<TestDomainObj>> action) {
        action.execute(testDomains)
    }

    void test() {
        //遍历命名领域对象容器,打印出所有的领域对象值
        testDomains.all { data ->
            println data        
        }
    }
}

//创建一个名为 test 的 Extension
def testExt = getExtensions().create("test", TestExtension, project)

test {
    testDomain {
        domain2 {
            msg "This is domain2"
        }
        domain1 {
            msg "This is domain1"
        }
        domain3 {
            msg "This is domain3"
        }
    }   
}

task myTask << {
    testExt.test()
}

运行结果如下:

name = domain1, msg = This is domain1
name = domain2, msg = This is domain2
name = domain3, msg = This is domain3

4. 查找和遍历

NamedDomainObjectContainer 既然是一个容器类,与之相应的必然会有查找容器里的元素和遍历容器的方法:

//遍历
void all(Closure action)
//查找
<T> T getByName(String name)
//查找
<T> T findByName(String name)

还是接着前面的例子:

//通过名字查找
TestDomainObj testData = testDomains.getByName("domain2")
println "getByName: ${testData}"

//遍历命名领域对象容器,打印出所有的领域对象值
testDomains.all { data ->
    println data        
}                   

需要注意的是,Gradle 中有很多容器类的迭代遍历方法有 each(Closure action)、all(Closure action),但是一般我们都会用 all(...) 来进行容器的迭代。all(...) 迭代方法的特别之处是,不管是容器内已存在的元素,还是后续任何时刻加进去的元素,都会进行遍历。

系列文章

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

推荐阅读更多精彩内容