从 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,因此:
    • ScreenAhiltViewModel()NavBackStackEntryA 获取 ViewModel。
    • ScreenBhiltViewModel()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>()
    // ...
}

运行后会发现:

  • 如果 screenAscreenB 是不同的目的地,它们的 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 的作用域。

相关推荐
程序员陆业聪6 小时前
绕过Frida/Xposed的最后防线:SVC直接系统调用与Native反Hook实战
android
程序员陆业聪6 小时前
WebView与原生JS交互:JSBridge生产级实现与安全防护
android
我命由我123459 小时前
Android 开发问题:MlKitException: An internal error occurred during initialization.
android·java·java-ee·android jetpack·android-studio·androidx·android runtime
Meteors.9 小时前
Android自定义 View 三核心方法详解
android
2501_916007479 小时前
前端开发常用软件与工具全面指南
android·ios·小程序·https·uni-app·iphone·webview
赏金术士10 小时前
Android Tinker 热修复集成与使用指南 1.9.15.2
android·热修复·tinker
2603_9541383911 小时前
安卓误删文件先别慌!5个实用小技巧指南教你补救
android·智能手机
波诺波13 小时前
5-SOFA可变形的3D物体 5-elasticity.scn
android
2501_9159090614 小时前
iOS应用性能优化:十大策略提升用户体验与开发效率
android·ios·小程序·https·uni-app·iphone·webview
sun00770014 小时前
打通android全链路,网卡驱动, 内核 , 到上层hal, framework
android