Kotlin委托属性在Android开发中的使用

Android面试技术要点汇总
2021-05-17 14:29 · 阅读时长12分钟
小课

委托属性是kotlin一个非常好用的特性,它可以让我们通过类来封装控制属性的set和get方法,通过这种方式来复用代码,能让代码更加简洁,下面看看它在Android开发中的应用。

一、基础用法

案例:如果要实现在给String类型的属性赋值时,去掉字符串两边的空格,最常见的实现方式是这样的。

class Example {
    var param: String = ""
        set(value) {
            field = value.trim()
        }
    ...
}

这样操作没什么问题,但是如果有很多个String属性,都需要这个功能,那么代码就会比较重复,而且看上去会很乱。如果是使用委托属性,我们需要先定义一个类,实现ReadWriteProperty<T, V>接口,用来处理String属性的set和get方法,T表示限定声明属性所属类的类型,V表示限定属性自身的类型,这里不限定是某种类的String属性,所以实现接口ReadWriteProperty<Any?, String>,如果限定XX类的String属性,改为实现ReadWriteProperty<XX, String>即可。

1class TrimDelegate : ReadWriteProperty<Any?, String> {
2
3    private var trimmedValue: String = ""
4
5    override fun getValue(
6        thisRef: Any?,
7        property: KProperty<*>
8    ): String {
9        return trimmedValue
10    }
11    
12    override fun setValue(
13        thisRef: Any?,
14        property: KProperty<*>, value: String
15    ) {
16        trimmedValue = value.trim()
17    }
18}

在需要实现该功能的属性上使用by TrimDelegate()即可,如下

class Example {
    //使用委托属性此处简洁很多
    var param1 by TrimDelegate()
    var param2 by TrimDelegate()
}

如果有很多属性都需要该功能,使用委托属性可以非常明显的简化代码,下面是一个完整示例,可在线运行查看效果。

加载中...
二、使用委托属性简化Fragment传参

在Android开发中,在Fragment中一般使用arguments来进行传参,在创建Frament时,把参数设置在arguments中。

val demoFragment = DemoFragment().apply {
    arguments = Bundle().apply {
        putString("name", "Demo from arguments")
    }
}

然后在Fragment的onCreate中通过arguments来读取参数,然后保存至成员属性中。

private var name: String? = null

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    name = arguments?.getString("name")
}

下面看看如何使用委托属性来简化,为了统一处理Bundle各种类型的参数,先给Bundle添加一个扩展方法。

1fun <T> Bundle.put(key: String, value: T) {
2    when (value) {
3        is Boolean -> putBoolean(key, value)
4        is String -> putString(key, value)
5        is Int -> putInt(key, value)
6        is Short -> putShort(key, value)
7        is Long -> putLong(key, value)
8        is Byte -> putByte(key, value)
9        is ByteArray -> putByteArray(key, value)
10        is Char -> putChar(key, value)
11        is CharArray -> putCharArray(key, value)
12        is CharSequence -> putCharSequence(key, value)
13        is Float -> putFloat(key, value)
14        is Bundle -> putBundle(key, value)
15        is Parcelable -> putParcelable(key, value)
16        is Serializable -> putSerializable(key, value)
17        else -> throw IllegalStateException("Type of property $key is not supported")
18    }
19}

接下来是实现委托类,因为是针对Fragment类的所有类型属性,所以实现接口ReadWriteProperty<Fragment, T?>,在getValue方法中,我们根据属性的名字从Fragment的arguments中读取数据,在setValue时把数据保存到Fragment的arguments中。

1class FragmentArgumentDelegate<: Any?> :
2    ReadWriteProperty<Fragment, T?> {
3
4    @Suppress("UNCHECKED_CAST")
5    override fun getValue(
6        thisRef: Fragment,
7        property: KProperty<*>
8    ): T? {
9        val key = property.name
10        return thisRef.arguments?.get(key) as? T
11    }
12
13    override fun setValue(
14        thisRef: Fragment,
15        property: KProperty<*>, value: T?
16    ) {
17        val args = thisRef.arguments
18            ?: Bundle().also(thisRef::setArguments)
19        val key = property.name
20        value?.let { args.put(key, it) } ?: args.remove(key)
21    }
22}

在声明属性和给属性赋值时这样使用,就会从arguments中获取和保存至arguments中。

//声明属性
private var userId: Int? by FragmentArgumentDelegate()
private var name: String? by FragmentArgumentDelegate()
//给属性赋值
DemoFragment().apply {
    this.userId = userId
    this.name = name
}

为了更加简化声明属性的代码,我们可以定义一个方法argument()来替换FragmentArgumentDelegate。

fun <: Any> argument() = FragmentArgumentDelegate<T>()

//声明属性的时候就可以这样
private var userId: Int? by argument()
private var name: String? by argument()
三、使用委托属性简化SharedPreferences

同样的在使用SharedPreferences时,我们也会从SharedPreferences中获取值,在更新值的时候同步到SharedPreferences中,代码会是这样的。

private val preferences = context.getSharedPreferences("settings", Context.MODE_PRIVATE)

var uuid: String?
    get() {
        return preferences.getString("uuid", null)
    }
    set(value) {
        preferences.edit().putString("uuid", value).apply()
    }

如果属性很多,这样的代码就会很重复,我们一样可以通过委托属性来优化这段代码,之前我们都是定义一个类来代理属性,这里之前有些不一样,这个地方会依赖具体的preferences,我们有两种方式可以处理,一种是将preferences作为参数传入到委托类中,第二种就是使用扩展方法。

1fun SharedPreferences.string(
2    defaultValue: String? = null,
3    key: (KProperty<*>) -> String = KProperty<*>::name
4): ReadWriteProperty<Any, String?> =
5    object : ReadWriteProperty<Any, String?> {
6        override fun getValue(
7            thisRef: Any,
8            property: KProperty<*>
9        ) = getString(key(property), defaultValue)
10
11        override fun setValue(
12            thisRef: Any,
13            property: KProperty<*>,
14            value: String?
15        ) = edit().putString(key(property), value).apply()
16    }

在声明属性的地方改成这样即可。

private val preferences = context.getSharedPreferences("settings", Context.MODE_PRIVATE)

var uuid by preferences.string()

这样是不是比之前更加简洁了?下面是上面示例的完整代码。

加载中...
kotlinDelegated properties委托属性SharedPreferencesFragment arguments