5、ViewModel KTX:viewModelScope与by viewModels()委托,优雅管理生命周期
ViewModel 这个东西,说白了就是帮你在屏幕旋转时保住那点数据。我早年做 Android 开发时,最头疼的就是 Activity 重建——用户转个手机,输入框里的内容全没了,气得想摔手机。后来 Google 推出了 ViewModel,算是把这个问题给治了。
但光有 ViewModel 还不够。你想想看,每次都要手动写 ViewModelProvider,还要处理协程的生命周期,代码量不小。KTX 扩展库就是来解决这些痛点的。今天咱们就聊聊 ViewModel KTX 里的两个核心工具:viewModelScope 和 by viewModels() 委托。
5.1 为什么需要 ViewModel KTX?
先看一段原生写法,感受一下:
// 没有 KTX 的日子
class MyActivity : AppCompatActivity() {
private lateinit var viewModel: MyViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewModel = ViewModelProvider(this).get(MyViewModel::class.java)
}
}
这段代码有什么问题?嗯,其实也没大毛病,就是啰嗦。每次都要写 ViewModelProvider,而且容易忘记初始化。更麻烦的是协程管理——如果你在 ViewModel 里启动协程,得自己处理取消逻辑。
我个人习惯用 KTX 之后,再也不想回到这种写法了。它带来的不仅是代码量减少,更重要的是——帮你规避了生命周期管理的坑。
5.2 by viewModels() 委托:告别模板代码
Kotlin 的委托属性在这里派上了大用场。by viewModels() 本质上是一个属性委托,它会在你第一次访问这个属性时,自动创建 ViewModel 实例。
// 使用 KTX 委托
class MyActivity : AppCompatActivity() {
val viewModel: MyViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 直接用,不用管初始化
viewModel.loadData()
}
}
简洁吧?一行搞定。而且它内部已经处理好了 ViewModel 的缓存和生命周期绑定。你在 Fragment 里也能用:
class MyFragment : Fragment() {
// 获取 Activity 范围内的 ViewModel,适合多个 Fragment 共享数据
val sharedViewModel: SharedViewModel by viewModels(
ownerProducer = { requireActivity() }
)
// 获取 Fragment 自己的 ViewModel
val myViewModel: MyViewModel by viewModels()
}
by viewModels(),默认是 Fragment 作用域。想跟兄弟 Fragment 共享数据?记得传 ownerProducer = { requireActivity() }。
我曾经在项目里遇到过一个坑:两个 Fragment 各自用 by viewModels() 获取同一个 ViewModel 类,结果数据不共享。排查了半天才发现,它们各自创建了不同的实例。后来改成 by activityViewModels() 才解决。
5.3 viewModelScope:协程的自动取消
协程在 ViewModel 里用得很频繁——网络请求、数据库操作、耗时计算。但有个问题:ViewModel 被清除时,这些协程还在跑怎么办?
原生写法你得这样:
class MyViewModel : ViewModel() {
private var job: Job? = null
fun loadData() {
job = CoroutineScope(Dispatchers.IO).launch {
// 网络请求...
}
}
override fun onCleared() {
super.onCleared()
job?.cancel() // 手动取消,容易忘
}
}
说实话,我刚开始写的时候经常忘记在 onCleared() 里取消协程。结果就是 Activity 销毁了,网络请求还在后台跑,甚至可能回调时操作已销毁的 View,直接崩掉。
viewModelScope 就是来解决这个问题的。它是 ViewModel 的一个扩展属性,绑定的是 Dispatchers.Main,并且会在 ViewModel 的 onCleared() 时自动取消所有子协程。
class MyViewModel : ViewModel() {
fun loadData() {
viewModelScope.launch {
// 自动绑定生命周期,不用手动取消
val result = repository.fetchData()
_liveData.value = result
}
}
}
这里有个细节要注意:viewModelScope 默认使用 Dispatchers.Main。如果你要做 IO 操作,记得切线程:
viewModelScope.launch {
// 主线程
showLoading(true)
// 切到 IO 线程
val data = withContext(Dispatchers.IO) {
repository.fetchDataFromNetwork()
}
// 回到主线程更新 UI
showLoading(false)
_liveData.value = data
}
5.4 实战:一个完整的例子
把上面两个工具结合起来,写一个用户信息加载的 ViewModel:
class UserViewModel : ViewModel() {
private val _user = MutableLiveData<User?>()
val user: LiveData<User?> = _user
private val _loading = MutableLiveData<Boolean>()
val loading: LiveData<Boolean> = _loading
fun loadUser(userId: String) {
viewModelScope.launch {
_loading.value = true
try {
val result = withContext(Dispatchers.IO) {
userRepository.getUser(userId)
}
_user.value = result
} catch (e: Exception) {
_user.value = null
} finally {
_loading.value = false
}
}
}
}
// Activity 中使用
class UserActivity : AppCompatActivity() {
private val viewModel: UserViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewModel.user.observe(this) { user ->
// 更新 UI
showUserInfo(user)
}
viewModel.loadUser("12345")
}
}
这段代码里,by viewModels() 帮我们省去了 ViewModelProvider 的模板代码,viewModelScope 保证了协程在 ViewModel 销毁时自动取消。你想想看,如果不用 KTX,同样的功能你得写多少行?
5.5 避坑指南
用这两个工具时,有几个坑我踩过,分享给你:
- 不要在 viewModelScope 里启动全局协程——比如用
GlobalScope.launch,那生命周期就失控了。 - by viewModels() 只能在 Activity/Fragment 里用——不能在自定义 View 或普通类里用,因为需要 LifecycleOwner。
- Fragment 里用 by viewModels() 默认是 Fragment 作用域——想共享数据记得用
by activityViewModels()。 - viewModelScope 的协程默认跑在主线程——做耗时操作一定要切线程,否则会 ANR。
viewModelScope.launch 默认是 Dispatchers.Main,IO 操作必须用 withContext(Dispatchers.IO)。
5.6 知识体系总览
下面这张图帮你理清 ViewModel KTX 的核心逻辑:
说白了,ViewModel KTX 就是帮你把那些重复的、容易出错的生命周期管理代码给封装掉了。你只需要关注业务逻辑,剩下的交给 KTX 去处理。
我个人觉得,by viewModels() 和 viewModelScope 是 Jetpack KTX 里最实用的两个工具。它们不改变你的架构,只是让代码写起来更舒服、更安全。如果你还没用上,我建议你从下一个项目开始就引入它们——相信我,你会爱上这种简洁的写法。