从 NavBackStackEntry 看 Compose 导航的 ViewModel 生命周期管理

测试:小伙子,怎么你的App不根据角度,进行横竖屏切换了。上个版本还没事呢?

bug介绍

有个数据源,是单例模式, 使用的接口回调的方式,更新Viewmodel里的数据,ViewModel实现更新数据的接口,viewModel 实例化的时候,将实例通过set方法,注入到数据源里。 我在NavHost通过hiltViewModel外边实例化了一个接收角度的viewModel,用来控制屏幕方向,横屏和竖屏切换 在Navhost里首页里,用hiltViewModel 实例化了一个相同的viewModel,用来显示实时的角度。

我在Compose里是用hiltViewModel()方法实例化,第一个版本,是没用Compose的Navgation,是没有问题的。viewModel只有一个实例,所有数据更新会跟着更新。

用了Navgation之后,数据就不会更新了,疯狂挠头。debug了半天,最后打印了个日志,发现。viewModel的init方法走了两遍。

在 Navigation Compose 中,ViewModelStore 是由 NavBackStackEntry 实例化的 ,而 NavBackStackEntry 是导航库用来管理每个目的地(destination)生命周期的核心组件。下面详细解释它的创建和管理机制:


1. ViewModelStore 的实例化时机

ViewModelStore 是由 NavBackStackEntry 在初始化时创建的:

  • 当导航到一个新目的地时,Navigation 库会创建一个新的 NavBackStackEntry。
  • NavBackStackEntry 内部持有一个 ViewModelStore,用于管理该目的地的 ViewModel 实例。
  • 这个 ViewModelStore 会跟随 NavBackStackEntry 的生命周期 (即当目的地被移除时,ViewModelStore 也会被清除)。

关键代码逻辑(简化版)

kotlin 复制代码
// NavBackStackEntry 的简化实现
class NavBackStackEntry(
    // ...
) : ViewModelStoreOwner {  // 实现了 ViewModelStoreOwner 接口
    private val viewModelStore = ViewModelStore()  // 在这里实例化

    override fun getViewModelStore(): ViewModelStore {
        return viewModelStore
    }

    // 当该 Entry 被销毁时,清理 ViewModelStore
    fun destroy() {
        viewModelStore.clear()
    }
}

当调用 navController.navigate("route") 时:

  1. Navigation 库会检查目标目的地是否需要新建 NavBackStackEntry。
  2. 如果是新目的地,就会创建一个新的 NavBackStackEntry,并初始化它的 ViewModelStore。
  3. 该 NavBackStackEntry 会被压入返回栈(BackStack)。

当用户按返回键或调用 navController.popBackStack():

  1. Navigation 库会从返回栈弹出最顶层的 NavBackStackEntry。
  2. 调用 NavBackStackEntry.destroy(),进而触发 viewModelStore.clear(),释放所有关联的 ViewModel。

3. 为什么不同目的地默认不共享 ViewModelStore?

  • 设计目标 :Navigation 库希望每个目的地有独立的状态管理,避免内存泄漏或状态污染。
  • 默认行为 :每个 NavBackStackEntry 持有自己的 ViewModelStore,因此:
    • ScreenA 的 hiltViewModel() 从 NavBackStackEntryA 获取 ViewModel。
    • ScreenB 的 hiltViewModel() 从 NavBackStackEntryB 获取 ViewModel。
    • 两者默认不是同一个实例。

4. 如何验证 ViewModelStore 的作用域?

可以通过以下方式观察 ViewModelStore 的归属:

kotlin 复制代码
composable("screenA") {
    val backstackEntry = LocalViewModelStoreOwner.current as NavBackStackEntry
    Log.d("NAV_DEBUG", "ScreenA ViewModelStore: ${backstackEntry.viewModelStore}")

    val vm = hiltViewModel<SharedViewModel>()
    // ...
}

composable("screenB") {
    val backstackEntry = LocalViewModelStoreOwner.current as NavBackStackEntry
    Log.d("NAV_DEBUG", "ScreenB ViewModelStore: ${backstackEntry.viewModelStore}")

    val vm = hiltViewModel<SharedViewModel>()
    // ...
}

运行后会发现:

  • 如果 screenA 和 screenB 是不同的目的地,它们的 ViewModelStore 是不同的对象。
  • 如果它们属于同一个 navigation 导航图,则可能共享同一个 ViewModelStore。

5. 共享 ViewModelStore 的几种方式

kotlin 复制代码
NavHost(navController, "main") {
    navigation(startDestination = "screenA", route = "feature") {
        composable("screenA") { /* 共享 ViewModelStore */ }
        composable("screenB") { /* 共享 ViewModelStore */ }
    }
}

(2) 手动指定 ViewModelStoreOwner

kotlin 复制代码
val parentEntry = remember { navController.getBackStackEntry("parent_route") }
val vm = hiltViewModel<SharedViewModel>(parentEntry)

(3) 使用 Activity 作用域(全局共享)

kotlin 复制代码
val activity = LocalContext.current as ComponentActivity
val vm = hiltViewModel<SharedViewModel>(activity)

总结

关键点 说明
ViewModelStore 由谁创建? NavBackStackEntry 在初始化时创建
默认作用域 每个导航目的地(NavBackStackEntry)有自己的 ViewModelStore
如何共享? 使用同一导航图、手动指定 ViewModelStoreOwner 或使用 Activity 作用域
何时销毁? 当 NavBackStackEntry 被移除时,ViewModelStore 会调用 clear()

这种设计确保了 Compose Navigation 的灵活性和内存安全性,同时允许开发者按需控制 ViewModel 的作用域。

相关推荐
用户607097591490628 分钟前
AOSP 14 OTA 升级指导(三):三条升级链路——Java API、自研 HAL 与 TR069
android
前端小D2 小时前
React Native Android 项目搭建
android·react native·react.js
恋猫de小郭2 小时前
感人,Flutter 居然有了一个超实用提案,期待落地
android·前端·flutter
CS_Zero3 小时前
安卓开发:Linux的android工具命令
android·linux·运维
fellow994 小时前
DSH Desktop - Android Edition
android·agent·deepseek·harness·dsh
Carson带你学Android4 小时前
Android Studio Rabbit 2 发布:除了Gemini,40+ AI Agent 正式入驻!
android·ai编程
三少爷的鞋16 小时前
Java 线程自上而下,Kotlin 协程自下而上
android
好的佩奇18 小时前
Dart 之增强枚举
android·flutter·dart
梦影_18 小时前
MySQL进阶教程(五)—— SQL调优(下)
android·大数据·数据库·后端·sql·mysql·database
好的佩奇21 小时前
蓝牙之无线电与通信距离
android·flutter·蓝牙