kotlin 介绍(二)高级特性和 anko 概述

高级特性

@Parcelize

我们经常使用 Parceable 这个接口,但是使用这个接口存在两个非常操蛋的地方。

  1. 需要实现 writeToParcel() 方法和 createFromParcel() 方法,这一个手写非常操蛋,还好有 as 插件可以支持快速生成。

  2. 在该类添加字段之后,需要手动或者再一次生成 Parceable 相关的实现方法和抽象类。

这种操作极度不友好。

在 kotlin 这里,你可以消除这些烦恼。代码如下:

@Parcelize
data class UserInfo(var name: String, var age: Int) : Parcelable {
}

再此之前你需要在该 module 下的 build.gradle 文件中的 Android 标签下,添加如下代码:

android {
 androidExtensions{
 experimental = true
 }
}

通过decompile 该kt 文件的字节码,我们可以知道,实际上这个注解,帮助我们实现了 Parcelable 接口的相关内容。

Delegation 代理

代理和代理属性,代理是类似继承的一种方式,同时也是继承更好的替代方案。kotlin 实现类似多继承的功能,也就是代理,简单的 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")
 }
}

总结:

<Dervied Class Name>: <Delagate Inferface Name> by <Delagate Class SingTone>

也就是 代理类 :被代理接口 by 接口实例

那么有人问,这里和 java 的接口继承有什么不同呢?这里是拓展了,接口继承的形式,java 继承类是单继承的,可以实现多个接口,但是接口一般是空的。所 kotlin 拓展的继承,是一种偏向组合的思想,而不是继承。假设这样一个场景,你需要继承多个具有Base 功能的类,那么在 java 上,是无法实现的,但是在kotlin 可以使用代理实现。(例举 关注逻辑的实现)

Decompile 之后发现:

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);
 }
}

所以实际上,这是一种类似继承的关系,但代理模式是比继承更解耦的设计模式。

我们可以给我们的功能设计许多的类似工具方法或者工具类的代理对象类,例如实现主播关注的类,实现请求用户信息的类,然后再需要实现这个功能的地方,使用代理实现,这样就可以节省很多代码,同时也达到了高度解耦。

Delegated Properties 代理属性

怎么理解代理属性这样一种东西呢?我们可能需要实现各种各样的特效的属性,例如延迟初始化,kotlin 拓展了常用的几种属性。

  1. lazy 属性 :其值只在首次访问时计算;

  2. observable 属性:监听器会收到有关此属性变更的通知;

  3. 映射 属性:把多个属性储存在一个映射(map)中,而不是每个存在单独的字段中

标准代理属性-by lazy block

这是委托属性的一种,可以实现延迟加载,并且提供多种方式,控制线程安全,其使用语法:

val/var <property name>: <Type> by <expression>

例如一个简单的例子:

val mMsg: String by lazy {
 "kk" + "oo"
}

mMsg 会在第一次访问的时候,执行初始化,然后被赋值。

其代码实现在 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)
 }

重载了两个 lazy 方法,其中第二个方法提供了一个参数 mode:LazyThreadSafetyMode,也是是线程安全的模式,存在三种

  1. LazyThreadSafetyMode.SYNCHRONIZED

    线程安全,并且只有一个现场可以访问到 initializer 初始化代码块,其实现类是 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 给对象加锁,确保只有一个线程能够访问得到该方法。

  2. LazyThreadSafetyMode.PUBLICATION

    线程安全,可以允许多个线程

  3. LazyThreadSafetyMode.NONE

    非线程安全

Kotlin DSL

DSL 总体来说是非常简洁的,在使用 Android 开发 java 的过程中,貌似么有使用过 DSL之类的写法,而在 kotlin 中,使用最多的 DSL 就是 anko

根据 anko 的官方文档说明,使用 DSL 有以下好处(也就是使用 xml 的坏处):

By default, UI in Android is written using XML. That is inconvenient in the following ways:

  • It is not typesafe 非类型安全的,也即是 findView 的时候,需要注意类型

  • It is not null-safe 可能为空的

  • It forces you to write almost the same code for every layout you make layout 布局里面存在大量的共同代码,复用率较低

  • XML is parsed on the device wasting CPU time and battery xml 解析消耗额外的 cpu 和时间

  • Most of all, it allows no code reuse 代码不存在复用关系

