8、LiveData与Room:LiveData简介、在DAO中返回LiveData、观察数据变化、生命周期感知
说实话,刚接触Android开发那会儿,我最头疼的就是数据更新后怎么通知UI。手动刷新?太low了。用回调?代码能写成蜘蛛网。直到我遇到了LiveData,这玩意儿简直就是为Room量身定做的。
LiveData是个观察者模式的实现,但它有个杀手锏——它知道Activity和Fragment的生命周期。这意味着什么?你不需要手动管理订阅和取消订阅,LiveData自己会搞定。我当年在做一个电商项目时,就因为忘记在onDestroy里取消注册,导致内存泄漏,查了两天bug。后来用了LiveData,再也没犯过这种错。
8.1 LiveData是什么?
LiveData是一个可观察的数据持有类。它遵循观察者模式,但和普通的观察者不同——它感知生命周期。
说白了,LiveData就是你的数据管家。数据变了,它会主动通知观察者。但如果你在后台,它就不打扰你。如果你被销毁了,它自动解除绑定。嗯,这才是我们想要的。
- 生命周期感知——自动管理订阅,不会在Activity销毁后还更新UI
- 数据驱动——数据变化自动通知,不需要手动刷新
- 无内存泄漏——观察者与LifecycleOwner绑定,自动清理
- 共享资源——单例模式的数据可以供多个组件观察
8.2 在DAO中返回LiveData
Room对LiveData的支持非常优雅。你只需要在DAO方法的返回类型上改成LiveData,Room会自动帮你把查询结果包装成可观察的数据流。
我在项目中遇到过这样一个场景:用户修改了个人资料,返回列表页时数据必须是最新的。如果用普通查询,你得手动刷新。但用LiveData,数据一变,UI自动更新。省心。
@Dao
interface UserDao {
@Query("SELECT * FROM users WHERE id = :userId")
fun getUserById(userId: Int): LiveData<User>
@Query("SELECT * FROM users ORDER BY name ASC")
fun getAllUsers(): LiveData<List<User>>
@Insert
suspend fun insertUser(user: User)
@Update
suspend fun updateUser(user: User)
@Delete
suspend fun deleteUser(user: User)
}
看到没?返回类型从原来的 User 变成了 LiveData<User>。就这么简单。Room会在后台自动监听数据库表的变化,一旦有增删改操作,就会触发LiveData的更新。
8.3 观察数据变化
有了LiveData,观察数据变化变得异常简单。在Activity或Fragment中,你只需要调用 observe() 方法。
class UserProfileActivity : AppCompatActivity() {
private lateinit var userViewModel: UserViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_user_profile)
userViewModel = ViewModelProvider(this).get(UserViewModel::class.java)
// 观察用户数据变化
userViewModel.getUserById(1).observe(this) { user ->
// 数据变化时自动回调
tvName.text = user.name
tvEmail.text = user.email
ivAvatar.load(user.avatarUrl)
}
}
}
你想想看,以前我们要写多少代码?先注册ContentObserver,再写回调,还要在onDestroy里反注册。现在一行 observe() 全搞定。
为什么会这样?因为LiveData内部帮我们处理了所有脏活累活。它会在Activity处于STARTED状态时激活观察者,在RESUMED时传递数据,在DESTROYED时自动清理。
8.4 生命周期感知
这是LiveData最牛的地方。它知道Activity和Fragment什么时候活着,什么时候死了。
我曾经接手过一个项目,里面全是手动管理回调的代码。有个Fragment在后台时收到了网络回调,结果更新UI时直接崩溃——因为视图已经销毁了。修复这种bug,你得在每个回调里判断 isAdded 或 isDestroyed。烦不烦?
LiveData直接解决了这个问题。它只会在LifecycleOwner处于活跃状态时通知观察者。活跃状态包括STARTED和RESUMED。如果Activity在后台(PAUSED或STOPPED),LiveData不会推送数据。等Activity回到前台,再推送最新的数据。
8.5 知识体系图
下面这张图展示了LiveData与Room协作的核心流程。我建议你仔细看看,理解数据是怎么从数据库流到UI的。
8.6 实战中的避坑指南
用LiveData + Room,有几个坑我踩过,分享给你:
- 不要在非UI线程观察LiveData——LiveData默认在主线程回调。如果你在子线程观察,会出问题。我一般只在Activity和Fragment里调用observe()。
- 避免在ViewModel中暴露MutableLiveData——对外暴露不可变的LiveData,内部用MutableLiveData。这是封装原则,我见过太多人直接暴露MutableLiveData,结果数据被外部乱改。
- 注意LiveData的粘性事件——如果你用LiveData做一次性事件(比如Snackbar提示),粘性事件会导致重复显示。我建议用Event包装类或者SingleLiveEvent。
- 大量数据更新时考虑去抖——如果数据库频繁写入,LiveData会频繁触发UI更新。我做过一个笔记应用,用户打字时实时保存,结果UI卡成PPT。后来加了500ms的去抖,问题解决。
private val _users = MutableLiveData<List<User>>()val users: LiveData<List<User>> get() = _users这样外部只能观察,不能修改。干净利落。
好了,LiveData和Room的配合就讲到这里。记住一句话:LiveData让数据观察变得简单,Room让数据持久化变得简单,两者结合,你的数据层就稳了。