高級(jí)特性
@Parcelize
我們經(jīng)常使用 Parceable 這個(gè)接口,但是使用這個(gè)接口存在兩個(gè)非常操蛋的地方。
需要實(shí)現(xiàn) writeToParcel() 方法和 createFromParcel() 方法做盅,這一個(gè)手寫非常操蛋,還好有 as 插件可以支持快速生成。
在該類添加字段之后,需要手動(dòng)或者再一次生成 Parceable 相關(guān)的實(shí)現(xiàn)方法和抽象類邢疙。
這種操作極度不友好。
在 kotlin 這里望薄,你可以消除這些煩惱疟游。代碼如下:
@Parcelize
data class UserInfo(var name: String, var age: Int) : Parcelable {
}
再此之前你需要在該 module 下的 build.gradle 文件中的 Android 標(biāo)簽下,添加如下代碼:
android {
androidExtensions{
experimental = true
}
}
通過decompile 該kt 文件的字節(jié)碼痕支,我們可以知道颁虐,實(shí)際上這個(gè)注解,幫助我們實(shí)現(xiàn)了 Parcelable 接口的相關(guān)內(nèi)容卧须。
Delegation 代理
代理和代理屬性另绩,代理是類似繼承的一種方式,同時(shí)也是繼承更好的替代方案花嘶。kotlin 實(shí)現(xiàn)類似多繼承的功能笋籽,也就是代理,簡單的 demo 如下:
//被代理的 接口A
interface Base {
val mDefaultMsg: String
fun printX()
fun printMessage(contenc: String)
}
class BaseImpl : Base {
override val mDefaultMsg: String
get() = "mDefaultMsg"
override fun printX() {}
override fun printMessage(contenc: String) {
}
}
//被代理的接口B
interface IPresnter
class BasePresenter : IPresnter
open class Item
open class HomeItem
//代理類
class Dervied(b: Base, basePresenter: BasePresenter) : Base by b, Item(), IPresnter by basePresenter {
override fun printX() {
Log.i("Dervied", "printX")
}
}
總結(jié):
<Dervied Class Name>: <Delagate Inferface Name> by <Delagate Class SingTone>
也就是 代理類 :被代理接口 by 接口實(shí)例
那么有人問察绷,這里和 java 的接口繼承有什么不同呢干签?這里是拓展了津辩,接口繼承的形式拆撼,java 繼承類是單繼承的容劳,可以實(shí)現(xiàn)多個(gè)接口,但是接口一般是空的闸度。所 kotlin 拓展的繼承竭贩,是一種偏向組合的思想,而不是繼承莺禁。假設(shè)這樣一個(gè)場景留量,你需要繼承多個(gè)具有Base 功能的類,那么在 java 上哟冬,是無法實(shí)現(xiàn)的楼熄,但是在kotlin 可以使用代理實(shí)現(xiàn)。(例舉 關(guān)注邏輯的實(shí)現(xiàn))
Decompile 之后發(fā)現(xiàn):
public static final class Dervied extends DelategeTest.Item implements DelategeTest.Base, DelategeTest.IPresnter {
// $FF: synthetic field
private final DelategeTest.Base $delegate_0;
// $FF: synthetic field
private final DelategeTest.BasePresenter $delegate_1;
public void printX() {
Log.i("Dervied", "printX");
}
public Dervied(@NotNull DelategeTest.Base b, @NotNull DelategeTest.BasePresenter basePresenter) {
Intrinsics.checkParameterIsNotNull(b, "b");
Intrinsics.checkParameterIsNotNull(basePresenter, "basePresenter");
super();
this.$delegate_0 = b;
this.$delegate_1 = basePresenter;
}
@NotNull
public String getMDefaultMsg() {
return this.$delegate_0.getMDefaultMsg();
}
public void printMessage(@NotNull String contenc) {
Intrinsics.checkParameterIsNotNull(contenc, "contenc");
this.$delegate_0.printMessage(contenc);
}
}
所以實(shí)際上浩峡,這是一種類似繼承的關(guān)系可岂,但代理模式是比繼承更解耦的設(shè)計(jì)模式。
我們可以給我們的功能設(shè)計(jì)許多的類似工具方法或者工具類的代理對(duì)象類翰灾,例如實(shí)現(xiàn)主播關(guān)注的類缕粹,實(shí)現(xiàn)請(qǐng)求用戶信息的類,然后再需要實(shí)現(xiàn)這個(gè)功能的地方纸淮,使用代理實(shí)現(xiàn)平斩,這樣就可以節(jié)省很多代碼,同時(shí)也達(dá)到了高度解耦咽块。
Delegated Properties 代理屬性
怎么理解代理屬性這樣一種東西呢绘面?我們可能需要實(shí)現(xiàn)各種各樣的特效的屬性,例如延遲初始化侈沪,kotlin 拓展了常用的幾種屬性飒货。
lazy 屬性 :其值只在首次訪問時(shí)計(jì)算;
observable 屬性:監(jiān)聽器會(huì)收到有關(guān)此屬性變更的通知峭竣;
映射 屬性:把多個(gè)屬性儲(chǔ)存在一個(gè)映射(map)中塘辅,而不是每個(gè)存在單獨(dú)的字段中
標(biāo)準(zhǔn)代理屬性-by lazy block
這是委托屬性的一種,可以實(shí)現(xiàn)延遲加載皆撩,并且提供多種方式扣墩,控制線程安全,其使用語法:
val/var <property name>: <Type> by <expression>
例如一個(gè)簡單的例子:
val mMsg: String by lazy {
"kk" + "oo"
}
mMsg 會(huì)在第一次訪問的時(shí)候扛吞,執(zhí)行初始化呻惕,然后被賦值。
其代碼實(shí)現(xiàn)在 Lazy.kt 文件中滥比,源碼如下:
@kotlin.jvm.JvmVersion
public fun lazy(initializer: () -> T): Lazy = SynchronizedLazyImpl(initializer)
@kotlin.jvm.JvmVersion
public fun <T> lazy(mode: LazyThreadSafetyMode, initializer: () -> T): Lazy<T> =
when (mode) {
LazyThreadSafetyMode.SYNCHRONIZED -> SynchronizedLazyImpl(initializer)
LazyThreadSafetyMode.PUBLICATION -> SafePublicationLazyImpl(initializer)
LazyThreadSafetyMode.NONE -> UnsafeLazyImpl(initializer)
}
重載了兩個(gè) lazy 方法亚脆,其中第二個(gè)方法提供了一個(gè)參數(shù) mode:LazyThreadSafetyMode,也是是線程安全的模式,存在三種
-
LazyThreadSafetyMode.SYNCHRONIZED
線程安全盲泛,并且只有一個(gè)現(xiàn)場可以訪問到 initializer 初始化代碼塊濒持,其實(shí)現(xiàn)類是 SynchronizedLazyImpl键耕。
@JvmVersion private class SynchronizedLazyImpl(initializer: () -> T, lock: Any? = null) : Lazy, Serializable { private var initializer: (() -> T)? = initializer @Volatile private var _value: Any? = UNINITIALIZED_VALUE // final field is required to enable safe publication of constructed instance private val lock = lock ?: this override val value: T get() { val _v1 = _value //先看變量是否初始化過,如果初始化過柑营,直接返回變量值 if (_v1 !== UNINITIALIZED_VALUE) { @Suppress("UNCHECKED_CAST") return _v1 as T } //synchronized return synchronized(lock) { val _v2 = _value if (_v2 !== UNINITIALIZED_VALUE) { @Suppress("UNCHECKED_CAST") (_v2 as T) } else { val typedValue = initializer!!() _value = typedValue initializer = null typedValue } } } override fun isInitialized(): Boolean = _value !== UNINITIALIZED_VALUE override fun toString(): String = if (isInitialized()) value.toString() else "Lazy value not initialized yet." private fun writeReplace(): Any = InitializedLazyImpl(value) }
這里的代碼非常簡單屈雄,在get() 方法里面,使用 synchronized 給對(duì)象加鎖官套,確保只有一個(gè)線程能夠訪問得到該方法酒奶。
-
LazyThreadSafetyMode.PUBLICATION
線程安全,可以允許多個(gè)線程
-
LazyThreadSafetyMode.NONE
非線程安全
Kotlin DSL
DSL 總體來說是非常簡潔的,在使用 Android 開發(fā) java 的過程中奶赔,貌似么有使用過 DSL之類的寫法惋嚎,而在 kotlin 中,使用最多的 DSL 就是 anko
根據(jù) anko 的官方文檔說明站刑,使用 DSL 有以下好處(也就是使用 xml 的壞處):
By default, UI in Android is written using XML. That is inconvenient in the following ways:
It is not typesafe 非類型安全的瘸彤,也即是 findView 的時(shí)候,需要注意類型
It is not null-safe 可能為空的
It forces you to write almost the same code for every layout you make layout 布局里面存在大量的共同代碼笛钝,復(fù)用率較低
XML is parsed on the device wasting CPU time and battery xml 解析消耗額外的 cpu 和時(shí)間
Most of all, it allows no code reuse 代碼不存在復(fù)用關(guān)系
同樣對(duì)比傳統(tǒng)的 硬編碼編寫UI 來說质况,anko 也存在吸引點(diǎn),就是代碼簡潔玻靡。
例如结榄,傳統(tǒng)的硬編碼編寫 UI 如下:
val act = this
val layout = LinearLayout(act)
layout.orientation = LinearLayout.VERTICAL
val name = EditText(act)
val button = Button(act)
button.text = "Say Hello"
button.setOnClickListener {
Toast.makeText(act, "Hello, ${name.text}!", Toast.LENGTH_SHORT).show()
}
layout.addView(name)
layout.addView(button)
但是如果使用 anko-layout:
verticalLayout {
val name = editText()
button("Say Hello") {
onClick { toast("Hello, ${name.text}!") }
}
}
anko-layout 原理
簡單來說,anko-layout 的原理就是擴(kuò)展函數(shù)以及DSL囤捻,例如在 AnkoLayoutActivity.kt 中有如下代碼:
class AnkoLayoutActivity:Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
fun initLayout(){
verticalLayout {
backgroundColor = Color.parseColor("#eeeeee")
}
}
}
實(shí)際上使用的是 Activity 的一個(gè)擴(kuò)展函數(shù),如下:
inline fun Activity.verticalLayout(theme: Int = 0, init: (@AnkoViewDslMarker _LinearLayout).() -> Unit): LinearLayout {
return ankoView(`$Anko$Factories$CustomViews`.VERTICAL_LAYOUT_FACTORY, theme, init)
}
后面的操作臼朗,實(shí)際上就是幫你創(chuàng)建一個(gè) LinearLayout ,這個(gè) LinearLayout 也是特殊的 LinearLayout,增加了對(duì)應(yīng)的擴(kuò)展方法蝎土,如下:
open class _LinearLayout(ctx: Context): LinearLayout(ctx) {
inline fun <T: View> T.lparams(
c: Context?,
attrs: AttributeSet?,
init: LinearLayout.LayoutParams.() -> Unit
): T {
val layoutParams = LinearLayout.LayoutParams(c!!, attrs!!)
layoutParams.init()
this@lparams.layoutParams = layoutParams
return this
}
......
}
整體流程圖如下:
(圖片來源網(wǎng)絡(luò))
View的創(chuàng)建一層一層的傳遞下去视哑,其中共有三種情況,即創(chuàng)建View的三種上下文:
Activity誊涯,在Activity的onCreate中調(diào)用挡毅,判斷到最后通過setContentView來填充整個(gè)布局
Context & Fragment,在其他場景下創(chuàng)建的View暴构,可直接通過UI { } 域生成相應(yīng)的View
AnkoContext跪呈,在自定義AnkoComponent中的createView方法中定義
也就意味著,你可以在 Activity取逾,F(xiàn)ragment耗绿,或者自定義 ViewGroup 中使用 Anko-layout.
原生DSL
anko 支持 DSL 實(shí)現(xiàn)原理有兩個(gè)要素:
我們以一個(gè)案例講解這里的用法:
inline fun Activity.verticalLayout(theme: Int = 0, init: (@AnkoViewDslMarker _LinearLayout).() -> Unit): LinearLayout {
return ankoView(`$Anko$Factories$CustomViews`.VERTICAL_LAYOUT_FACTORY, theme, init)
}
首先,在Activity 的擴(kuò)展函數(shù) verticalLayout() 里面砾隅,傳入一個(gè) lambdas 作為參數(shù)误阻,init: (@AnkoViewDslMarker _LinearLayout).() -> Unit),然后這個(gè)命名為 init 的函數(shù),是一個(gè)帶接受者的函數(shù)究反,也就是 _LinearLayout.() -> Unit寻定,其接受者類型為 _LinearLayout,這個(gè)類繼承自 LinearLayout奴紧。
上述代碼可以簡化成這樣:
fun Context.createLinearLayout(init:LinearLayout.() ->Unit):LinearLayout{
val layout = LinearLayout(this)
layout.init()
return layout
}
所以你在某種 Context 內(nèi)調(diào)用這個(gè) createLinearLayout 的時(shí)候特姐,就可以直接傳入一個(gè) Lambdas 表達(dá)式晶丘,其this 指向 LinearLayout黍氮,所以你調(diào)用該方法的時(shí)候,如下:
//anko 的寫法
verticalLayout {
backgroundColor = Color.parseColor("#eeeeee")
}
//我們自己的寫法
createLinearLayout {
backgroundColor = Color.parseColor("#eeeeee")
}
DslMarker
其次Anko 為了提高 DSL 的效率浅浮,還是使用了 @DslMarker 注解:
//Ui.kt 中
@DslMarker
@Target(AnnotationTarget.TYPE)
annotation class AnkoViewDslMarker
//Annotatios.kt 中
@Target(ANNOTATION_CLASS)
@Retention(BINARY)
@MustBeDocumented
@SinceKotlin("1.1")
public annotation class DslMarker
根據(jù)kotlin 官方文檔的解釋:
The general rule:
an implicit receiver may belong to a DSL @X if marked with a corresponding DSL marker annotation
two implicit receivers of the same DSL are not accessible in the same scope
the closest one wins
-
other available receivers are resolved as usual, but if the resulting resolved call binds to such a receiver, it's a compilation error
這里規(guī)定了DSL 的一些有意義的規(guī)范沫浆,例如DSL 塊必須有接收者,其次不允許在一個(gè)代碼塊滚秩,存在兩個(gè)相同的接收者专执,只要靠近代碼塊那個(gè),才會(huì)生效郁油。
簡單來說本股,DslMarker 是為了控制作用范圍,例如以下的情況:
html {
head {
head { } // 錯(cuò)誤:外部接收者的成員
}
// ……
}
也就是說桐腌,第二個(gè) head 代碼塊的接受者實(shí)際上應(yīng)該是 html,但是在這種情況下拄显,明顯就不是了,所以使用 DslMarker案站,編譯器會(huì)提示這種錯(cuò)誤躬审。
從 1.1 版本開始, kotlin 使用 @DslMaker 用來檢查 dsl 塊的作用范圍蟆盐,如果沒有顯示聲明代碼塊的接收者,編譯器會(huì)發(fā)出警告。
why anko
控件通用庫刁标,從業(yè)務(wù)場景來說秒赤,一個(gè) TextView 的樣式,可能是固定幾種痹愚,例如聊天列表的樣式翔始,首頁標(biāo)題欄樣式,復(fù)用效果可能比 style 要好里伯,可讀性也要好一些城瞎。
使用 anko 代替 xml 的好處(見上kotlin DSL 開頭的介紹)
標(biāo)準(zhǔn)控件庫(插件版本),可以將代碼復(fù)用程度提到最高疾瓮。
缺點(diǎn):
java 調(diào)用脖镀,十分不方便。