4. 保存自定义对象——Serializable方式

4.1 原理概述

当 ViewModel 需要保存比基本类型更复杂的自定义数据模型(如用户信息、订单详情等)时,Android 的 SavedStateHandle 提供了一套基于序列化的存储机制。最简洁的方式是让自定义类实现 java.io.Serializable 接口,利用 Java 原生的序列化能力将对象转换为字节流,存储在 SavedStateHandle 中,并在进程重建时反序列化恢复。

其核心流程包含三个步骤:

  • 实现接口:自定义类实现 Serializable,并确保所有字段均可序列化。
  • 存储:通过 SavedStateHandle.set(key, value) 写入序列化对象。
  • 恢复与空安全处理:在 ViewModel 初始化或恢复时,通过 SavedStateHandle.get<T>(key) 获取,并处理可能为 null 的情况。

4.2 实现步骤与代码

4.2.1 自定义类实现 Serializable

import java.io.Serializable

data class UserProfile(
    val id: Long,
    val name: String,
    val email: String?,
    val age: Int
) : Serializable

注意事项:

  • 所有非静态内部类也必须实现 Serializable。
  • 字段类型推荐使用基本类型、String、或其它已实现 Serializable 的类。
  • 使用 @Transient 注解可标记不需要序列化的字段。

4.2.2 在 ViewModel 中存储和恢复

class ProfileViewModel(private val savedStateHandle: SavedStateHandle) : ViewModel() {

    companion object {
        private const val KEY_USER_PROFILE = "user_profile"
    }

    // 通过 Flow 暴露恢复后的数据(空安全处理)
    val userProfile: StateFlow<UserProfile?> = savedStateHandle
        .getStateFlow<UserProfile?>(KEY_USER_PROFILE, null)

    /**
     * 保存用户信息(每次更新时调用)
     */
    fun saveProfile(profile: UserProfile) {
        savedStateHandle[KEY_USER_PROFILE] = profile
    }

    /**
     * 获取当前保存的对象(可能为 null)
     */
    fun getSavedProfile(): UserProfile? {
        return savedStateHandle.get<UserProfile?>(KEY_USER_PROFILE)
    }
}

4.2.3 在 Fragment/Activity 中使用

class ProfileFragment : Fragment() {

    private val viewModel: ProfileViewModel by viewModels()

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        // 观察数据变化,安全处理空值
        viewModel.userProfile.observe(viewLifecycleOwner) { profile ->
            if (profile != null) {
                // 显示用户信息
                binding.nameTextView.text = profile.name
                binding.emailTextView.text = profile.email ?: "未设置"
            } else {
                // 显示默认状态
                binding.nameTextView.text = "未登录"
            }
        }

        // 模拟保存数据
        val testProfile = UserProfile(1L, "张三", "zhangsan@example.com", 28)
        viewModel.saveProfile(testProfile)
    }
}

4.3 Serializable 序列化与恢复流程图

Serializable 对象存储与恢复流程 自定义类实现 Serializable savedStateHandle[key] = obj Java 序列化为字节流
保存到 Bundle savedStateHandle[key]? 反序列化恢复对象
可能为 null 需要处理
观察或直接使用对象 进程重启后 提示:Serializable 属于 Java 原生且简单易用,但序列化性能较低,适合内部数据模型;大数据量建议改用 Parcelable 或 Kotlin Serialization。

4.4 空安全处理策略

由于进程被系统杀死后重建时 SavedStateHandle 中可能没有之前存入的数据,恢复时得到 null。因此必须对所有恢复操作进行空安全处理。常用方式:

策略适用场景代码示例
getStateFlow 提供默认值 配合 LiveData/Flow 观察 savedStateHandle.getStateFlow<UserProfile?>(KEY, null)
Elvis 运算符 ?: 提供备用数据 临时获取值,且需要默认值 val profile = savedStateHandle.get<UserProfile>(KEY) ?: UserProfile()
显式 null 检查 需要区分“未设置”与“默认值” if (profile != null) { ... } else { showEmptyView() }
最佳实践:推荐使用 getStateFlow 并指定默认值 null,在 UI 层统一观察并处理空状态。这样既能响应后续的更新操作,又能自然处理进程重建后的空数据。

4.5 常见错误与调试

  • NotSerializableException:自定义类中包含了不支持序列化的字段(如线程、Socket、未实现 Serializable 的内部类)。解决方法:检查所有字段类型,或者用 @Transient 排除。
  • ClassNotFoundException:进程重建后,反序列化时找不到定义的类(如混淆导致类名改变)。解决方法:在 ProGuard/R8 规则中保留序列化类的类名和 serialVersionUID。
  • 不同版本数据不兼容:修改类的字段后,旧版本序列化数据无法反序列化。解决方法:显式声明 serialVersionUID,并在字段变更时手动更新。
调试技巧:在 ViewModel 的 onCleared() 或 Fragment 的 onSaveInstanceState 中手动检查 SavedStateHandle 内已保存的 key 列表,确认数据已持久化:
savedStateHandle.keys().forEach { key -> Log.d("SavedState", "$key -> ${savedStateHandle.get<Any>(key)}") }

4.6 性能与稳定性优化

关注点优化建议
序列化大小避免在 Serializable 对象中包含大数据(如 Bitmap 转字节数组),建议只存储轻量级结构数据。
反序列化频率仅在进程重建或首次创建 ViewModel 时触发一次,不要频繁写入和读取 SavedStateHandle。
类演化始终为类定义 private static final long serialVersionUID = 1L;,确保不同版本兼容。
混淆配置在 proguard-rules.pro 中添加:
-keep class com.example.model.UserProfile { *; }
注意:Serializable 序列化的对象会被持久化到磁盘(进程杀死后由系统保存),但 SavedStateHandle 本身有一定的容量限制(约 1MB)。如果存储的数据过大,建议改用 Room 等持久化方案,而仅将关键导航或 UI 状态通过 Serializable 保存。