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 序列化与恢复流程图
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 保存。