【问题标题】:Dagger-Android: ViewModel not destroyed when fragment destroyedDagger-Android:片段销毁时 ViewModel 未销毁
【发布时间】:2022-01-03 17:48:14
【问题描述】:

在我的项目中,我使用 Dagger2 将 ViewModels 注入片段。

  override val viewModel: AllStockListTabViewModel by viewModels ({this}, {viewModelFactory})

为了简要解释我的情况,我有一个使用片段状态适配器的片段,其中包含两个片段。为方便起见,我将片段状态适配器片段 B 和片段 C 中的父片段片段 A 和子片段称为。

通常,在测试应用程序时,用户会在包含 recyclerview 的片段 B 中花费时间。当用户点击其中一个项目时,它会导致一个带有一些详细信息的不同片段。当用户输入该详细信息片段时,持有该项目的片段 B 将通过 onPause() 和 onStop()。同时在片段C中调用了onStop()。

关键是,如果用户在片段 B(由片段 A 包含)中花费了足够的时间,片段 C 将被销毁,这并不奇怪,因为我知道这是片段状态适配器的意图。它应该在不可见时摆脱一些碎片。

我的问题是,当片段 C 被破坏时,与之关联的视图模型不会被破坏。 这很糟糕,因为现在当用户转到片段 C 时,它仍然引用旧的视图模型,app不向片段提供任何数据,因为当调用 onDestroy() 时,片段 C 的视图模型被清除,因此 viewmodelscope.launch 不起作用。

我也想过不使用 viewmodelscope(改用 coroutinescope),但 这不是问题。 我很好奇和渴望知道为什么片段 C 的视图模型,范围为片段 C 的生命周期没有被破坏。(我想在片段 C 的消亡时摆脱旧的视图模型并获得新的视图模型实例)

请理解我笨拙的措辞和我缺乏知识可能会造成一些混乱。我是匕首新手。请参阅下面的代码以更好地理解。

AppComponent.kt

@Singleton
@Component(
  modules = [
    AndroidSupportInjectionModule::class,
    ActivityBindingModule::class,
    RepositoryModule::class,
    DataSourceModule::class,
    ServiceModule::class,
    DaoModule::class,
    ViewModelModule::class,
  ]
)

ViewModelModule.kt

@MapKey
@Target(
  AnnotationTarget.FUNCTION,
  AnnotationTarget.PROPERTY_GETTER,
  AnnotationTarget.PROPERTY_SETTER
)
annotation class ViewModelKey(val value: KClass<out ViewModel>)

@Module
abstract class ViewModelModule {

  @Binds
  @IntoMap
  @ViewModelKey(AllStockListTabViewModel::class)
  abstract fun bindAllStockListTabViewModel(allStockListTabViewModel: AllStockListTabViewModel): ViewModel
}

ViewModelFactory

@Singleton
class ViewModelFactory @Inject constructor(
  private val viewModelMap: Map<Class<out ViewModel>, @JvmSuppressWildcards Provider<ViewModel>>
) : ViewModelProvider.Factory {
  override fun <T : ViewModel?> create(modelClass: Class<T>): T {
    return viewModelMap[modelClass]?.get() as T
  }
}

片段

class AllStockListTabFragment @Inject constructor() :
  ViewModelFragment<FragmentAllStockListBinding>(R.layout.fragment_all_stock_list) {

  @Inject
  lateinit var viewModelFactory: ViewModelFactory

  override val viewModel: AllStockListTabViewModel by viewModels ({this}, {viewModelFactory})
}

适配器

    tradingTabAdapter = TradingTabAdapter(
      this.childFragmentManager,
      this.lifecycle,
      tradingStateTabFragment,
      allStockListTabFragment
    )
class TradingTabAdapter @Inject constructor(
  fragmentManager: FragmentManager,
  lifecycle: Lifecycle,
  private val tradingStateTabFragment: TradingStateTabFragment,
  private val allStockListTabFragment: AllStockListTabFragment
) : FragmentStateAdapter(fragmentManager, lifecycle) {

  override fun createFragment(position: Int): Fragment =
    when (position) {
      0 -> tradingStateTabFragment
      else -> allStockListTabFragment
    }

  override fun getItemCount(): Int = 2
}

子组件

@FragmentScope
@Subcomponent(
  modules = [
    TradingTabBindingModule::class,
    TradingTabModule::class,
    EventModule::class,
    UseCaseModule::class
  ]
)

适配器模块

@Module
class TradingTabModule {

  @Provides
  fun provideTradingTabAdapter(
    fragment: TradingTabFragment,
    allStockListTabFragment: AllStockListTabFragment,
    tradingStateTabFragment: TradingStateTabFragment
  ) = TradingTabAdapter(
    fragment.childFragmentManager,
    fragment.lifecycle,
    tradingStateTabFragment,
    allStockListTabFragment
  )

我发现当片段C被销毁并再次创建时,ViewModelFactory的create方法没有被调用。我认为这是因为我正在使用 viewmodel 的延迟初始化,这就是 ViewModelLazy 的工作方式。它缓存视图模型并仅在缓存为空时调用工厂的创建方法。我猜发生的事情是片段 C 的旧视图模型仍在引用死视图模型(它在 viewModelStore.onclear 中幸存下来)。我在片段 C 的视图模型的 init 块中放置了一条日志语句,我可以看到它仅在片段 C 的第一次创建时被调用,并且即使片段 C 被销毁并再次创建,也不会再次调用它。

非常感谢您耐心阅读所有这些哈哈。因此,我需要任何有经验的 Android 专家的帮助,他们可能能够提供一些见解。

我的目标:使用片段的生命周期销毁和重新创建视图模型。我想避免由于未使用的僵尸视图模型而导致内存泄漏。

现状: viewmodel 永远不会被销毁,重生的 Fragment 仍然引用旧的 viewmodel,因此延迟初始化会保留旧的 viewmodel 的缓存,不会触发 ViewModelFactory 的 create 方法。

--编辑--

我使用的匕首版本 “com.google.dagger:dagger-android:2.37”

【问题讨论】:

  • 为什么你的片段有@Inject 构造函数,如何在适配器中创建片段以及如何将viewModelFactory 注入片段?将您的适配器添加到问题中
  • @IR42 嘿,感谢您的评论。我刚刚为适配器添加了代码。我真的不需要 Inject 构造函数,但我在其中添加了我正在构建的应用程序的约定。有些片段需要 Inject 构造函数作为参数。
  • 从适配器构造函数中删除片段并在每次调用时在 createFragment 中创建它们
  • @IR42 好的,所以我从构造函数中删除了片段参数并在 createFragment 中创建了这些片段,但仍然不走运。感谢您的建议。为什么您认为这可能会导致问题?

标签: android kotlin viewmodel dagger-2 fragmentstateadapter


【解决方案1】:

由于您的 ViewModel 与您的 Activity 绑定,因此在 Fragment 被销毁时它不会被销毁。

@ViewModelKey(MainActivityViewModel::class)
abstract fun bindMainActivityViewModel(mainActivityViewModel: MainActivityViewModel): ViewModel

您可以查看解释如何将 ViewModel 与 Fragment 一起使用的答案。

How to use ViewModel in a fragment?

【讨论】:

  • 感谢您的帮助。我刚刚编辑了我的帖子。我认为我的视图模型与片段相关。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-07-18
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多