关闭

使用 kotlin 来进行 Android 开发(一)

标签: ANDROIDkotlinMVP移动开发
5443人阅读 评论(0) 收藏 举报
分类:

文章选自本人知乎专栏并做更改:http://zhuanlan.zhihu.com/kotandroid 未经允许请勿转载。

本系列文章将通过解剖 kotlin_android_base_framework 项目,对其中的一些代码进行展开讲解,来挖掘 kotlin 在现实应用中的一些敏捷优雅之处。

一些资料

本系列文章内容的侧重点是 「 kotlin 在 Android Studio 中的实际应用」,通过现实代码来对 kotlin 的一些琐碎知识进行展开。所以作者建议读者,在阅读此系列文章之前先掌握 kotlin 的一些基础知识。

对于「如何在 Android Studio 中加入 kotlin 支持」,以及 kotlin 的一些基础语法,本系列文章不进行系统地讲解,读者可以参考下面两份文档。

Hello,Kotlin~

在编码界当中,对于绝大部分 Java 公民来讲,要说服他们花学习成本在一门新的语言上并不容易,要推动他们将 kotlin 用在生产环境中更加不容易。

所以在文章开头,请允许我先对该次专栏的主角 kotlin 进行一番介绍,藉此宣扬下「使用 kotlin 代替 Java 进行编码」的异端思想~

下面,我将列举 kotlin 的语法上的一些先进思想。至于 kotlin 语言本身的一些优势,请参考上一篇文章 #前言#,这里只谈谈 kotlin 的语法。

  • 简洁,安全的变量

kotlin 里的变量定义有两种,val 和 var。其中 val 等同 Java 中 final 修饰的变量(只读)。kotlin 的变量定义支持 赋值时类型推断,且 所有变量默认被修饰为不可为 null」,必须显式在类型后添加 ? 修饰符才可赋值为 null。

// Java
String strJava = "test"

// Kotlin
val strKot = "test"
var strNullable: String? = null

初次使用 kotlin 的用户可能会不习惯,在 Java 中,大多情况下变量都被定义为 nullable,这样可以通过对其赋值 null ,来表示该变量尚未初始化,或者该变量无数据。

但是这样要付出的代价往往是沉重的,默认类型为 nullable,导致了各种空类型安全问题。Java 代码中很多对外不透明的实现,你根本不知道返回值是否有可能为空,导致很多地方需要繁琐的非空检测,否则很容易触发 runtime error。

而 kotlin 明确地规定了所有类型默认为非空,这样的好处在于「从习惯上解决了空类型安全担忧」,无需频繁地担心变量是否为 null。但相应带来的一些成本就是,在变量初始化(赋值)阶段可能需要多做些功夫。

例如我在写 App.kt 中使用的巧妙的用法 (这里 companion object 代码块内的变量可以看作 Java 中的静态变量,关于 companion object 的介绍在下文会有提到) :

public class App : Application() {
    override fun onCreate() {
        super.onCreate()
        instanceTmp = this
    }

    companion object {
        private var instanceTmp: App? = null

        public val instance: App by lazy {
            instanceTmp!!
        }
    }
}

这里通过 lazy delegate 来对 instane 进行懒赋值(直到首次调用再进行赋值),这样,只要保证第一次对 instance 进行访问时 APP 已经实例化,那就可以保证 instance 恒不为空(大多情况下,你在 Android 开发中对Application 的实例进行访问时,该 Application 通常就已经被实例化了)。

形如上面的奇技淫巧还有很多,日后再详细展开说。而对于 nullable 的变量,可以使用 ?. 运算符检验是否为 null,如果为 null 则不运行运算符后的操作。

或者使用 !. 运算符来强制访问 nullable 变量的子属性或方法,如果该变量为 null 的话,就在运行时抛出错误:

var strNullable: String? = null
strNullable?.trim()
strNullable!.trim()

总结下。感觉 kotlin 的思想就是,尽量习惯性地将变量设计为 不可为空,这样在后面对该变量的运算中会减少很多问题。

  • 支持 Lambdas,函数是一级公民

不用再通过匿名对象传递方法,直接支持形如下面的写法:

view.setOnClickListener({
    // do something...
})

关于 kotlin 中的匿名类和 lambdas 的具体应用,会在以后的篇幅介绍。

例如下面对于 View 类的拓展:

public val View.ctx: Context
    get() = getContext()

进行以上定义后,就可以在所有 View 及其子类内直接访问 ctx 属性来取代调用 getContext(),多么美妙的事情!很多 kotlin 的外部库都依赖这个特性,来生成一些减少编码量的语法糖。

  • 此外还有一些超棒的语法糖!!!

例如 kotlin 默认为已定义的类属性生成 getter,setter,并支持形如下面的语法糖:

// Java
textView.setText("test")
showToast(textView.getText().toString())

// Kotlin
textView.text = "test"
showToast(textView.text.toString())

