探秘Android黑科技-ViewModel如何无视ConfigurationChange

3,321 阅读6分钟

背景介绍

ViewModel是Android Architecture Components中提供的一个组件,其作用是为UI组件提供需要展现的数据内容,帮助开发者更优雅简单高效的实现多个组件间的数据共享。其中值得注意的是ViewModel的生命周期,以往我们将UI展示的数据直接缓存在对应的UI组件中,遇到ConfigurationChange等事件UI组件重新创建,我们缓存的数据也随之销毁。但ViewModel可以在内存中长期被持有而不受ConfigurationChange的影响,直到相关联的UI组件真正销毁的时候ViewModel才随之释放。下面是来自官方的对ViewModel生命周期的描述图:

本文主要探讨文章标题提到的问题,关于ViewModel的细节介绍可以参阅官方文档

开门见山

如果堆代码不如自己去阅读源码,那样反而来的更清晰直观。所以后面的部分不会打源码战术,但是也有一些个人分析的过程描述,如果只关心黑科技是啥,可以直接跳到最后结论部分。

由官方的demo我们可以看到使用ViewModel的简单方法如下:

public class MyViewModel extends ViewModel {
    private MutableLiveData<List<User>> users;
    public LiveData<List<User>> getUsers() {
        if (users == null) {
            users = new MutableLiveData<List<Users>>();
            loadUsers();
        }
        return users;
    }

    private void loadUsers() {
        // Do an asynchronous operation to fetch users.
    }
}

public class MyActivity extends AppCompatActivity {
    public void onCreate(Bundle savedInstanceState) {
        // Create a ViewModel the first time the system calls an activity's onCreate() method.
        // Re-created activities receive the same MyViewModel instance created by the first activity.

        MyViewModel model = ViewModelProviders.of(this).get(MyViewModel.class);
        model.getUsers().observe(this, users -> {
            // update UI
        });
    }
}

我们需要实现一个派生自ViewModel的类。接着在需要的UI组件中通过ViewModelProviders.of().get()来获取我们定义的ViewModel实例。demo中看到真正使用时往往在ViewModel中以LiveData来封装需要UI组件展现的数据,于是在获取我们定义的ViewModel实例后便可以通过其内部的LiveData来添加观察者,进而实现数据变更时能及时的通知到UI组件。关于LiveData的细节介绍可以参阅官方文档。 我们还是回到ViewModel。就以demo中看到的ViewModelProviders为切入点。

我画了一张类图,打算尝试按照自己的理解将他们的关系讲清楚,那也就基本了解为什么ViewModel对ConfigurationChange免疫了。也为大家查阅源码或者提出异议提供一个思路。

查看原图

图中虚线箭头表示依赖关系,箭头尾部对应的类依赖箭头头部对应的类。 中间的实线箭头表示类的组合关系,箭头尾部对应的类中包含箭头头部对应的类的实例。

类图简述

  • ViewModelProvider依赖ViewModelProviders类来创建ViewModelProvider实例。
  • ViewModelProvider中有一个ViewModelStore类型的成员变量mViewModelStore。ViewModelProviders在创建ViewModelProvider的过程中需要依赖ViewModelStores来创建ViewModelStore实例并赋值给ViewModelProvider的成员变量mViewModelStore。
  • ViewModelStore依赖ViewModelStores类来创建ViewModelStore实例。
  • ViewModelStores创建ViewModelStore的过程中需要依赖HolderFragment的支持,创建好的ViewModelStore保存在HolderFragment的成员变量mViewModelStore中,通过getViewModelStore()方法返回其内部保存的ViewModelStore类型对象给ViewModelProvider。
  • 最终通过ViewModelProvider类的get()方法获取ViewModel实例。

ViewModelProvider实例

ViewModel对ConfigurationChange免疫,换个角度可以理解为在不同的UI组件实例中获取的ViewModel实例相同。 根据demo得知,我们通过ViewModelProviders.of().get()这样一个链式调用来获取ViewModel实例。 所以要获得一个ViewModel分为两个步骤:

