在自定义Gradle 插件的时候,总是期望外面能够传递一些指定的参数给插件,这样就可以按照需要进行动态配置了。这样插件也就更加通用,灵活并且适用性更广,所以了解扩展属性的创建也是非常有必要的。
而要实现参数的动态配置,就需要插件对外提供 扩展属性
,即为 Project 创建 Extension。而 Extension (扩展属性)的创建,都是通过 ExtensionContainer 类的相关 Api 来创建的。下面就来详细的了解一下,扩展属性的创建。
一、创建扩展属性的常用方式
创建一个扩展属性的步骤如下:
- 创建一个扩展类:用于定义配置项,即指明用户可以配置的字段和调用的方法
- 通过 ExtensionContainer 的 Api 为目标对象(即Project对象)创建扩展:这样在使用插件时,就可以在build.gradle文件中配置扩展属性了。
通过上面的步骤,就可以创建出属于自己插件的扩展属性。
下面就通过一个简单的案例来说明一下:
1、简单的扩展属性
-
(1)创建一个扩展类:
open class BigImageConfig { /** * 是否允许检测 */ var checkEnabled: Boolean = true /** * 大图大小 */ var maxSize: Long = 1024 * 1024 }
这里需要注意的一点是,扩展类不能是 final 类型的,即必须可以被继承,否则无法创建扩展属性。
-
(2)通过 ExtensionContainer 创建扩展
class CheckBigImagePlugin : Plugin<Project> { override fun apply(project: Project) { // 创建配置项 project.extensions.create("bigImageConfig", // 参数1 BigImageConfig::class.java)// 参数2 } }
通过 ExtensionContainer 的
create
方法,创建扩展属性,参数说明:- 参数1: 扩展属性名,可以随意指定,建议根 扩展类保持一致,识别方便
- 参数2: 扩展类的类类型
如果 扩展类 BigImageConfig 的默认构造函数是有参数的,这就需要给构造函数传递所需要的参数,可以使用 ExtensionContainer 的create 重载方法,传递参数,如:
open class BigImageConfig( /** * 是否允许检测 */ var checkEnabled: Boolean = true ) { /** * 大图大小 */ var maxSize: Long = 1024 * 1024 } // 插件 class CheckBigImagePlugin : Plugin<Project> { override fun apply(project: Project) { // 创建配置项 project.extensions.create("bigImageConfig", // 参数1 BigImageConfig::class.java,// 参数2 true)// 传递给 BigImageConfig 构造函数的参数 } }
这样就可以传递参数给扩展类了。
-
(3)扩展属性的使用
通过上面的步骤,就可以创建一个名 bi gImageConfig 的扩展,在使用这个插件的时候,就可以在 build.gradle 文件中增加这个配置,如:
bigImageConfig { checkEnabled = true maxSize = 1024 }
上面能配置的信息,都是我们在 扩展类 中定义的字段,所以要想增加别的配置信息,就在 扩展类 中继续增加字段就可以了。
除了通过 等号赋值外,还可以直接用键值对的方式,即去掉等号,如:
bigImageConfig { checkEnabled true maxSize 1024 }
不管是使用 键值对的方式 还是用 等号的方式 赋值,最终调用的都是属性的 setter 方法。
-
(4)扩展属性的获取
经过第三步,我们配置了自己的扩展属性,那么在插件中,如何拿到这些扩展属性呢?这里可以通过 Project 的 property() 方法获取到,如:
class CheckBigImagePlugin : Plugin<Project> { // 配置信息 private lateinit var imageConfig: BigImageConfig override fun apply(project: Project) { // 创建配置项 project.extensions.create("bigImageConfig", BigImageConfig::class.java) // 获取配置信息 imageConfig = project.property("bigImageConfig") as BigImageConfig } }
正如上面的代码所示,通过 Project 的 property() 方法,传入 扩展属性名(即在创建扩展属性的时候,传入的名称),就可以拿到指定的扩展属性。
当然,如果插件用的是 Groovy 语言开发,还可以直接通过 project.扩展属性名 的方法,获取到扩展属性,如:
// 获取配置信息 def imageConfig = project.bigImageConfig
经过上面的步骤,我们就知道了如下三件事:
- 创建扩展属性
- 使用扩展属性
- 获取扩展属性
需要注意的点:
- 扩展类 必须是可以被继承的,不能是
final
类型
2、创建扩展属性的其它方式
上面通过案例,我们知道,通过 ExtensionContainer 的 create 方法,可以为插件创建 扩展属性,那么 ExtensionContainer 中,还有没有其它的方式可以用于创建 扩展属性呢? 当然有,我们可以看看 ExtensionContainer 的 主要Api:
public interface ExtensionContainer {
/**
* 添加一个扩展属性
* @param name 扩展属性名
* @param extension 扩展类 对象
* @throws IllegalArgumentException When an extension with the given name already exists
*/
void add(String name, Object extension);
/**
* 创建一个扩展属性
* @param name 扩展属性名
* @param type 扩展类类型
* @param constructionArguments 扩展类的构造函数所需要的参数
* @return 返回创建的扩展对象
*/
<T> T create(String name, Class<T> type, Object... constructionArguments);
/**
* 通过名称,查找指定的扩展属性,如果没有找到,抛异常
*/
Object getByName(String name) throws UnknownDomainObjectException;
/**
* 通过名称,查找指定的扩展属性,如果没有找到,返回null,不会抛异常
*/
@Nullable
Object findByName(String name);
}
上面截取了 ExtensionContainer 类的一部分Api,从上面的类声明中也可以看出,除了通过 create 方法创建扩展之外,还可以通过 add 方法创建扩展属性。
如:
class CheckBigImagePlugin : Plugin<Project> {
override fun apply(project: Project) {
// 创建配置项
project.extensions.add("bigImageConfig", BigImageConfig::class.java)
}
}
ExtensionContainer 的实现类 DefaultConvention 中,可以看到 add 方法的具体实现:
@Override
public void add(String name, Object extension) {
if (extension instanceof Class) {
create(name, (Class<?>) extension);
} else {
addWithDefaultPublicType(name, extension);
}
}
可以看到,如果传递的是一个 扩展对象,那么直接添加,如果传递的是一个 Class 类类型,则通过 create 方法进行创建
3、总结
扩展属性创建方式总结:
- 通过 ExtensionContainer 的 create 方法进行创建
- 通过 ExtensionContainer 的 add 方法进行创建
两种方式都可以,区别在于:
-
相同点:
-
都可以通过键值对的方式进行配置,也可以使用等号的方式进行配置,最终调用的都是 属性的 setter 方法。
-
都会抛异常:当需要创建的扩展属性已经存在的时候,即扩展属性重复,则都会抛出异常。
-
-
不同点:
- create 方法会返回创建 扩展对象,而 add 方法不会;
二、扩展属性嵌套
扩展属性嵌套,就是在扩展属性里面,还有扩展属性,就相当于实体类内部,引用了其它的实体类一样,如:
android {
defaultConfig {
vectorDrawables.useSupportLibrary = true
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
viewBinding {
enabled = true
}
dataBinding {
enabled = true
}
}
上面是Android 工程的 build.gradle 文件,通常都能见到的配置,其中 android 就是最外层的配置,defaultConfig ,buildTypes 和 viewBinding 等,就是 嵌套 在 android 下的扩展属性,这还是一层嵌套,还有多层嵌套,如 release 就是二层嵌套了。
像上面那样的嵌套扩展属性,也有多种方式可以实现,总结来说有如下方式:
- 在外层对象的构造函数中,通过 ExtensionContainer 的 api 创建扩展属性
- 在外层类中,定义一个方法,名字为配置项的名称,参数为 Closure ,泛型参数为嵌套的 扩展类
- 在外层类中,定义一个方法,名字为配置项的名称,参数为 Action, 泛型参数为嵌套的 扩展类
详细使用如下
1、通过 ExtensionContainer 的 api 创建扩展属性
在创建外层扩展属性的时候,通过 ExtensionContainer 的 api 同步创建 嵌套扩展属性,即在外层扩展类的构造函数中,创建扩展属性,如:
扩展类:
// 外层扩展类
class User {
String name = ""
int age = 10
User() {
// 在 创建 User 对象的时候,就创建 嵌套扩展 属性
extensions.create("friendConfig", // 嵌套扩展属性的名称
Friend.class, // 嵌套扩展属性的类类型
"朋友是张三")// 嵌套扩展类的构造参数
}
}
// 嵌套的扩展类
class Friend {
String friendName = "ss"
Friend(String friendName) {
this.friendName = friendName
}
}
在插件中,创建 扩展属性:
project.extensions.create("userConfig", User.class)
配置代码:
userConfig {
name "张三"
age 20
friendConfig {
friendName = "Tom"
}
}
测试代码:
task test2 {
doLast {
println "${project.userConfig.name}" // 可以正常拿到配置的用户名
println "${project.userConfig.friendConfig.friendName}" // 也可以拿到朋友的名称
}
}
打印的信息:
张三 Tom
注意:通过 ExtensionContainer 的 api 创建的扩展属性,可以省略 属性的等号,如:
userConfig {
...
friendConfig {
friendName "Tom" // 省略等号,也是可以的,这就更像配置了,虽然省略了等号,但调用的还是 属性的 setter 方法
}
}
2、在外层类中,定义一个方法,名字为配置项的名称,参数为 Closure ,泛型参数为嵌套的 扩展类
这种方式创建扩展属性的步骤:
- 创建一个嵌套扩展类的对象
- 定义一个配置方法,方法名为:配置项的名称,可以随意指定,参数为闭包,泛型参数为嵌套扩展类
2-1、创建对象
-
直接在定义属性的时候,new 出对象
class User { String name = "" int age = 10 // 创建一个嵌套的扩展类对象 Friend friend = new Friend() }
-
通过Gradle 的 ObjectFactory 类,创建扩展类对象,如:
class User { String name = "jj" int age = 10 Friend friend User(Project project) { // 通过 ObjectFactory 创建 扩展类对象 friend = project.objects.newInstance(Friend) } } class Friend { String friendName = "ss" // 构造函数 @Inject Friend() { } }
值得注意的是,通过 ObjectFactory 创建 扩展类对象的时候,需要给扩展类的构造函数,增加 @Inject 注解,否则会报异常。
2-1、创建一个配置方法
创建一个配置方法,方法名就是在配置文件中配置的扩展属性名,可以根据自己的喜好定义,参数为 Closure ,闭包的泛型参数为 扩展类,如:
class User {
String name = ""
int age = 10
// 创建一个嵌套的扩展类对象
Friend friend = new Friend()
// 2. 定义一个配置方法,方法名按照自己喜好定义,参数是一个闭包对象,泛型类型是扩展类类型
void friendConfig(Closure<Friend> closure) {
// 通过 ConfigureUtil 类,为对象 创建 配置
org.gradle.util.ConfigureUtil.configure(closure,
friend)// 扩展对象
}
}
方法名:friendConfig,在配置的时候,使用的也是这个名称,
参数:Closure closure
实现:直接调用 gradle 的 ConfigureUtil 的 configure 方法执行 闭包参数,并传递创建的扩展对象。
配置代码如下:
userConfig {
name "张三"
friendConfig {
friendName = "Tom"
}
}
注意:
-
为了使用的方便,最好是让创建的扩展对象的名称与配置方法的名称保持一致,这样配置和获取都能保持一致,如:
class User { // 扩展对象 Friend friendConfig = new Friend() // 配置方法 void friendConfig(Closure<Friend> closure) { org.gradle.util.ConfigureUtil.configure(closure, friendConfig) } }
-
这种方式创建的扩展属性,配置的时候,等号不能省略,即:
userConfig { friendConfig { friendName "Tom" // 这种方式不能使用,会报错 } }
3、在外层类中,定义一个方法,名字为配置项的名称,参数为 Action, 泛型参数为嵌套的 扩展类
这种方式跟第二种方式类似,配置方法的实现上有一些差异,具体如下:
class User {
String name = "jj"
int age = 10
User(Project project) {
// 创建扩展对象
friend = project.objects.newInstance(Friend)
}
Friend friend
// 配置方法
void friendConfig(Action<Friend> action) {
action.execute(friend) // 直接执行 action 参数的 execute 方法,并传入扩展对象
}
}
配置代码:
userConfig {
name "张三"
friendConfig {
friendName = "Tom"
}
}
通过把配置方法的参数替换为 Action 对象,并执行action的exectute方法,其它不变。配置的时候,等号也是不能省略
4、总结
- 通过 ExtensionContainer 的 api 的方式,创建的嵌套扩展属性,在配置的时候,可以省略等号,而通过定义配置方法的方式创建的扩展属性,等号不能省略
- 通过 ObjectFactory 方法创建扩展对象的时候,扩展类的构造函数必须使用 @Inject 注解标注
- 扩展属性名和配置方法名,最好保持一致,方便使用
三、容器类型的扩展属性
在进行 Android 配置时,我们一定用过 buildTypes
的配置,类似:
buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
debug {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
这种类型可以用于在代码块中创建新的指定类型的对象。
先来看一下源码:
public void buildTypes(Action<? super NamedDomainObjectContainer<BuildType>> action) {
this.checkWritability();
action.execute(this.buildTypes);
}
它传入的是一个 BuildType
类型列表的闭包代码。
NamedDomainObjectContainer
是一个容器,追根溯源它是继承自 Collection<T>
。我们这里叫它命名对象容器,可以用于buildScript 中创建对象,且创建的对象必须要有 name 属性作为容器内元素的标识。
怎么来得到这样的容器对象呢?我们来看一下 Project
的 container
方法:
<T> NamedDomainObjectContainer<T> container(Class<T> var1);
<T> NamedDomainObjectContainer<T> container(Class<T> var1, NamedDomainObjectFactory<T> var2);
<T> NamedDomainObjectContainer<T> container(Class<T> var1, Closure var2);
下面通过实例来介绍一下。
1、实例介绍
其实,创建一个容器类型的扩展属性,跟创建普通的扩展属性类似,也是分为两步走:
- 定义容器扩展类
- 创建扩展对象
- 添加扩展对象到 extension 中
具体如下所示:
1-1、定义 容器 扩展类
首先创建一个容器类型的扩展类 PhoneConfig ,上面说了,这个类必须有个 name
属性:
class PhoneConfig {
String name
String color
float price
PhoneConfig(String name) {
this.name = name
}
}
注意,name 字段必须要有。
1-2、创建容器扩展对象
通过 Project 的 container 方法,创建容器扩展对象,如:
// 通过 project 的 container 方法,创建容器对象
NamedDomainObjectContainer<PhoneConfig> phoneConfig = project.container(PhoneConfig.class)
1-3、创建容器扩展属性
即把创建的容器扩展对象,添加到extension 中,如:
project.extensions.add("phoneConfig", phoneConfig)
1-4、完整代码
完整的代码如下:
class User {
String name = ""
int age = 10
User(Project project) {
friend = project.objects.newInstance(Friend)
// 创建容器对象
NamedDomainObjectContainer<PhoneConfig> phoneConfig = project.container(PhoneConfig.class)
// 把容器对象添加到 extensions中
project.extensions.add("phoneConfig", phoneConfig)
}
Friend friend
void friendConfig(Action<Friend> action) {
action.execute(friend)
}
}
配置代码:
userConfig {
name "张三"
phoneConfig {
iphone {// 名字
color = "红色" // 等号不能省略
price = 10000
}
xiaomi {
color = "黄色"
price = 5000
}
}
}
获取配置信息:
task test2 {
doLast {
println "${project.userConfig.name}"
// 直接通过 project 对象去拿 phoneConfig
project.phoneConfig.forEach { phone ->
println "name=${phone.name},color=${phone.color},price=${phone.price}"
}
}
}
打印信息如下:
张三 name=iphone,color=红色,price=10000.0 name=xiaomi,color=黄色,price=5000.0
我们注意到,上面的容器配置,都要写上等号,否则就会报错,那有没有办法,不写等号也可以呢?当然有,给 扩展类 定义一个跟属性同名的 赋值方法就可以了,如:
class PhoneConfig {
String name
String color
float price
PhoneConfig(String name) {
this.name = name
}
// 定义一个跟 属性同名的配置方法
def color(String color) {
this.color = color
}
}
配置代码:
userConfig {
name "张三"
phoneConfig {
iphone {// 名字,这个就是容器里面的item的名称,
color "红色"
price = 10000
}
xiaomi {
color "黄色"
price = 5000
}
}
}
定义了跟属性同名的赋值方法之后,就可以把等号省略了,如 color 属性,而price 属性没有定义同名的 赋值方法,则等号不能省略。
这里创建的容器属性,没有保存在外层扩展类中,如果想在外层类中也能访问容器类,则需要在外层类中保存容器类对象,如:
class User {
NamedDomainObjectContainer<PhoneConfig> phoneConfig
User(Project project) {
// 创建容器对象
phoneConfig = project.container(PhoneConfig.class)
// 把容器对象添加到 extensions中
project.extensions.add("phoneConfig", phoneConfig)
}
}
访问:
task test2 {
doLast {
println "${project.userConfig.name}"
// 通过userConfig拿到他的属性 phoneConfig对象
project.userConfig.phoneConfig.forEach { phone ->
println "name=${phone.name},color=${phone.color},price=${phone.price}"
}
}
}
到此,扩展属性相关的东西就介绍完了。
四、总结:
1、 扩展属性的创建方式
- 通过 ExtensionContainer 的 create 或者 add 方法 创建扩展属性
2、扩展属性嵌套的创建方式
- 通过 ExtensionContainer 创建
- 通过定义配置方法,创建嵌套属性
3、容器扩展的创建方式
- 通过 Project 的 container 方法,创建 容器对象,并把创建的扩展对象添加到 ExtensionContainer 容器中。