【发布时间】: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