通过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")
}

对比

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

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

相关推荐
三少爷的鞋6 小时前
AI 时代,我们都将成为通才型开发者:只懂 Android,已经不够了
android
Dovis(誓平步青云)10 小时前
家里设备越来越多,如何用一张空间地图控制灯光和温度![
android·java·前端·javascript·人工智能·电脑
晚风叙码12 小时前
MySQL 数据类型详解:从数值到字符串,一篇讲透
android·mysql·adb
传奇开心果编程12 小时前
【Compose Multiplatform 跨端开发学与练】第3课 布局与组件
android·windows·学习·ui·ios·kotlin·composer
传奇开心果编程15 小时前
【Compose Multiplatform 跨端开发学与练】第8课 资源管理与主题
android·windows·学习·ios·kotlin·web·composer
传奇开心果编程17 小时前
【Compose Multiplatform 跨端开发学与练】第9课 测试与调试
android·学习·macos·ios·kotlin·web·composer
传奇开心果编程17 小时前
【Compose Multiplatform 跨端开发学与练】第4课 导航与路由
android·windows·学习·ui·ios·kotlin·composer
传奇开心果编程17 小时前
【Compose Multiplatform 跨端开发学与练】第6课 状态管理与架构
android·学习·ui·ios·架构·kotlin·composer
事圆则缓17 小时前
Android AOSP 定制常见概念:源码目录、系统镜像与刷机流程
android
传奇开心果编程17 小时前
【Compose Multiplatform 跨端开发学与练】第2课 Compose 基础语法
android·windows·学习·ui·ios·kotlin·composer