通过Intent传递自定义对象的两种方式

前言

我们经常会使用 Intent 来启动 Activity、发送广播等。在进行上述操作的过程中,我们还可以往 Intent 对象中添加额外的数据,比如:

kotlin 复制代码
// MainActivity.kt
val intent = Intent(this, AnotherActivity::class.java)
intent.putExtra("name", "Martin")
intent.putExtra("age", 21)
startActivity(intent)

以此来完成数据的传递:

kotlin 复制代码
// AnotherActivity.kt
val name = intent.getStringExtra("name") ?: "Unknown Name"
val age = intent.getIntExtra("age", -1)

但问题在于 putExtra 可传递的数据类型是有限的,如果你想传递自定义对象,就行不通 了。但请别担心,我们接下来就来学习如何通过 Intent 传递自定义对象。

方式一:Serializable

Serializable 翻译过来是序列化的意思,表示将一个对象转换为可存储或可传输的状态,序列化后的对象可被存储到本地,也可在网络上进行传输。

序列化一个类,其实非常简单,只需让这个类实现 Serializable 接口即可。例如:

kotlin 复制代码
class Person : Serializable {
    var name = ""
    var age = 0
}

这样,所有的 Person 对象都是可序列化的了。现在,我们想要传递一个 Person 对象,只需这样:

kotlin 复制代码
val person = Person()
person.name = "Jack"
person.age = 21
val intent = Intent(this, AnotherActivity::class.java)
intent.putExtra("person", person)
startActivity(intent)

那么,该怎么从 Intent 中取出这个 Person 对象呢?

答案是使用 getSerializableExtra() 方法获取序列化对象,再向下转型为 Person 对象即可。

kotlin 复制代码
// val person_from_intent: Person = intent.getSerializableExtra("person") as Person

不过,这个方法已经被废弃了。原因在于它不能保证类型安全 ,如果获取到的不是 Person 类型的对象,又或是键名拼写错误导致返回 null,那么在运行时,as 强制类型转换可能会失败并导致程序崩溃。

所以,现在我们会使用更安全 的重载方法 getSerializableExtra(String, Class<T>)。这个方法是在 Android 13 (API 33) 引入的,为了兼容所有版本,我们需要这样写:

kotlin 复制代码
val person: Person? = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
    intent.getSerializableExtra("person", Person::class.java)
} else {
    // 使用 as? 安全转型来避免崩溃
    @Suppress("DEPRECATION")
    intent.getSerializableExtra("person") as? Person
}

这样,我们就成功实现了通过 Intent 来传递自定义对象。

最后注意一点:在这个过程中,会将对象序列化为可存储或可传输的状态,再将其反序列化为一个新的对象。虽然这两个对象存储的数据一致,但其实是两个不相同的对象。

方式二:Parcelable

我们也可以通过 Parcelable 来完成。只不过它的实现原理是将完整的对象进行分解,使得分解后的每一个部分都是 Intent 所支持的数据类型,这样也能实现通过 Intent 来传递自定义对象的功能。

如果使用 Parcelable 的实现方式,Person 类的代码将会是这样的:

kotlin 复制代码
class Person(var name: String?, var age: Int) : Parcelable {

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

    override fun writeToParcel(parcel: Parcel, flags: Int) {
        // 将当前类中的字段写出
        parcel.writeString(name)
        parcel.writeInt(age)
    }

    override fun describeContents(): Int {
        return 0
    }
    
    // 注意:读取字段的顺序需要和写出字段的顺序保持一致。
    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)
        }
    }
}

可以看到,手动编写这些模版代码非常繁琐。为此,Kotlin 提供了一种更加简便的用法。

我们先在 app/build.gradle.kts 文件中添加插件依赖:

kotlin 复制代码
plugins { 
    id("kotlin-parcelize")
}

然后,修改 Person 类:

kotlin 复制代码
import kotlinx.parcelize.Parcelize

@Parcelize
data class Person(var name: String, var age: Int) : Parcelable

只需加上一个 @Parcelize 注解,就可以省去了一大堆模板代码的麻烦。

注意:使用 @Parcelize 时,所有要传递的数据都必须在类的主构造函数中声明。

接下来,在获取 Intent 中的数据时,只需使用 getParcelableExtra() 方法即可。同样地,需要处理版本兼容问题:

kotlin 复制代码
val person: Person? = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
    intent.getParcelableExtra("person", Person::class.java)
} else {
    @Suppress("DEPRECATION")
    intent.getParcelableExtra<Person>("person")
}

对比

对比 SerializableParcelable 这两种方式,Serializable 的实现非常简单,但它序列化时会用到反射 ,导致性能开销大。Parcelable 则通过显式的读写操作来分解对象,没有反射的开销,效率要高得多。

因此,在安卓开发中,通常情况下,我们都会选择使用 Parcelable 的方式。

相关推荐
CANI_PLUS9 小时前
ESP32将DHT11温湿度传感器采集的数据上传到XAMPP的MySQL数据库
android·数据库·mysql
来来走走10 小时前
Flutter SharedPreferences存储数据基本使用
android·flutter
安卓开发者11 小时前
Android模块化架构深度解析:从设计到实践
android·架构
雨白11 小时前
HTTP协议详解(二):深入理解Header与Body
android·http
阿豪元代码12 小时前
深入理解 SurfaceFlinger —— 如何调试 SurfaceFlinger
android
阿豪元代码12 小时前
深入理解 SurfaceFlinger —— 概述
android
CV资深专家13 小时前
Launcher3启动
android
stevenzqzq14 小时前
glide缓存策略和缓存命中
android·缓存·glide
雅雅姐14 小时前
Android 16 的用户和用户组定义
android
没有了遇见14 小时前
Android ConstraintLayout 之ConstraintSet
android