ViewModel 如何在配置更改后存活下来

How ViewModel survives configuration change

我正在尝试在我的应用程序中使用 ViewModel。我想到的问题是视图模型如何在配置更改后幸存下来。我读了很多博客文章说“

It will create a HolderFragment to add to your activity or your fragment, it's invisible, when the configuration changed, activity destroyed, but holder fragment is still alive

这使得 sense.But 我试图对此进行更多探索,发现在 支持库 27.1.0+ 中,他们删除了 HolderFragment,其描述为

Deprecate ViewModelStores.of() and the HolderFragment it relies on as they are no longer needed link for android.googlesource.

现在的问题是他们现在是如何做同样的事情的?

ViewModelProviders.of() 方法创建的 ViewModels 存储在 ViewModelStore hashmap 中,所以真正的问题是如何存储 ViewModelStore

对于活动来说,这个逻辑很简单。 ViewModelStore 使用 onRetainNonConfigurationInstance 方法存储:

@Override
    @Nullable
    public final Object onRetainNonConfigurationInstance() {
        Object custom = onRetainCustomNonConfigurationInstance();

        ViewModelStore viewModelStore = mViewModelStore;
        if (viewModelStore == null) {
            // No one called getViewModelStore(), so see if there was an existing
            // ViewModelStore from our last NonConfigurationInstance
            NonConfigurationInstances nc =
                    (NonConfigurationInstances) getLastNonConfigurationInstance();
            if (nc != null) {
                viewModelStore = nc.viewModelStore;
            }
        }

        if (viewModelStore == null && custom == null) {
            return null;
        }

        NonConfigurationInstances nci = new NonConfigurationInstances();
        nci.custom = custom;
        nci.viewModelStore = viewModelStore;
        return nci;
    }

对于片段,事情有点复杂。 FragmentManagerImpl 现在有一个名为 mNonConfig 的字段:

private FragmentManagerViewModel mNonConfig;

其中存储了片段的 UUID 和 ViewModelStore.

的散列图

mNonConfig 字段在 FragmentManagerImpl#attachController 方法中初始化:

    public void attachController(@NonNull FragmentHostCallback host,
            @NonNull FragmentContainer container, @Nullable final Fragment parent) {
        if (mHost != null) throw new IllegalStateException("Already attached");
        mHost = host;
        mContainer = container;
        mParent = parent;
        if (mParent != null) {
            // Since the callback depends on us being the primary navigation fragment,
            // update our callback now that we have a parent so that we have the correct
            // state by default
            updateOnBackPressedCallbackEnabled();
        }
        // Set up the OnBackPressedCallback
        if (host instanceof OnBackPressedDispatcherOwner) {
            OnBackPressedDispatcherOwner dispatcherOwner = ((OnBackPressedDispatcherOwner) host);
            mOnBackPressedDispatcher = dispatcherOwner.getOnBackPressedDispatcher();
            LifecycleOwner owner = parent != null ? parent : dispatcherOwner;
            mOnBackPressedDispatcher.addCallback(owner, mOnBackPressedCallback);
        }

        // Get the FragmentManagerViewModel
        if (parent != null) {
            mNonConfig = parent.mFragmentManager.getChildNonConfig(parent);
        } else if (host instanceof ViewModelStoreOwner) {
            ViewModelStore viewModelStore = ((ViewModelStoreOwner) host).getViewModelStore();
            mNonConfig = FragmentManagerViewModel.getInstance(viewModelStore);
        } else {
            mNonConfig = new FragmentManagerViewModel(false);
        }
    }

基本上,要在 Activity 中检索 ViewModel,应该调用 ViewModelProviders.of(this).get(SomeViewModel.class)。现在,如果我们查看 of,它如下所示:

public static ViewModelProvider of(@NonNull FragmentActivity activity,
        @Nullable Factory factory) {
    Application application = checkApplication(activity);
    if (factory == null) {
        factory = ViewModelProvider.AndroidViewModelFactory.getInstance(application);
    }
    return new ViewModelProvider(activity.getViewModelStore(), factory);
}

因此,重要的部分是此方法 - activity.getViewModelStore() 因为它 returns 是所有 ViewModel 对象的包装器对象(HashMap 持有者),如果它可以在配置更改后继续存在,所以您的所有 ViewModel 对象也可以:

public ViewModelStore getViewModelStore() {
    if (getApplication() == null) {
        throw new IllegalStateException("Your activity is not yet attached to the "
                + "Application instance. You can't request ViewModel before onCreate call.");
    }
    if (mViewModelStore == null) {
        NonConfigurationInstances nc =
                (NonConfigurationInstances) getLastNonConfigurationInstance();
        if (nc != null) {
            // Restore the ViewModelStore from NonConfigurationInstances
            mViewModelStore = nc.viewModelStore;
        }
        if (mViewModelStore == null) {
            mViewModelStore = new ViewModelStore();
        }
    }
    return mViewModelStore;
}

mViewModelStore 将从 NonConfigurationInstances 恢复或从头开始创建。几乎,NonConfigurationInstances 是在配置更改后仍然存在的对象,因此用于存储 ViewModelStore。这就是旋转后返回相同 ViewModelStore 对象的原因 - 它存储在独立于配置更改的 NonConfigurationInstances:

如果您查看 onRetainNonConfigurationInstance(),您实际上会发现您的 ViewModelStore 保存在那里:

public final Object onRetainNonConfigurationInstance() {
    ...
    NonConfigurationInstances nci = new NonConfigurationInstances();
    nci.custom = custom;
    nci.viewModelStore = viewModelStore;
    return nci;
}

另外,只有当 onDestroy 由于非配置更改原因被调用时才会被清除:

...
    getLifecycle().addObserver(new LifecycleEventObserver() {
        @Override
        public void onStateChanged(@NonNull LifecycleOwner source,
                @NonNull Lifecycle.Event event) {
            if (event == Lifecycle.Event.ON_DESTROY) {
                if (!isChangingConfigurations()) {
                    getViewModelStore().clear();
                }
            }
        }
    });
...   

类似的技巧用于为 Fragment 存储 ViewModel