Kotlin — Part 8:方向改变(序列化&数据类)

在这个章节我们将回顾如何处理我们设备的方向改变问题,事实,我们将查看如何处理运行改变,在方向改变时,不浪费已经加载的数据.同时,我们将查看一些 Kotlin 的技巧,使我们的代码更加简洁,干净,可阅读.

全部章节:

Kotlin — Part 0:关于这个系列
Kotlin — Part 1:配置 Android Studio
Kotlin — Part 2:语法,空安全,静态类型
Kotlin — Part 3:扩展函数、Android 扩展、委托属性
Kotlin — Part 4:RecyclerView— Kotlin 适配器委托&数据类
Kotlin — Part 5:Kotlin,RxJava&RxAndroid
Kotlin — Part 6:API-Retrofit&Kotlin)
Kotlin — Part 7:无限滑动:高阶函数& Lambdas
Kotlin — Part 8:方向改变(序列化&数据类)
Kotlin — Part 9:单元测试与 Kotlin(Mockito,RxJava)

Github 仓库:https://github.com/imuhao/KedditBySteps

Parcelable

如果你已经做过 Android 开发工作,那么你肯定已经知道关于 Parcelable.Parcelable 是一个接口,实现它的实例可以从一个 Parcel 中进行读写属性.这是 Android 中提供的一个序列化方式.

另外,Android 提供了一些生命周期方法,允许我们暂时保存请求的数据到一个 Parcel 类中,处理运行时改变(设备旋转,或者 Activity 被系统杀死).在我们的例子中,我们需要保存我们的新闻,所以我们将实现 Parcelable 接口

数据类与 Parcelable

在这个 commit 中可以查看到我们的数据类实现 Parcelable 接口的所有详情

https://github.com/imuhao/KedditBySteps/commit/0bf385f0556074569fc6a2a7838e1291d163ce9b

让我们重新查看一下这个类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
data class RedditNews(val after: String,
val before: String,
val news: List<RedditNewsItem>) : Parcelable {

// 1
companion object {
// 2
@JvmField @Suppress("unused")
val CREATOR = createParcel { RedditNews(it) } // 3
}

// 4
protected constructor(parcelIn: Parcel) : this(
parcelIn.readString(),
parcelIn.readString(),
mutableListOf<RedditNewsItem>().apply {
parcelIn.readTypedList(this, RedditNewsItem.CREATOR)
}
)

override fun writeToParcel(dest: Parcel, flags: Int) {
dest.writeString(after)
dest.writeString(before)
dest.writeTypedList(news)
}

override fun describeContents() = 0
}

1.Companion object

你可能注意到,在 Kotlin 中没有static 关键字来为类创建一个静态方法,但是存在一个叫做 companion object,运行你提供相同的行为.当你直接在一个对象中使用 这个关键字,那么这个对象内部的属性和方法可以直接通过类名调用.

1
2
3
4
5
data class RedditNews(...)
companion object {
val ENDPOINT = "http://kotlinlang.org"
}
}

使用它:

1
RedditNews.ENDPOINT

2.@JvmField 注解

类实现 Parcelable 接口必须有一个非空的静态字段 CREATOR ,这个类型实现了 Parcelable.Creator 接口,为了使我们的 CREATOR 在 Java 中可见,我们需要使用指定注解 @JvmField,否则在Java 中会找不到而抛出一个异常.

3.Creator扩展函数

使用参数函数创建 CREATOR

1
val CREATOR = createParcel { RedditNews(it) }

默认定义的继承函数像这样:

1
2
3
4
5
6
inline fun <reified T : Parcelable> createParcel(
crossinline createFromParcel: (Parcel) -> T?): Parcelable.Creator<T> =
object : Parcelable.Creator<T> {
override fun createFromParcel(source: Parcel): T? = createFromParcel(source)
override fun newArray(size: Int): Array<out T?> = arrayOfNulls(size)
}

4.第二构造函数

我们已经习惯了使用主函数创建类,像这样

1
data class News(val title: String)

但是主函数有时候可能不是足够的,我们需要提供一个选择的方法创建这个类,像这个例子我们请求一个 Parcel 参数.

1
protected constructor(parcelIn: Parcel) : this(...)

序列化插件

如果上面这些使你觉得很复杂,你想要一个简单的方法使你的数据类序列化,这里有一个伟大的插件可以帮助你

android-parcelable-intellij-plugin-ktion

使用这个扩展可以一键将类序列化

保存新闻列表

现在我们需要使用著名的 onSaveInstanceState函数来保存我们的新闻列表,并且在 onActivityCreated 中得到保存的新闻数据.这个 commit 有所有需要的代码

https://github.com/imuhao/KedditBySteps/commit/234e2f9ec5bf1f6a40a79fc8094551c2339517b1

你应该感兴趣的代码:

1
2
3
4
5
6
7
8
news_list.apply {
setHasFixedSize(true)
val linearLayout = LinearLayoutManager(context)
layoutManager = linearLayout
clearOnScrollListeners()
...
...
}

Apply 是一个扩展函数,允许你在执行 apply 实例对象的内部执行一段代码,这是一个非常有用的功能,这样你就不用每次调用属性方法时添加属性名.

结论

我们差不多已经到了这个系列的结尾,希望这个部分对你是有用的,可以帮助你编写更好的 Kotlin app.

在下一个部分我们将在 Kotlin 中创建测试.

再见!