kotlin 介紹(二)高級(jí)特性和 anko 概述

高級(jí)特性

@Parcelize

我們經(jīng)常使用 Parceable 這個(gè)接口,但是使用這個(gè)接口存在兩個(gè)非常操蛋的地方。

  1. 需要實(shí)現(xiàn) writeToParcel() 方法和 createFromParcel() 方法做盅,這一個(gè)手寫非常操蛋,還好有 as 插件可以支持快速生成。

  2. 在該類添加字段之后,需要手動(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 拓展了常用的幾種屬性飒货。

  1. lazy 屬性 :其值只在首次訪問時(shí)計(jì)算;

  2. observable 屬性:監(jiān)聽器會(huì)收到有關(guān)此屬性變更的通知峭竣;

  3. 映射 屬性:把多個(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,也是是線程安全的模式,存在三種

  1. 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è)線程能夠訪問得到該方法酒奶。

  2. LazyThreadSafetyMode.PUBLICATION

    線程安全,可以允許多個(gè)線程

  3. 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
 }
 ......
}

整體流程圖如下:

image

(圖片來源網(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è)要素:

  1. Lambdas express

  2. Function-literals-with-receiver

我們以一個(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

  1. 控件通用庫刁标,從業(yè)務(wù)場景來說秒赤,一個(gè) TextView 的樣式,可能是固定幾種痹愚,例如聊天列表的樣式翔始,首頁標(biāo)題欄樣式,復(fù)用效果可能比 style 要好里伯,可讀性也要好一些城瞎。

  2. 使用 anko 代替 xml 的好處(見上kotlin DSL 開頭的介紹)

  3. 標(biāo)準(zhǔn)控件庫(插件版本),可以將代碼復(fù)用程度提到最高疾瓮。

缺點(diǎn):

java 調(diào)用脖镀,十分不方便。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市蜒灰,隨后出現(xiàn)的幾起案子弦蹂,更是在濱河造成了極大的恐慌,老刑警劉巖强窖,帶你破解...
    沈念sama閱讀 222,104評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件凸椿,死亡現(xiàn)場離奇詭異,居然都是意外死亡翅溺,警方通過查閱死者的電腦和手機(jī)脑漫,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,816評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來咙崎,“玉大人优幸,你說我怎么就攤上這事⊥拭停” “怎么了网杆?”我有些...
    開封第一講書人閱讀 168,697評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長伊滋。 經(jīng)常有香客問我碳却,道長,這世上最難降的妖魔是什么笑旺? 我笑而不...
    開封第一講書人閱讀 59,836評(píng)論 1 298
  • 正文 為了忘掉前任昼浦,我火速辦了婚禮,結(jié)果婚禮上燥撞,老公的妹妹穿的比我還像新娘座柱。我一直安慰自己,他們只是感情好物舒,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,851評(píng)論 6 397
  • 文/花漫 我一把揭開白布色洞。 她就那樣靜靜地躺著,像睡著了一般冠胯。 火紅的嫁衣襯著肌膚如雪火诸。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,441評(píng)論 1 310
  • 那天荠察,我揣著相機(jī)與錄音置蜀,去河邊找鬼。 笑死悉盆,一個(gè)胖子當(dāng)著我的面吹牛盯荤,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播焕盟,決...
    沈念sama閱讀 40,992評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼秋秤,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起灼卢,我...
    開封第一講書人閱讀 39,899評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤绍哎,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后鞋真,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體崇堰,經(jīng)...
    沈念sama閱讀 46,457評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,529評(píng)論 3 341
  • 正文 我和宋清朗相戀三年涩咖,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了海诲。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,664評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡抠藕,死狀恐怖饿肺,靈堂內(nèi)的尸體忽然破棺而出蒋困,到底是詐尸還是另有隱情盾似,我是刑警寧澤,帶...
    沈念sama閱讀 36,346評(píng)論 5 350
  • 正文 年R本政府宣布雪标,位于F島的核電站零院,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏村刨。R本人自食惡果不足惜告抄,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,025評(píng)論 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望嵌牺。 院中可真熱鬧打洼,春花似錦、人聲如沸逆粹。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,511評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽僻弹。三九已至阿浓,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間蹋绽,已是汗流浹背芭毙。 一陣腳步聲響...
    開封第一講書人閱讀 33,611評(píng)論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留卸耘,地道東北人退敦。 一個(gè)月前我還...
    沈念sama閱讀 49,081評(píng)論 3 377
  • 正文 我出身青樓,卻偏偏與公主長得像蚣抗,于是被迫代替她去往敵國和親侈百。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,675評(píng)論 2 359