浅谈 ViewModel 的生命周期控制

栏目: IOS · Android · 发布时间: 5年前

内容简介:想必有过一定开发经验的同学对 ViewModel 都不会陌生,它是 Google 推出的 MVVM 架构模式的一部分。这里它的基础使用我们就不介绍了,毕竟这种类型的文章也遍地都是。今天我们着重来探讨一下它的生命周期。起因是这样的,昨天在修复程序中的 Bug 的时候遇到了一个异常,是从 ViewModel 中获取存储的数据的时候报了空指针。我启用了开发者模式的 “不保留活动” 之后很容易地重现了这个异常。出现错误的原因也很简单,相关的代码如下:在方法

想必有过一定开发经验的同学对 ViewModel 都不会陌生,它是 Google 推出的 MVVM 架构模式的一部分。这里它的基础使用我们就不介绍了,毕竟这种类型的文章也遍地都是。今天我们着重来探讨一下它的生命周期。

起因是这样的,昨天在修复程序中的 Bug 的时候遇到了一个异常,是从 ViewModel 中获取存储的数据的时候报了空指针。我启用了开发者模式的 “不保留活动” 之后很容易地重现了这个异常。出现错误的原因也很简单,相关的代码如下:

private ReceiptViewerViewModel viewModel;

    @Override
    protected void doCreateView(Bundle savedInstanceState) {
        viewModel = ViewModelProviders.of(this).get(ReceiptViewerViewModel.class); // 1
        handleIntent(savedInstanceState);
        // ... 
    }

    private void handleIntent(Bundle savedInstanceState) {
        LoadingStatus loadingStatus;
        if (savedInstanceState == null) {
            loadingStatus = (LoadingStatus) getIntent().getSerializableExtra(Router.RECEIPT_VIEWER_LOADING_STATUS);
        }
        viewModel.setLoadingStatus(loadingStatus);
    }
复制代码

在方法 doCreateView() 中我获取了 viewModel 实例,然后在 handleIntent() 方法中从 Intent 中取出传入的参数。当然,还要使用 viewModelgetter 方法从其中取出 loadingStatus 并使用。在使用的时候抛了空指针。

显然,一般情况下是不会出现问题的,但是如果 Activity 在后台被销毁了,那么再重建的时候就会出现空指针异常。

解决方法也比较简单,在 onSaveInstanceState() 方法中将数据缓存起来即可,即:

private void handleIntent(Bundle savedInstanceState) {
        LoadingStatus loadingStatus;
        if (savedInstanceState == null) {
            loadingStatus = (LoadingStatus) getIntent().getSerializableExtra(Router.RECEIPT_VIEWER_LOADING_STATUS);
        } else {
            loadingStatus = (LoadingStatus) savedInstanceState.get(Router.RECEIPT_VIEWER_LOADING_STATUS);
        }
        viewModel.setLoadingStatus(loadingStatus);
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putSerializable(Router.RECEIPT_VIEWER_LOADING_STATUS, viewModel.getLoadingStatus());
    }
复制代码

现在的问题是 ViewModel 的生命周期问题,有人说在 doCreateView() 方法的 1 处得到的不是之前的 ViewModel 吗,数据不是之前已经设置过了吗?所以,这牵扯 ViewModel 是在什么时候被销毁和重建的问题。

2、ViewModel 的生命周期

有的人希望使用 ViewModel 缓存 Activity 的信息,然后在 doCreateView() 方法的 1 处得到之前的 ViewModel 实例,这样 ViewModel 的数据就是 Activity 销毁之前的数据,这可行吗?我们从源码角度来看下这个问题。

首先,每次获取 viewmodel 实例的时候都会调用下面的方法来获取 ViewModel 实例。从下面的 get() 方法中可以看出,实例化过的 ViewModel 是从 mViewModelStore 中获取的。如果由 ViewModelStores.of(activity) 方法得到的 mViewModelStore 不是同一个,那么得到的 ViewModel 也不是同一个。

下面方法中的 get() 方法中后续的逻辑是如果之前没有缓存过 ViewModel,那么就构建一个新的实例并将其放进 mViewModelStore 中。这部分代码逻辑比较简单,我们不继续分析了。

// ViewModelProviders#of()
    public static ViewModelProvider of(@NonNull FragmentActivity activity) {
        ViewModelProvider.AndroidViewModelFactory factory =
                ViewModelProvider.AndroidViewModelFactory.getInstance(activity);
        return new ViewModelProvider(ViewModelStores.of(activity), factory); // 1
    }

    // ViewModelProvider#get()
    public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
        ViewModel viewModel = mViewModelStore.get(key);

        if (modelClass.isInstance(viewModel)) {
            return (T) viewModel;
        }

        viewModel = mFactory.create(modelClass);
        mViewModelStore.put(key, viewModel);
        return (T) viewModel;
    }
复制代码

我们回到上述 of() 方法的 1 处,来看下 ViewModelStores.of() 方法,其定义如下:

// ViewModelStores#of()
    public static ViewModelStore of(@NonNull FragmentActivity activity) {
        if (activity instanceof ViewModelStoreOwner) {
            return ((ViewModelStoreOwner) activity).getViewModelStore();
        }
        return holderFragmentFor(activity).getViewModelStore();
    }

    // HolderFragment#holderFragmentFor()
    public static HolderFragment holderFragmentFor(FragmentActivity activity) {
        return sHolderFragmentManager.holderFragmentFor(activity);
    }
