手动清除Android ViewModel吗?

时间:2018-12-06 14:04:43

标签: android android-architecture-components android-jetpack android-viewmodel

关于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也可以用作不同层之间的通信层   活动的片段。每个片段都可以获取ViewModel   通过他们的活动使用相同的键。这样可以进行交流   在片段之间以解耦的方式,这样它们就永远不需要   直接与另一个Fragment通话。

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


现在,在新的Jetpack导航模式中,建议使用“一个活动/很多片段”架构。这意味着该活动在应用程序的整个使用过程中一直存在。

即范围为ViewModel生命周期的任何共享Activity实例都不会被清除-内存将保持不变。

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


如何从ViewModel或持有人片段中手动清除ViewModelStore

13 个答案:

答案 0 :(得分:14)

无需使用Navigation Component库的快速解决方案:

getActivity().getViewModelStore().clear();

这将解决此问题,而无需合并Navigation Component库。这也是简单的一行代码。它将清除通过ViewModelsFragments之间共享的Activity

答案 1 :(得分:5)

正如OP和Archie所说,Google使我们能够将ViewModel的作用域范围扩展到导航图。如果您已经在使用导航组件,我将在此处添加操作方法。

您可以在导航图和right-click->move to nested graph->new graph中选择所有需要组合在一起的片段

现在,这会将选定的片段移动到主导航图内的嵌套图,如下所示:

<navigation app:startDestination="@id/homeFragment" ...>
    <fragment android:id="@+id/homeFragment" .../>
    <fragment android:id="@+id/productListFragment" .../>
    <fragment android:id="@+id/productFragment" .../>
    <fragment android:id="@+id/bargainFragment" .../>

    <navigation 
        android:id="@+id/checkout_graph" 
        app:startDestination="@id/cartFragment">

        <fragment android:id="@+id/orderSummaryFragment".../>
        <fragment android:id="@+id/addressFragment" .../>
        <fragment android:id="@+id/paymentFragment" .../>
        <fragment android:id="@+id/cartFragment" .../>

    </navigation>

</navigation>

现在,在初始化视图模型时在片段内部执行此操作

val viewModel: CheckoutViewModel by navGraphViewModels(R.id.checkout_graph)

如果您需要通过视图模型工厂(可能是用于注入视图模型),则可以这样做:

val viewModel: CheckoutViewModel by navGraphViewModels(R.id.checkout_graph) { viewModelFactory }

请确保其R.id.checkout_graph而不是R.navigation.checkout_graph

出于某种原因,创建导航图并使用include将其嵌套在主导航图内对我不起作用。可能是一个错误。

来源:https://medium.com/androiddevelopers/viewmodels-with-saved-state-jetpack-navigation-data-binding-and-coroutines-df476b78144e

感谢OP和@Archie向我指出正确的方向。

答案 2 :(得分:4)

如果您检查代码here,就会发现,您可以从ViewModelStoreViewModelStoreOwner,例如Fragment中获得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。

答案 3 :(得分:2)

如果您不希望将ViewModel的范围划分为Activity的生命周期,则可以将其划分为父片段的生命周期。因此,如果您要与一个屏幕中的多个片段共享ViewModel的实例,则可以对这些片段进行布局,以使它们都共享一个公共的父片段。这样,在实例化ViewModel时,您可以执行以下操作:

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

希望这会有所帮助!

答案 4 :(得分:1)

我认为我有一个更好的解决方案。

如@Nagy Robi所述,您可以通过调用ViewModel来清除viewModelStore.clear()。这样做的问题是,它将清除此ViewModelStore内的所有视图模型。换句话说,您将无法控制要清除的ViewModel

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

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

请对此方法提供一些反馈。一切都会感激的。

答案 5 :(得分:1)

我只是在编写库来解决此问题:scoped-vm,请随时进行检查,我们将非常感谢您提供任何反馈。 在后台,它使用上述方法@Archie-每个范围维护一个单独的ViewModelStore。但是它又向前迈进了一步,一旦从该范围请求视图模型的最后一个片段销毁了,它就会清除ViewModelStore本身。

我应该说,当前的整个视图模型管理(尤其是这个lib)都受到serious bug和backstack的影响,希望它将得到修复。

摘要:

  • 如果您担心ViewModel.onCleared()不被调用,最好的方法(目前)是自己清除它。由于该错误,您无法保证将清除fragment的视图模型。
  • 如果您只是担心泄漏的ViewModel-不用担心,它们将像其他任何未引用的对象一样被垃圾回收。如果需要,请随意使用我的lib进行细粒度的范围设置。

