Kotlin开发Android笔记11:Kotlin中属性委托

 通常开发Android应用时,会自定义全局Application,当然在Kotlin中也是如此,我们也要对Application进行处理。

—-Applicaton单例化——
下面是最简单地方式 ,就如果Java中开发一样,获取Application实例:

class GlobalApp : Application() {
    companion object {
        private var instance: Application? = null
        fun instance() = instance!!
    }
    override fun onCreate() {
        super.onCreate()
        instance = this
    }
}

记得在AndroidManifest.xml中增加这个App:

<application
    android:allowBackup="true"
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name"
    android:theme="@style/AppTheme"
    android:name=".GlobalApp">
    ...
</application>

—Kotlin中属性委托——-

 Android中我们不能去控制很多类的构造函数。比如,不能初始化一个非null属性,它的值必需要在构造函数中去定义。
 所以我们需要一个可null的变量,和一个返回非null值的函数。我们知道我们应用中有一个Application实例,但是在它调用onCreate之前我们不能去操作任何事情,
 所以我们为了安全性,我们假设instance()函数将会总是返回一个非null的app实例。

 但是这个方案看起来有点不自然。我们需要定义个一个属性(已经有了getter和setter),然后通过一个函数来返回那个属性。
 我们有其他方法去达到相似的效果么?是的,我们可以通过委托这个属性的值给另外一个类。这个就是我们知道的委托属性。
  1. 委托属性

    需要一个属性具有一些相同的行为,使用lazy或者observable可以被很有趣地实现重用。而不是一次又一次地去声明那些相同的代码,Kotlin提供了一个委托属性到一个类的方法。这就是我们知道的委托属性。

    当我们使用属性的get或者set的时候,属性委托的getValue和setValue就会被调用。

属性委托的结构如下:

class Delegate<T> : ReadWriteProperty<Any?, T> {
    fun getValue(thisRef: Any?, property: KProperty<*>): T {
        return ...
    }

    fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
        ...
    }
}
这个T是委托属性的类型。getValue函数接收一个类的引用和一个属性的元数据。setValue函数又接收了一个被设置的值。
如果这个属性是不可修改(val),就会只有一个getValue函数。

下面展示属性委托是怎么设置的:

class Example {
    var p: String by Delegate()//它使用了by这个关键字来指定一个委托对象。
}

2.标准委托

在Kotlin的标准库中有一系列的标准委托。它们包括了大部分有用的委托,当然也可以创建我们自己的委托。

2.1.Lazy

 它包含一个lambda,当第一次执行getValue的时候这个lambda会被调用,所以这个属性可以被延迟初始化。之后的调用都只会返回同一个值。这是非常有趣的特性, 当我们在它们第一次真正调用之前不是必须需要它们的时候。我们可以节省内存,在这些属性真正需要前不进行初始化。
class App : Application() {
    val database: SQLiteOpenHelper by lazy {
        MyDatabaseHelper(applicationContext)
    }

    override fun onCreate() {
        super.onCreate()
        val db = database.writableDatabase
    }
}
在这个例子中,database并没有被真正初始化,直到第一次调用onCreate时。
在那之后,我们才确保applicationContext存在,并且已经准备好可以被使用了。lazy操作符是线程安全的。

如果你不担心多线程问题或者想提高更多的性能,你也可以使用
lazy(LazyThreadSafeMode.NONE){ ... }。

2.2.Observable

 这个委托会帮我们监测我们希望观察的属性的变化。当被观察属性的set方法被调用的时候,它就会自动执行我们指定的lambda表达式。
 所以一旦该属性被赋了新的值,我们就会接收到被委托的属性、旧值和新值。
class ViewModel(val db: MyDatabase) {
    var myProperty by Delegates.observable("") {
        d, old, new ->
        db.saveChanges(this, new)
    }
}
这个例子展示了,一些我们需要关心的ViewMode,每次值被修改了,就会保存它们到数据库。

2.3.Vetoable

这是一个特殊的observable,它让你决定是否这个值需要被保存。它可以被用于在真正保存之前进行一些条件判断。
var positiveNumber = Delegates.vetoable(0) {
    d, old, new ->
    new >= 0
}
上面这个委托只允许在新的值是正数的时候执行保存。在lambda中,最后一行表示返回值。你不需要使用return关键字(实质上不能被编译)。

