前言
我們很快要去實(shí)現(xiàn)一個(gè)數(shù)據(jù)庫(kù),如果我們想要保持我們代碼的簡(jiǎn)潔性和層次性(而
不是把所有代碼添加到Activity中),我們就要需要有一個(gè)更簡(jiǎn)單的訪問(wèn)application
context的方式眶拉。
Applicaton單例化
按照我們?cè)贘ava中一樣創(chuàng)建一個(gè)單例最簡(jiǎn)單的方式:
class App : Application() {
companion object {
private var instance: Application? = null
fun instance() = instance!!
}
override fun onCreate() {
super.onCreate()
instance = this
}
}
為了這個(gè)Application實(shí)例被使用,要記得在 AndroidManifest.xml 中增加這
個(gè) App :
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme"
android:name=".ui.App">
...
</application>
Android有一個(gè)問(wèn)題憔儿,就是我們不能去控制很多類(lèi)的構(gòu)造函數(shù)。比如放可,我們不能初
始化一個(gè)非null屬性谒臼,因?yàn)樗闹敌枰跇?gòu)造函數(shù)中去定義。所以我們需要一個(gè)可
null的變量耀里,和一個(gè)返回非null值的函數(shù)蜈缤。我們知道我們一直都有一個(gè) App 實(shí)例,
但是在它調(diào)用 onCreate 之前我們不能去操作任何事情冯挎,所以我們?yōu)榱税踩缘赘纾?br>
們假設(shè) instance() 函數(shù)將會(huì)總是返回一個(gè)非null的 app 實(shí)例。
但是這個(gè)方案看起來(lái)有點(diǎn)不自然房官。我們需要定義個(gè)一個(gè)屬性(已經(jīng)有了getter和
setter)趾徽,然后通過(guò)一個(gè)函數(shù)來(lái)返回那個(gè)屬性。我們有其他方法去達(dá)到相似的效果
么翰守?是的孵奶,我們可以通過(guò)委托這個(gè)屬性的值給另外一個(gè)類(lèi)。這個(gè)就是我們知道的 委托屬性 蜡峰。
委托屬性
我們可能需要一個(gè)屬性具有一些相同的行為了袁,使用 lazy 或者 observable 可以
被很有趣地實(shí)現(xiàn)重用朗恳。而不是一次又一次地去聲明那些相同的代碼,Kotlin提供了
一個(gè)委托屬性到一個(gè)類(lèi)的方法载绿。這就是我們知道的 委托屬性 粥诫。
當(dāng)我們使用屬性的 get 或者 set 的時(shí)候,屬性委托
的 getValue 和 setValue 就會(huì)被調(diào)用崭庸。
屬性委托的結(jié)構(gòu)如下:
class Delegate<T> : ReadWriteProperty<Any?, T> {
fun getValue(thisRef: Any?, property: KProperty<*>): T {
return ...
}
fun setValue(thisRef: Any?, property: KProperty<*>, value: T){
...
}
}
這個(gè)T是委托屬性的類(lèi)型怀浆。 getValue 函數(shù)接收一個(gè)類(lèi)的引用和一個(gè)屬性的元數(shù)
據(jù)。 setValue 函數(shù)又接收了一個(gè)被設(shè)置的值冀自。如果這個(gè)屬性是不可修改
(val)揉稚,就會(huì)只有一個(gè) getValue 函數(shù)。
下面展示屬性委托是怎么設(shè)置的:
class Example {
var p: String by Delegate()
}
它使用了 by 這個(gè)關(guān)鍵字來(lái)指定一個(gè)委托對(duì)象熬粗。
標(biāo)準(zhǔn)委托
在Kotlin的標(biāo)準(zhǔn)庫(kù)中有一系列的標(biāo)準(zhǔn)委托搀玖。它們包括了大部分有用的委托,但是我
們也可以創(chuàng)建我們自己的委托驻呐。
Lazy
它包含一個(gè)lambda灌诅,當(dāng)?shù)谝淮螆?zhí)行 getValue 的時(shí)候這個(gè)lambda會(huì)被調(diào)用,所以
這個(gè)屬性可以被延遲初始化含末。之后的調(diào)用都只會(huì)返回同一個(gè)值猜拾。這是非常有趣的特
性, 當(dāng)我們?cè)谒鼈兊谝淮握嬲{(diào)用之前不是必須需要它們的時(shí)候佣盒。我們可以節(jié)省內(nèi)
存挎袜,在這些屬性真正需要前不進(jìn)行初始化。
class App : Application() {
val database: SQLiteOpenHelper by lazy {
MyDatabaseHelper(applicationContext)
}
override fun onCreate() {
super.onCreate()
val db = database.writableDatabase
}
}
在這個(gè)例子中肥惭,database并沒(méi)有被真正初始化盯仪,直到第一次調(diào)用 onCreate 時(shí)。
在那之后蜜葱,我們才確保applicationContext存在全景,并且已經(jīng)準(zhǔn)備好可以被使用
了。 lazy 操作符是線程安全的牵囤。
如果你不擔(dān)心多線程問(wèn)題或者想提高更多的性能爸黄,你也可以使
用 lazy(LazyThreadSafeMode.NONE){ ... } 。
Observable
這個(gè)委托會(huì)幫我們監(jiān)測(cè)我們希望觀察的屬性的變化揭鳞。當(dāng)被觀察屬性的 set 方法被
調(diào)用的時(shí)候炕贵,它就會(huì)自動(dòng)執(zhí)行我們指定的lambda表達(dá)式。所以一旦該屬性被賦了新
的值野崇,我們就會(huì)接收到被委托的屬性鲁驶、舊值和新值。
class ViewModel(val db: MyDatabase) {
var myProperty by Delegates.observable("") {d, old, new ->db.saveChanges(this,new)}
}
這個(gè)例子展示了舞骆,一些我們需要關(guān)心的ViewMode钥弯,每次值被修改了径荔,就會(huì)保存它
們到數(shù)據(jù)庫(kù)。
Vetoable
這是一個(gè)特殊的 observable 脆霎,它讓你決定是否這個(gè)值需要被保存总处。它可以被用
于在真正保存之前進(jìn)行一些條件判斷。
var positiveNumber = Delegates.vetoable(0) {d, old, new ->new >= 0}
上面這個(gè)委托只允許在新的值是正數(shù)的時(shí)候執(zhí)行保存睛蛛。在lambda中鹦马,最后一行表示
返回值。你不需要使用return關(guān)鍵字(實(shí)質(zhì)上不能被編譯)忆肾。
Not Null
有時(shí)候我們需要在某些地方初始化這個(gè)屬性荸频,但是我們不能在構(gòu)造函數(shù)中確定,或
者我們不能在構(gòu)造函數(shù)中做任何事情客冈。第二種情況在Android中很常見(jiàn):在
Activity旭从、fragment、service场仲、receivers……無(wú)論如何和悦,一個(gè)非抽象的屬性在構(gòu)造函
數(shù)執(zhí)行完之前需要被賦值。為了給這些屬性賦值渠缕,我們無(wú)法讓它一直等待到我們希
望給它賦值的時(shí)候鸽素。我們至少有兩種選擇方案。
第一種就是使用可null類(lèi)型并且賦值為null亦鳞,直到我們有了真正想賦的值馍忽。但是我們
就需要在每個(gè)地方不管是否是null都要去檢查。如果我們確定這個(gè)屬性在任何我們
使用的時(shí)候都不會(huì)是null燕差,這可能會(huì)使得我們要編寫(xiě)一些必要的代碼了遭笋。
第二種選擇是使用 notNull 委托。它會(huì)含有一個(gè)可null的變量并會(huì)在我們?cè)O(shè)置這個(gè)
屬性的時(shí)候分配一個(gè)真實(shí)的值谁不。如果這個(gè)值在被獲取之前沒(méi)有被分配,它就會(huì)拋出
一個(gè)異常徽诲。
這個(gè)在單例App這個(gè)例子中很有用:
class App : Application() {
companion object {
var instance: App by Delegates.notNull()
}
override fun onCreate() {
super.onCreate()
instance = this
}
}
從Map中映射值
另外一種屬性委托方式就是刹帕,屬性的值會(huì)從一個(gè)map中獲取value,屬性的名字對(duì)應(yīng)
這個(gè)map中的key谎替。這個(gè)委托可以讓我們做一些很強(qiáng)大的事情偷溺,因?yàn)槲覀兛梢院芎?jiǎn)
單地從一個(gè)動(dòng)態(tài)地map中創(chuàng)建一個(gè)對(duì)象實(shí)例。如果我們import
kotlin.properties.getValue 钱贯,我們可以從構(gòu)造函數(shù)映射到 val 屬性來(lái)得到
一個(gè)不可修改的map挫掏。如果我們想去修改map和屬性,我們也可以import
kotlin.properties.setValue 秩命。類(lèi)需要一個(gè) MutableMap 作為構(gòu)造函數(shù)的參
數(shù)尉共。
想象我們從一個(gè)Json中加載了一個(gè)配置類(lèi)褒傅,然后分配它們的key和value到一個(gè)map
中。我們可以僅僅通過(guò)傳入一個(gè)map的構(gòu)造函數(shù)來(lái)創(chuàng)建一個(gè)實(shí)例:
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
}
作為一個(gè)參考袄友,這里我展示下對(duì)于這個(gè)類(lèi)怎么去創(chuàng)建一個(gè)必須要的map:
conf = Configuration(mapOf(
"width" to 1080,
"height" to 720,
"dp" to 240,
"deviceName" to "mydevice"
))
怎么去創(chuàng)建一個(gè)自定義的委托
先來(lái)說(shuō)說(shuō)我們要實(shí)現(xiàn)什么殿托,舉個(gè)例子,我們創(chuàng)建一個(gè) notNull 的委托剧蚣,它只能被
賦值一次支竹,如果第二次賦值,它就會(huì)拋異常鸠按。
Kotlin庫(kù)提供了幾個(gè)接口礼搁,我們自己的委托必須要實(shí)
現(xiàn): ReadOnlyProperty 和 ReadWriteProperty 。具體取決于我們被委托的對(duì)
象是 val 還是 var 目尖。
我們要做的第一件事就是創(chuàng)建一個(gè)類(lèi)然后繼承 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) {
}
}
這個(gè)委托可以作用在任何非null的類(lèi)型馒吴。它接收任何類(lèi)型的引用,然后像getter和
setter那樣使用T”把悖現(xiàn)在我們需要去實(shí)現(xiàn)這些函數(shù)募书。
- Getter函數(shù) 如果已經(jīng)被初始化,則會(huì)返回一個(gè)值测蹲,否則會(huì)拋異常莹捡。
- Setter函數(shù) 如果仍然是null,則賦值扣甲,否則會(huì)拋異常篮赢。
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 i
nitialized")
}
}
現(xiàn)在你可以創(chuàng)建一個(gè)對(duì)象,然后添加函數(shù)使用你的委托:
object DelegatesExt {
fun notNullSingleValue<T>():
ReadWriteProperty<Any?, T> = NotNullSingleValueVar()
}
重新實(shí)現(xiàn)Application單例化
在這個(gè)情景下琉挖,委托就可以幫助我們了启泣。我們直到我們的單例不會(huì)是null,但是我
們不能使用構(gòu)造函數(shù)去初始化屬性示辈。所以我們可以使用 notNull 委托:
class App : Application() {
companion object {
var instance: App by Delegates.notNull()
}
override fun onCreate() {
super.onCreate()
instance = this
}
}
這種情況下有個(gè)問(wèn)題寥茫,我們可以在app的任何地方去修改這個(gè)值,因?yàn)槿绻覀兪?br>
用 Delegates.notNull() 矾麻,屬性必須是var的纱耻。但是我們可以使用剛剛創(chuàng)建的委
托,這樣可以多一點(diǎn)保護(hù)险耀。我們只能修改這個(gè)值一次:
companion object {
var instance: App by DelegatesExt.notNullSingleValue()
}
盡管弄喘,在這個(gè)例子中,使用單例可能是最簡(jiǎn)單的方法甩牺,但是我想用代碼的形式展示
給你怎么去創(chuàng)建一個(gè)自定義的委托蘑志。