答案 6 :(得分: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 = this@MainActivity.getIndividualViewModelStore<VIEWMODEL>()
    return ViewModelProvider(this.getIndividualViewModelStore<VIEWMODEL>(), factory).get(VIEWMODEL::class.java)
}

    val viewModelStore = this@MainActivity.getIndividualViewModelStore<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,则可能希望尽快清除它(例如,如果其中包含一些敏感的用户数据,如用户名或密码)。这是一种在每次切换片段时记录individualModelStores状态的方法,以帮助您跟踪共享的ViewModel:

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

    if (BuildConfig.DEBUG) {
        navController.addOnDestinationChangedListener { _, _, _ ->
            if (individualModelStores.isNotEmpty()) {
                val tag = this@MainActivity.javaClass.simpleName
                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 ${this@MainActivity.javaClass.simpleName}:"
                )
                for ((index, viewModelClass) in individualModelStores.keys.withIndex()) {
                    Log.w(
                            tag,
                            "${index + 1}) $viewModelClass\n"
                    )
                }
            }
        }
    }
}

答案 7 :(得分:1)

就我而言,我观察到的大多数事物都与View有关,因此,在View被销毁(但{{1 }}。

如果我需要像Fragment这样的东西将我带到另一个LiveData(或者只做一次),我会创建一个“消费观察者”。

可以通过扩展Fragment来完成:

MutableLiveData<T>

,一旦观察到,它将从fun <T> MutableLiveData<T>.observeConsuming(viewLifecycleOwner: LifecycleOwner, function: (T) -> Unit) { observe(viewLifecycleOwner, Observer<T> { function(it ?: return@Observer) value = null }) } 中清除。

现在您可以这样称呼:

LiveData

答案 8 :(得分:0)

我找到了一种解决此问题的简单而优雅的方法。诀窍是使用DummyViewModel和模型密钥。

该代码有效,因为AndroidX在get()上检查了模型的类类型。如果不匹配,则使用当前的ViewModelProvider.Factory创建一个新的ViewModel。

public class MyActivity extends AppCompatActivity {
    private static final String KEY_MY_MODEL = "model";

    void clearMyViewModel() {
        new ViewModelProvider(this, new ViewModelProvider.NewInstanceFactory()).
            .get(KEY_MY_MODEL, DummyViewModel.class);
    }

    MyViewModel getMyViewModel() {
        return new ViewModelProvider(this, new ViewModelProvider.AndroidViewModelFactory(getApplication()).
            .get(KEY_MY_MODEL, MyViewModel.class);
    }

    static class DummyViewModel extends ViewModel {
        //Intentionally blank
    }
}   

答案 9 :(得分:0)

据我所知,您无法通过程序手动删除ViewModel对象,但是可以清除存储在其中的数据,在这种情况下,您应该手动调用Oncleared()方法 为此:

  1. 覆盖从Oncleared()类扩展的该类中的ViewModel方法
  2. 在这种方法中,您可以通过将存储数据的字段设置为空来清除数据
  3. 要完全清除数据时,请调用此方法。

答案 10 :(得分:0)

似乎在最新的体系结构组件版本中已经解决了该问题。

ViewModelProvider 具有以下构造函数:

    /**
 * Creates {@code ViewModelProvider}, which will create {@code ViewModels} via the given
 * {@code Factory} and retain them in a store of the given {@code ViewModelStoreOwner}.
 *
 * @param owner   a {@code ViewModelStoreOwner} whose {@link ViewModelStore} will be used to
 *                retain {@code ViewModels}
 * @param factory a {@code Factory} which will be used to instantiate
 *                new {@code ViewModels}
 */
public ViewModelProvider(@NonNull ViewModelStoreOwner owner, @NonNull Factory factory) {
    this(owner.getViewModelStore(), factory);
}

在使用Fragment的情况下,将使用范围限定的ViewModelStore。

androidx.fragment.app.Fragment#getViewModelStore

    /**
 * Returns the {@link ViewModelStore} associated with this Fragment
 * <p>
 * Overriding this method is no longer supported and this method will be made
 * <code>final</code> in a future version of Fragment.
 *
 * @return a {@code ViewModelStore}
 * @throws IllegalStateException if called before the Fragment is attached i.e., before
 * onAttach().
 */
@NonNull
@Override
public ViewModelStore getViewModelStore() {
    if (mFragmentManager == null) {
        throw new IllegalStateException("Can't access ViewModels from detached fragment");
    }
    return mFragmentManager.getViewModelStore(this);
}

androidx.fragment.app.FragmentManagerViewModel#getViewModelStore

    @NonNull
ViewModelStore getViewModelStore(@NonNull Fragment f) {
    ViewModelStore viewModelStore = mViewModelStores.get(f.mWho);
    if (viewModelStore == null) {
        viewModelStore = new ViewModelStore();
        mViewModelStores.put(f.mWho, viewModelStore);
    }
    return viewModelStore;
}

答案 11 :(得分:0)

如果您共享了 ViewModel 并希望将它们全部清除并且这些都基于单个活动,那么添加以下代码行即可。

getActivity().getViewModelStore().clear();

答案 12 :(得分:-2)

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

使用多个视图模型没有错。第一个可以限定为Activity,而另一个可以限定为片段。

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