同样对比传统的 硬编码编写UI 来说,anko 也存在吸引点,就是代码简洁。

例如,传统的硬编码编写 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 的原理就是扩展函数以及DSL,例如在 AnkoLayoutActivity.kt 中有如下代码:

class AnkoLayoutActivity:Activity() {
 override fun onCreate(savedInstanceState: Bundle?) {
 super.onCreate(savedInstanceState)
 }
 fun initLayout(){
 verticalLayout {
 backgroundColor = Color.parseColor("#eeeeee")
 }
 }
}

实际上使用的是 Activity 的一个扩展函数,如下:

inline fun Activity.verticalLayout(theme: Int = 0, init: (@AnkoViewDslMarker _LinearLayout).() -> Unit): LinearLayout {
 return ankoView(`$Anko$Factories$CustomViews`.VERTICAL_LAYOUT_FACTORY, theme, init)
}

后面的操作,实际上就是帮你创建一个 LinearLayout ,这个 LinearLayout 也是特殊的 LinearLayout,增加了对应的扩展方法,如下:

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

(图片来源网络)

View的创建一层一层的传递下去,其中共有三种情况,即创建View的三种上下文:

  • Activity,在Activity的onCreate中调用,判断到最后通过setContentView来填充整个布局

  • Context & Fragment,在其他场景下创建的View,可直接通过UI { } 域生成相应的View

  • AnkoContext,在自定义AnkoComponent中的createView方法中定义

也就意味着,你可以在 Activity,Fragment,或者自定义 ViewGroup 中使用 Anko-layout.

原生DSL

anko 支持 DSL 实现原理有两个要素:

  1. Lambdas express

  2. Function-literals-with-receiver

我们以一个案例讲解这里的用法:

inline fun Activity.verticalLayout(theme: Int = 0, init: (@AnkoViewDslMarker _LinearLayout).() -> Unit): LinearLayout {
 return ankoView(`$Anko$Factories$CustomViews`.VERTICAL_LAYOUT_FACTORY, theme, init)
}

首先,在Activity 的扩展函数 verticalLayout() 里面,传入一个 lambdas 作为参数,init: (@AnkoViewDslMarker _LinearLayout).() -> Unit),然后这个命名为 init 的函数,是一个带接受者的函数,也就是 _LinearLayout.() -> Unit,其接受者类型为 _LinearLayout,这个类继承自 LinearLayout。

上述代码可以简化成这样:

fun Context.createLinearLayout(init:LinearLayout.() ->Unit):LinearLayout{
 val layout = LinearLayout(this)
 layout.init()
 return  layout
}

所以你在某种 Context 内调用这个 createLinearLayout 的时候,就可以直接传入一个 Lambdas 表达式,其this 指向 LinearLayout,所以你调用该方法的时候,如下:

//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

根据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

    这里规定了DSL 的一些有意义的规范,例如DSL 块必须有接收者,其次不允许在一个代码块,存在两个相同的接收者,只要靠近代码块那个,才会生效。

简单来说,DslMarker 是为了控制作用范围,例如以下的情况:

html {
 head {
 head { } // 错误:外部接收者的成员
 }
 // ……
}

也就是说,第二个 head 代码块的接受者实际上应该是 html,但是在这种情况下,明显就不是了,所以使用 DslMarker,编译器会提示这种错误。

从 1.1 版本开始, kotlin 使用 @DslMaker 用来检查 dsl 块的作用范围,如果没有显示声明代码块的接收者,编译器会发出警告。

why anko

  1. 控件通用库,从业务场景来说,一个 TextView 的样式,可能是固定几种,例如聊天列表的样式,首页标题栏样式,复用效果可能比 style 要好,可读性也要好一些。

  2. 使用 anko 代替 xml 的好处(见上kotlin DSL 开头的介绍)

  3. 标准控件库(插件版本),可以将代码复用程度提到最高。

缺点:

java 调用,十分不方便。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,384评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,845评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,148评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,640评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,731评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,712评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,703评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,473评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,915评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,227评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,384评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,063评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,706评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,302评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,531评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,321评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,248评论 2 352