Android 在调用 ft.replace 时为同一片段返回不同的 ViewModel 实例

Android returning different instance of ViewModel for the same fragment when calling ft.replace

我有一个容器 Fragment,称它为 ContainerFragment,其中包含两个片段,FragmentAFragmentBFragmentAFragmentB 都使用 ViewModelProviders.of(this) 方法在 onCreate() 方法中创建了一个 ViewModel

假设容器一次只能显示一个片段,我使用 FragmentTransaction.replace() 方法在 FragmentAFragmentB 之间切换。在我的例子中,我显示了 FragmentA,它创建了 FragmentA ViewModel,然后一个动作触发 FragmentB 来替换 FragmentA。现在,当我完成 FragmentB 后,我再次调用 replace 以显示 FragmentA。这是我在替换片段的方法中所做的检查:

if (fragmentA == null) {
    fragmentA = FragmentA.newInstance();
}
FragmentTransaction ft = getChildFragmentManager().beginTransaction();
ft.replace(R.id.container_content, fragmentA, "my-tag");
ft.commit();

由于 FragmentA 是第一次创建它 运行,所以它不会进入 if 块。但是我注意到 FragmentA return 的 onCreate() 是 ViewModel 的不同实例。我在 FragmentA 中有以下代码:

public void onCreate(Bundle sI) {
    super.onCreate(sI);
    mViewModel = ViewModelProviders.of(this).get(MyViewModel.class);
    Log.i("test", "ViewModel-> " + mViewModel.toString());
}

日志打印:

ViewModel-> com.myapp.MyViewModel@ed93f63

我第一次创建 FragmentA 然后

ViewModel-> com.myapp.MyViewModel@ff3eee4

我打电话后 ft.replace().

所以,我很困惑为什么 ViewModelProviders return 第二次 ViewModel 的不同实例,即使 FragmentA 不为空(因为它没有进入 if 块,我假设它不为空)?

根据 doc:

A ViewModel is always created in association with a scope (an fragment or an activity) and will be retained as long as the scope is alive. E.g. if it is an Activity, until it is finished.

ViewModel的范围与输入Fragment/Activity的范围绑定。

  • Activity:当它销毁且不是由配置更改引起时
  • 片段:销毁且不是由配置更改引起的(即从FragmentManager中删除)

换句话说,当您将 FragmentA 替换为 FragmentB 时,FragmentA 将被销毁,因此其关联的 ViewModel 实例将被删除。


当您调用 ViewModelProviders.of(fragment) 获取 ViewModel 时,它最终将此任务交给 ViewModelStore,它存储了我们的 ViewModel 个实例。

让我们深入探讨 androidx.fragment.app.Fragment-1.0.0

source code
@CallSuper
public void onDestroy() {
    mCalled = true;
    FragmentActivity activity = getActivity();
    boolean isChangingConfigurations = activity != null && activity.isChangingConfigurations();
    if (mViewModelStore != null && !isChangingConfigurations) {
        mViewModelStore.clear();
    }
}

因此 ViewModel 实例因为这个调用被移除 mViewModelStore.clear();