复制代码

这里会从 holderFragmentFor() 方法中获取一个 HolderFragment 实例,它是一个 Fragment 的实现类。然后从该实例中获取 ViewModelStore 的实例。所以,ViewModel 对生命周期的管理与 Glide 和 RxPermission 等框架的处理方式一致,就是使用一个空的 Fragment 来进行生命周期管理。

对于 HolderFragment ,其定义如下。从下面的代码我们可以看出,上述用到的 ViewModelStore 实例就是 HolderFragment 的一个局部变量。所以,ViewModel 使用空的 Fragment 管理生命周期实锤了。

public class HolderFragment extends Fragment implements ViewModelStoreOwner {
        private static final HolderFragmentManager sHolderFragmentManager = new HolderFragmentManager();
        private ViewModelStore mViewModelStore = new ViewModelStore();

        public HolderFragment() {
            setRetainInstance(true);
        }

        // ...
    }
复制代码

此外,我们注意到上面的 HolderFragment 的构造方法中还调用了 setRetainInstance(true) 这一行代码。我们进入该方法看它的注释:

Control whether a fragment instance is retained across Activity re-creation (such as from a configuration change). This can only be used with fragments not in the back stack. If set, the fragment lifecycle will be slightly different when an activity is recreated:

就是说,当 Activity 被重建的时候该 Fragment 会被保留,然后传递给新创建的 Activity. 但是,这只适用于不处于后台的 Fragment. 所以,如果 Activity 处于后台的时候,Fragment 不会保留,那么它得到的 ViewModelStore 实例就不同了。

所以,总结下来,准确地将: 当 Activity 处于前台的时候被销毁了,那么得到的 ViewModel 是之前实例过的 ViewModel;如果 Activity 处于后台时被销毁了,那么得到的 ViewModel 不是同一个。举例说,如果 Activity 因为配置发生变化而被重建了,那么当重建的时候,ViewModel 是之前的实例;如果因为长期处于后台而被销毁了,那么重建的时候,ViewModel 就不是之前的实例了。

回到之前的 holderFragmentFor() 方法,我们看下这里具体做了什么,其定义如下。

// HolderFragmentManager#holderFragmentFor()
    HolderFragment holderFragmentFor(FragmentActivity activity) {
        // 使用 FragmentManager 获取 HolderFragment
        FragmentManager fm = activity.getSupportFragmentManager();
        HolderFragment holder = findHolderFragment(fm);
        if (holder != null) {
            return holder;
        }
        // 从哈希表中获取 HolderFragment
        holder = mNotCommittedActivityHolders.get(activity);
        if (holder != null) {
            return holder;
        }

        if (!mActivityCallbacksIsAdded) {
            mActivityCallbacksIsAdded = true;
            activity.getApplication().registerActivityLifecycleCallbacks(mActivityCallbacks);
        }
        holder = createHolderFragment(fm);
        // 将新的实例放进哈希表中
        mNotCommittedActivityHolders.put(activity, holder);
        return holder;
    }
复制代码

首先,尝试使用 FragmentManager 来获取 HolderFragment ,如果获取不到就从 mNotCommittedActivityHolders 中进行获取。这里的 mNotCommittedActivityHolders 是一个哈希表,每次实例化的新的 HolderFragment 会被添加到哈希表中。

另外,上面的方法中还使用了 ActivityLifecycleCallbacks 对 Activity 的生命周期进行监听。其定义如下,

private ActivityLifecycleCallbacks mActivityCallbacks =
            new EmptyActivityLifecycleCallbacks() {
                @Override
                public void onActivityDestroyed(Activity activity) {
                    HolderFragment fragment = mNotCommittedActivityHolders.remove(activity);
                }
            };
复制代码

当 Activity 被销毁的时候会从哈希表中移除映射关系。所以,每次 Activity 被销毁的时候哈希表中的映射关系都不存在了。而之所以 ViewModel 能够实现在 Activity 配置发生变化的时候获取之前的 ViewModel 是通过上面的 setRetainInstance(true)findHolderFragment(fm) 来实现的。

总结

以上就是 ViewModel 的生命周期的总结。我们只是通过对主流程的分析研究了它的生命周期的流程,实际上内部还有许多小细节,逻辑也比较简单,我们就不一一说明了。

其实,从 Google 的官方文档中,我们也能够得到上面的总结,

浅谈 ViewModel 的生命周期控制

这里使用了 Activity rotated ,也就是 Activity 处于前台的时候配置发生变化的情况,而不是处于后台,不知道你之前有没有注意这一点呢?

以上。

(如有疑问,可以在评论中交流)

如果你喜欢这篇文章,请点赞哦!你也可以在以下平台关注我哦:

所有的文章维护在: Github, Android-notes


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

我知道他想看什么

我知道他想看什么

沙建军 / 中信出版社 / 2018-1 / 48.00

社交媒体迅速发展、信息快速迭代、时间碎片化;大数据、智能终端、物联网横空出世;移动支付、网红经济和传统营销失效,这些都让这个时代的媒体、内容、渠道、产品之间的边界越来越模糊,也从根本上改变了营销的逻辑,内容营销从热词变成趋势,变成营销的底层思维。未来一切都是媒体,形式也是内容。 本书作者通过对国内外36个内容营销的新近案例的故事化描述和透彻分析,提出“组织媒介化”“营销内容化”“内容情趣化”......一起来看看 《我知道他想看什么》 这本书的介绍吧!

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具