2.4.Not Null

有时候我们需要在某些地方初始化这个属性,但是我们不能在构造函数中确定,或者我们不能在构造函数中做任何事情。
第二种情况在Android中很常见:在Activity、fragment、service、receivers……无论如何,一个非抽象的属性在构造函数执行完之前需要被赋值。
为了给这些属性赋值,我们无法让它一直等待到我们希望给它赋值的时候。我们至少有两种选择方案。

第一种就是使用可null类型并且赋值为null,直到我们有了真正想赋的值。但是我们就需要在每个地方不管是否是null都要去检查。
如果我们确定这个属性在任何我们使用的时候都不会是null,这可能会使得我们要编写一些必要的代码了。

第二种选择是使用notNull委托。它会含有一个可null的变量并会在我们设置这个属性的时候分配一个真实的值。
如果这个值在被获取之前没有被分配,它就会抛出一个异常。

这个在单例App这个例子中很有用:
class App : Application() {
    companion object {
        var instance: App by Delegates.notNull()
    }

    override fun onCreate() {
        super.onCreate()
        instance = this
    }
}

2.5.从Map中映射值

另外一种属性委托方式就是,属性的值会从一个map中获取value,属性的名字对应这个map中的key。
这个委托可以让我们做一些很强大的事情,因为我们可以很简单地从一个动态地map中创建一个对象实例。
如果我们import kotlin.properties.getValue,我们可以从构造函数映射到val属性来得到一个不可修改的map。
如果我们想去修改map和属性,我们也可以import kotlin.properties.setValue。类需要一个MutableMap作为构造函数的参数。

想象我们从一个Json中加载了一个配置类,然后分配它们的key和value到一个map中。我们可以仅仅通过传入一个map的构造函数来创建一个实例:
import kotlin.properties.getValue
class Configuration(map: Map<String, Any?>) {
    val width: Int by map
    val height: Int by map
    val dp: Int by map
    val deviceName: String by map
}
//作为一个参考,这里我展示下对于这个类怎么去创建一个必须要的map:
conf = Configuration(mapOf(
    "width" to 1080,
    "height" to 720,
    "dp" to 240,
    "deviceName" to "mydevice"
))

—创建一个自定义的委托——-

先来说说我们要实现什么,举个例子,我们创建一个notNull的委托,它只能被赋值一次,如果第二次赋值,它就会抛异常。

Kotlin库提供了几个接口,我们自己的委托必须要实现:ReadOnlyProperty和ReadWriteProperty。具体取决于我们被委托的对象是val还是var。

我们要做的第一件事就是创建一个类然后继承ReadWriteProperty:
private class NotNullSingleValueVar<T>() : ReadWriteProperty<Any?, T> {

        override fun getValue(thisRef: Any?, property: KProperty<*>): T {
            throw UnsupportedOperationException()
        }

        override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
        }
}
这个委托可以作用在任何非null的类型。它接收任何类型的引用,然后像getter和setter那样使用T。现在我们需要去实现这些函数。

Getter函数 如果已经被初始化,则会返回一个值,否则会抛异常。
Setter函数 如果仍然是null,则赋值,否则会抛异常。
private class NotNullSingleValueVar<T>() : ReadWriteProperty<Any?, T> {
    private var value: T? = null
    override fun getValue(thisRef: Any?, property: KProperty<*>): T {
        return value ?: throw IllegalStateException("${desc.name} " +
                "not initialized")
    }

    override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
        this.value = if (this.value == null) value
        else throw IllegalStateException("${desc.name} already initialized")
    }
}

现在你可以创建一个对象,然后添加函数使用你的委托:

object DelegatesExt {
    fun notNullSingleValue<T>():
            ReadWriteProperty<Any?, T> = NotNullSingleValueVar()
}

博客学习来自《《Kotlin for Android Developers》中文翻译》。
相关代码可以查看:https://github.com/antoniolg/Kotlin-for-Android-Developers
学习交流:https://github.com/ldm520/Android_Kotlin_Demo

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值