安卓开发Android

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

2020-11-05  本文已影响0人  kotlon

高级特性

@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:

同样对比传统的 硬编码编写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,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:

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

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

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

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

why anko

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

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

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

缺点:

java 调用,十分不方便。

上一篇 下一篇

猜你喜欢

热点阅读