第一步,调用ViewModelProviders.of()方法获取一个ViewModelProvider实例。这正是图中左上部分表达的信息:

ViewModelProvider依赖于ViewModelProviders,ViewModelProvider是通过ViewModelProviders提供的一系列of方法构造出来的。通过ViewModelProvider的构造方法的定义可以看到在构造时需要传入ViewModelStore和Factory。ViewModelStore是真正缓存ViewModel的地方,Factory则用于在缓存未命中时创建ViewModel。

第二步,通过ViewModelProvider.get()方法获取ViewModel实例,这从图中的ViewModelProvider和ViewModelStore的关系能有所体现:

ViewModelProvider内部有一个ViewModelStore类型的成员变量mViewModelStore和一个用于创建ViewModel的Factory成员变量mFactory。他们是在创建对象时通过构造方法传入的。调用ViewModelProvider.get()方法其实是通过内部的ViewModelStore.get()方法来获取ViewModel,而如果获取为空,则通过mFactory构建一个新的ViewModel。

ViewModelStore中保存一个Map键值对,这里是真正缓存ViewModel的地方,所以ViewModelProvider.get()最终会调到ViewModelStore的这个Map中来找缓存的ViewModel。 因为最终是从ViewModelStore的Map键值对中查找缓存的ViewModel,所以要保证ViewModel的唯一性其实就是保证ViewModelStore的唯一性。 前面提到ViewModelProvider中的mViewModelStore是先通过ViewModelStores构造好,然后利用其构造方法传递到ViewModelProvider中的。所以接下来的重点就是ViewModelStores如何构造一个ViewModelStore。

ViewModelStore实例

ViewModelStore通过ViewModelStores构造,其构造过程需要图中的ViewModelStores和HolderFragment类一起完成。 从图中可以看到ViewModelStore是通过ViewModelStores.of()方法来构造的,ViewModelStores.of()方法内部会直接调用HolderFragment的静态方法holderFragmentFor(),该静态方法返回一个HolderFragment对象,这个HolderFragment对象就是一个派生自Fragment的类,只不过它不包含任何View树结构。接着调用它的getViewModelStore()方法返回HolderFragment的成员变量mViewModelStore,这个mViewModelStore是作为成员变量在HolderFragment类加载时直接new出来的。所以一个HolderFragment对象一定对应唯一一个ViewModelStore对象实例。同时这个mViewModelStore实例会作为前面构造ViewModelProvider时的参数传进去。也就是说ViewModelProvider和HolderFragment中的mViewModelStore对象指向的是同一个实例,这个实例是先在HolderFragment中构造初始化之后传递到ViewModelProvider中去的。 既然ViewModelStore是通过HolderFragment创建出来的,一个HolderFragment实例内部唯一对应一个ViewModelStore实例,那现在的问题就变成如何保证HolderFragment的唯一性了。 到这里,黑科技终于登场。其实就是利用Fragment的setRetainInstance()方法。通过在实例化HolderFragment的时候调用该方法,并传入true作为参数。HolderFragment就可以在其宿主的Fragment或Activity重走生命周期时在内存中持久化其自身实例,即此时Fragment对应的View会从View树中移除,但是Fragment实例本身不会销毁,在下一次宿主Fragment或Activity重走生命周期时会复用内存中持久化的Fragment实例。以此来达到HolderFragment实例的唯一性。关于setRetainInstance()方法的使用详情可以参阅官方文档

总结

  • ViewModel要无视ConfigurationChange事件,就得保证在ConfigurationChange事件发生时UI组件生命周期重走过程中获取到的ViewModel实例与之前的是同一个实例。
  • ViewModel最终会缓存在ViewModelStore的Map中,而一个HolderFragment实例与ViewModelStore实例一一对应。所以问题最终回到如何保证HolderFragment的唯一性。
  • HolderFragment派生自Fragment。利用Fragment的setRetainInstance()方法保证HolderFragment在ConfigurationChange事件发生时可以复用内存中同一个HolderFragment实例,进而保证HolderFragment的唯一性,达到无视ConfigurationChange的目的。