手动清除Android ViewModel?

问题描述 投票:17回答:5

参考android.arch.lifecycle.ViewModel类。

ViewModel的范围涉及它所涉及的UI组件的生命周期,因此在基于Fragment的应用程序中,这将是片段生命周期。这是一件好事。


在某些情况下,人们想要在多个片段之间共享ViewModel实例。具体而言,我对许多屏幕与相同基础数据相关的情况感兴趣。

(当多个相关片段显示在同一屏幕上但this can be worked around by using a single host fragment as per answer below时,文档提出了类似的方法。)

这在official ViewModel documentation中讨论过:

ViewModels还可以用作活动的不同片段之间的通信层。每个Fragment都可以通过其Activity使用相同的密钥获取ViewModel。这允许以解耦方式在片段之间进行通信,使得它们永远不需要直接与其他片段对话。

换句话说,要在表示不同屏幕的片段之间共享信息,ViewModel应该限定为Activity生命周期(根据Android文档,这也可以在其他共享实例中使用)。


现在,在新的Jetpack导航模式中,建议使用“One Activity / Many Fragments”架构。这意味着活动在应用程序使用的整个过程中都存在。

即任何限定为ViewModel生命周期的共享Activity实例永远不会被清除 - 内存仍然在不断使用。

为了保留内存并在任何时间点使用尽可能少的内容,能够在不再需要时清除共享的ViewModel实例会很好。


如何从它的ViewModel或持有者片段手动清除ViewModelStore

android android-architecture-components android-jetpack android-viewmodel
5个回答
5
投票

如果你检查代码here你会发现,你可以从ViewModelStoreViewModelStoreOwnerFragment获得FragmentActivity,例如,实现,接口。

从那里你可以打电话给viewModelStore.clear(),正如文件所说:

 /**
 *  Clears internal storage and notifies ViewModels that they are no longer used.
 */
public final void clear() {
    for (ViewModel vm : mMap.values()) {
        vm.clear();
    }
    mMap.clear();
}

N.B。:这将清除特定LifeCycleOwner的所有可用ViewModel,这不允许您清除一个特定的ViewModel。


2
投票

如果您不希望将ViewModel限定为Activity生命周期,则可以将其范围限定为父片段的生命周期。因此,如果要在屏幕中与多个片段共享ViewModel的实例,则可以布置片段,使它们共享一个共同的父片段。这样,当您实例化ViewModel时,您可以这样做:

CommonViewModel viewModel = ViewModelProviders.of(getParentFragment()).class(CommonViewModel.class);

希望这有帮助!


2
投票

我想我有更好的解决方案。

正如@Nagy Robi所说,你可以通过电话ViewModel清除viewModelStore.clear()。这个问题是它将清除此ViewModelStore范围内的所有视图模型。换句话说,你无法控制清除哪个ViewModel

但根据@mikehc here。我们实际上可以创建我们自己的ViewModelStore。这将允许我们精确控制ViewModel必须存在的范围。

注意:我没有看到有人这样做,但我希望这是一个有效的方法。这将是控制单个活动应用程序中的范围的一种非常好的方法。

请提供一些有关此方法的反馈。任何事情都会受到赞赏。

Update:

Navigation Component v2.1.0-alpha02开始,ViewModels现在可以用于流量。这样做的缺点是你必须为你的项目实施Navigation Component,而且你对ViewModel的范围没有任何粒度控制。但这似乎是一件好事。


1
投票

我只是写文库来解决这个问题:scoped-vm,随时查看它,我将非常感谢任何反馈。在引擎盖下,它使用了@Archie提到的方法 - 它为每个范围维护单独的ViewModelStore。但是,只要请求来自该范围的viewmodel的最后一个片段破坏,它就会更进一步清除ViewModelStore本身。

我应该说,目前整个视图模型管理(特别是这个lib)受到带有backstack的serious bug的影响,希望它将被修复。

