当Kotlin遇上Parcelable

由来

  因为现在项目里面都是用 kotlin 在编写项目,在跨界面传递数据的时候经常需要传递对象数据,这就需要使用到 对象的序列化 ,就难免和 Parcelable 打交道。

序列化的方式

  在Android中,对象的序列化方式是有两种的,一种是Java中的 Serializable ,一种是Android特有的 Parcelable 。既然Google新增加了一种 Parcelable 的方式,那必然有它的道理,我们先来看看两者有啥不可告人的秘密。

Serializable的序列化方式

  这种序列化方式给我们的第一印象就是 简洁 。因为你只需要实现 Serializable 接口就可以了。这是一个标识接口,你不需要实现任何方法,Java就会对其进行序列化操作。但但但是,这种序列化的方式使用了 反射,而且在序列化过程中产生很多的临时对象,造成过多的内存消耗

1
2
3
4
5
6
7
8
9
10
11
package com.yanfangxiong.kotlinparcelabledemo

import java.io.Serializable

/**
* @author fxYan
*/
data class Person(
var name: String?,
var sex: String?
) : Serializable

Parcelable的序列化方式

  这种序列化方式是Android所特有的。而且使用起来 比较复杂 ,我们先来举个栗子。

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
29
30
31
32
33
34
35
36
37
package com.yanfangxiong.kotlinparcelabledemo

import android.os.Parcel
import android.os.Parcelable

/**
* @author fxYan
*/
data class Person(
var name: String?,
var sex: String?
) : Parcelable {

companion object CREATOR : Parcelable.Creator<Person> {
override fun createFromParcel(parcel: Parcel): Person {
return Person(parcel)
}

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

constructor(parcel: Parcel) : this(
parcel.readString(),
parcel.readString())

override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeString(name)
parcel.writeString(sex)
}

override fun describeContents(): Int {
return 0
}

}

详解Parcelable

  1. writeToParcel() 通过这个方法你可以将对象的属性都写入到parcel中;
  2. describeContents() 这个方法一般情况下默认就好了。关于这个方法,API中是这么描述的
    describeContents,它表示这个Parcelable对象序列化内容的类别。举个栗子,如果你要序列化对象里面包含文件描述符,那么你需要将这个方法修改为返回 CONTENTS_FILE_DESCRIPTOR
  3. 编写一个类CREATOR继承自 Parcelable.Cretor ,这个接口包含两个方法,createFromParcel()从Parcel容器中值,newArray()这个方法是 供外部类反序列化本类数组使用的

  从上面的代码我们就可以看出,如何序列化这个对象已经非常清楚的表现出来,根本不需要通过反射来知道来推断类型,所以能够更加高效的序列化对象。

区别与抉择

  1. Serializable实现方式简单,但是比较消耗内存,一般建议在序列化对象保存到文件中的时候使用;
  2. Parcelable实现方式较为复杂,但是效率高,消耗内存小,在代码中建议使用这种方式。

问题所在

  我们知道,在kotlin中伴生对象只能存在一个 ,一般一些在java中的静态常量我们可能会定义在伴生对象中,但是我们可以看上面的Person的Parcelable实现,系统默认给我们创建的CREATOR对象就直接指定为了伴生对象,这样虽然是没有问题的,但是你定义的一些常量就是属于CREATOR对象了,实际上这是不必要的,所以我们需要一种方式将伴生对象 “释放” 出来。
  这里我们就直接上代码了。只需要使用 @JVMField 注解就可以解决这个问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
companion object {

@JvmField
val CREATOR: Parcelable.Creator<Person> = object : Parcelable.Creator<Person> {

override fun createFromParcel(parcel: Parcel): Person {
return Person(parcel)
}

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

}

}

  但但但是,前面说了,使用Parcelable的序列化方式,类里面的方法会增加很多,所以我们打算来优化优化。
  首先,kotlin像java 8一样,接口中的方法可以有默认的实现 ,于是我决定写一个KParcelable的接口,如下

1
2
3
4
5
interface KParcelable : Parcelable {
override fun describeContents() = 0

override fun writeToParcel(dest: Parcel, flags: Int)
}

  这样就减少了对象中的 describeContents 方法的实现。然后我们再来优化CREATOR的实现方式。对此我写了下面的函数

1
2
3
4
5
6
inline fun <reified T> parcelableCreator(crossinline creator: (Parcel) -> T) =
object : Parcelable.Creator<T> {
override fun createFromParcel(source: Parcel): T = creator(source)

override fun newArray(size: Int): Array<T?> = arrayOfNulls(size)
}

  它接受一个名为 creator 的方法,然后返回一个 Parcelable.Creator 的实现类,这样我的Person类就可以简化为如下的方式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
data class Person(
var name: String?,
var sex: String?
) : KParcelable {

companion object {

@JvmField
val CREATOR: Parcelable.Creator<Person> = parcelableCreator(::Person)

}

constructor(parcel: Parcel) : this(
parcel.readString(),
parcel.readString())

override fun writeToParcel(dest: Parcel, flags: Int) = with(dest) {
writeString(name)
writeString(sex)
}

}

  这样看着就舒服多了,哈哈哈。

毒鸡汤

  生活不如意时是上帝给你放的长假~

本文标题:当Kotlin遇上Parcelable

文章作者:严方雄

发布时间:2018-01-24

最后更新:2018-09-13

原始链接:http://yanfangxiong.com/2018/01/24/当Kotlin遇上Parcelable/

0%