回归本系列

安利了 kotlin 的一大堆神油后,我们还是回到本系列的核心内容吧。本系列主要对 kotlin 在 Android 开发中的一些敏捷实现进行讲解。上文讲了一些 kotlin 语法上的强大之处,但是难以在这么短的篇幅内将 kotlin 的语法全部覆盖完。

所以作者还是希望读者先详细读完 kotlin 的官方文档,再结合本系列文章去理解 kotlin,以及去思考如何在 Android 开发里使用 kotlin 提高生产效率。

下面本系列正文将从 kotlin_android_base_framework 中第一个 kotlin 类进行展开讲解。

model 类的最佳实践

正文终于开始啦~本次内容将围绕 Model.kt 的代码,讲解 kotlin 中的 data class 以及它在 Android 开发中的一些应用。

首先,由简单做起

我们可以像下面这样定义一个简单的 data 类:

data class Model(var test1: Int, var test2: Int)

data 关键字提供以下的一些 features:

  • equals() / hashCode() pair
  • toString() of the form "User(name=John, age=42)"
  • componentN() functions corresponding to the properties in their order or declaration
  • copy() function

componentN()

按上面第三点的解释,可以理解为 test1 按照属性定义的顺序与component1() 函数对应,test2 则对应 component2()。

为什么需要 componentN() 函数呢?可以参考 Multi-Declarations 这节,主要是为了实现 多重赋值,解锁下列各种便利的写法:

val (test1, test2) = model
// 内部实现为:
// test1 = model.component1()
// test2 = model.component2()

var list = arrayListOf<Model>()
for((t1, t2) in list ) { ... }

另外,kotlin 中提供了 Pair 类来处理双元对数据,例如跳转到其他Activity 时可以这样:

val intent = intentFor<OtherActivity>(
    "data" to Model(5, 0),
    "data2" to "xxx"
)
intent.singleTop()
startActivity(intent)

其中 "data2" to Model(5, 0) 的等价实现其实就是:

Pair<String, Any>("data2", Model(5, 0))

copy()

data class 提供的 copy() 函数是 深度复制,其内部实现如下:

fun copy(test1: Int = this.test1, test2: Int = this.test2) = Model(test1, test2)

于是乎能够支持下面这种优雅的用法~:

val jack = User(name = "Jack", age = 1)
val olderJack = jack.copy(age = 2)

Android 粑粑有话说

在 Android 中,Activity 之间需要传递数据通常需要对数据进行序列化,data class 就必须继承 Parcelable 或 Serializable 接口。结合我们上面的一些 points,可以对 Model 类进行如下的改造:

data class Model(var test1: Int, var test2: Int): Parcelable {

    constructor(source: Parcel): this(source.readInt(), source.readInt())

    override fun describeContents(): Int {
        return 0
    }

    override fun writeToParcel(dest: Parcel?, flags: Int) {
        dest?.writeInt(this.test1)
        dest?.writeInt(this.test2)
    }

    companion object {
        @JvmField final val CREATOR: Parcelable.Creator<Model> = object : Parcelable.Creator<Model> {
            override fun createFromParcel(source: Parcel): Model{
                return Model(source)
            }

            override fun newArray(size: Int): Array<Model?> {
                return arrayOfNulls(size)
            }
        }
    }
}

constructor

在上面的一坨代码中(大雾),我们先看看 data class 的构造函数。在 kotlin 的 官方文档 constructor 小结 中指定了,写在类定义头部(类名后面)的为首要构造函数 ,也就是下面这段已经构成了 data class 的类定义及 primary constructor

data class Model(var test1: Int, var test2: Int) { ... }

而其内部显式定义的的 constructor(source: Parcel) 则为副构造函数,其传入值类型为 Parcel,返回值为 使用主构造函数构造后的 class

this(source.readInt(), source.readInt())

companion

翻译自 kotlin doc

不像 Java 或者 C#,在 Kotlin 中,Class 没有静态方法。在大多数情况下,推荐用 package-level 的函数来代替静态方法。

如果你需要写一个不需要实例化 Class 就能访问 Class 内部的函数(例如一个工厂函数),你可以 把它声明成 Class 内的一个实名 Object

另外,如果你在 Class 内声明了一个 companion object,在该对象内的所有成员都将相当于使用了 Java/C# 语法中的 static 修饰符,在外部只能通过类名来对这些属性或者函数进行访问。

而至于 @JvmField 的作用可以参考 Java Interop - Fields

结语

至此,我们已经可以在 Android 中使用 kotlin 很轻易地定义一些我们需要的数据类,这是一次很不错的收获。下一章节,我们将提高下难度,从我们的 activity helper 讲起。

0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:128474次
    • 积分:2045
    • 等级:
    • 排名:第19504名
    • 原创:53篇
    • 转载:21篇
    • 译文:0篇
    • 评论:71条
    Git@OSC
    博客专栏
    文章分类
    最新评论