摘要:

  • 如果你关心ViewModel.onCleared()没有被调用,最好的方法(现在)是自己清除它。由于这个bug,你无法保证fragment的viewmodel将被清除。
  • 如果你只是担心泄漏ViewModel - 不用担心,它们将被垃圾收集为任何其他非引用对象。如果它适合您的需要,请随意使用我的lib进行细粒度范围界定。

1
投票

正如指出的那样,使用体系结构组件API无法清除ViewModelStore的单个ViewModel。此问题的一种可能解决方案是拥有可在必要时安全清除的每个ViewModel存储:

class MainActivity : AppCompatActivity() {

val individualModelStores = HashMap<KClass<out ViewModel>, ViewModelStore>()

inline fun <reified VIEWMODEL : ViewModel> getSharedViewModel(): VIEWMODEL {
    val factory = object : ViewModelProvider.Factory {
        override fun <T : ViewModel?> create(modelClass: Class<T>): T {
            //Put your existing ViewModel instantiation code here,
            //e.g., dependency injection or a factory you're using
            //For the simplicity of example let's assume
            //that your ViewModel doesn't take any arguments
            return modelClass.newInstance()
        }
    }

    val viewModelStore = [email protected]<VIEWMODEL>()
    return ViewModelProvider(this.getIndividualViewModelStore<VIEWMODEL>(), factory).get(VIEWMODEL::class.java)
}

    val viewModelStore = [email protected]<VIEWMODEL>()
    return ViewModelProvider(this.getIndividualViewModelStore<VIEWMODEL>(), factory).get(VIEWMODEL::class.java)
}

inline fun <reified VIEWMODEL : ViewModel> getIndividualViewModelStore(): ViewModelStore {
    val viewModelKey = VIEWMODEL::class
    var viewModelStore = individualModelStores[viewModelKey]
    return if (viewModelStore != null) {
        viewModelStore
    } else {
        viewModelStore = ViewModelStore()
        individualModelStores[viewModelKey] = viewModelStore
        return viewModelStore
    }
}

inline fun <reified VIEWMODEL : ViewModel> clearIndividualViewModelStore() {
    val viewModelKey = VIEWMODEL::class
    individualModelStores[viewModelKey]?.clear()
    individualModelStores.remove(viewModelKey)
}

}

使用getSharedViewModel()获取绑定到Activity生命周期的ViewModel实例:

val yourViewModel : YourViewModel = (requireActivity() as MainActivity).getSharedViewModel(/*There could be some arguments in case of a more complex ViewModelProvider.Factory implementation*/)

稍后,当处理共享的ViewModel时,请使用clearIndividualViewModelStore<>()

(requireActivity() as MainActivity).clearIndividualViewModelStore<YourViewModel>()

在某些情况下,如果不再需要ViewModel,则需要尽快清除ViewModel(例如,如果它包含一些敏感的用户数据,如用户名或密码)。这是一种在每个片段切换时记录individualModelStores状态的方法,以帮助您跟踪共享的ViewModel:

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    if (BuildConfig.DEBUG) {
        navController.addOnDestinationChangedListener { _, _, _ ->
            if (individualModelStores.isNotEmpty()) {
                val tag = [email protected]
                Log.w(
                        tag,
                        "Don't forget to clear the shared ViewModelStores if they are not needed anymore."
                )
                Log.w(
                        tag,
                        "Currently there are ${individualModelStores.keys.size} ViewModelStores bound to ${[email protected]}:"
                )
                for ((index, viewModelClass) in individualModelStores.keys.withIndex()) {
                    Log.w(
                            tag,
                            "${index + 1}) $viewModelClass\n"
                    )
                }
            }
        }
    }
}

-3
投票

通常,您不会手动清除ViewModel,因为它是自动处理的。如果您觉得需要手动清除ViewModel,那么您可能在ViewModel中做了太多...

使用多个视图模型没有任何问题。第一个可以作为活动的范围,而另一个可以作用于片段。

尝试仅将Activity范围的Viewmodel用于需要共享的内容。并在Fragment Scoped Viewmodel中放置尽可能多的东西。片段被销毁时,将清除Fragment范围的视图模型。减少整体内存占用。

© www.soinside.com 2019 - 2024. All rights reserved.