2018/1/31 19:01:26当前位置推荐好文程序员浏览文章

概览

在考虑到UI 元素生命周期变化的环境下,架构组件中的 ViewModel 主要用于存储以及管理UI相关的数据。ViewModel 可以在配置信息变化(configuration change) 时 (例如屏幕的旋转) 保持数据状态。

众所周知的是,Android系统框架管理着UI控制器(Activity / Fragment)的生命周期。出于对用户行为以及设备事件的响应,系统可能会销毁或重建UI控制器,而这些行为是完全不受控制的。如果系统销毁 或是 重建UI控制器时,任何UI相关的暂存数据都将会被清除。

另一个问题是,UI控制器通常需要频繁地调用异步接口,耗费一定的时间获取数据。除了维护相关的接口外,在UI控制器被重新创建时往往会发起重复的请求,这无疑会造成资源的浪费。

UI 控制器如Activity / Fragment主要用于展示UI数据,响应用户请求,以及处理系统的事件,如授权访问请求。在UI 控制器中加入请求加载数据库或是网络的数据的逻辑将使得类不断膨胀。将大量无关的职责分配给UI 控制器,而不是将各项职责分配出去则会导致出现 God Object,同时会降低整体项目的可测试性。

ViewModel 的生命周期 ( outlive Fragment or Activity ?)

且看官方给出的ViewModel生命周期图示:


viewmodel-lifecycle

对照Demo中的实际效果:

vm_test

利用ViewModel 在旋转屏幕后,可以看到ViewModel的instance 还是同一个,而且其中的score 数据依然保存了下来。(观察实际的log也可以印证这一点)

如果直接按back 键退出,可以看到几乎在ActivityonDestroy() 方法调用的同时,ViewModel#onCleared()方法被调用,此时ViewModel也随即销毁了。

显而易见的是,除了数据已经保存外,还有一个好处在于省去了需要手动关联/解除ViewModel与界面生命周期的调用。即不必在先前自实现的ViewModel|Presenter中添加类似OnDestroy()方法,并发起显示的调用了。

实现

以上的ViewModel中实现了‘自知’的生命周期,这究竟是如何做到的呢?让我们退一步回到在使用Fragment时,看如何在转屏条件下保存内存中的临时数据。

从Fragment的角度来看状态保存

在转屏时,按默认的情况,FragmentManager会负责销毁相关联的Fragment, Fragment的生命周期 onPause, onStop, onDestroy会被相继调用。然而Fragment有一个特性可以设置Fragment#setRetainIntance(true)保存当前的自身实例(instance)。当该选项设置后,Fragment 当前实例被保存下来了,然后随即被传给新的Activity。留存的Fragment实际利用了Fragment关联的View可以被销毁以及重新创建,而不需要销毁Fragment自身。

在发生configuration 改变的时候,Fragment 内部的View与Activity 包含的View 一样,都会因为这一原因被销毁以及重新创建。即当有一个新的configuration变化时,很有可能需要新的资源,为了使用更好适配的资源,View将会重新创建。

随后FragmentManager会检查每一个Fragment的retainInstance属性,如果值为默认的false,FragmentManager会将Fragment实例销毁,Fragment和它关联的View都将会在新的Activity中重新创建;而如果retainInstance值为true,当前Fragment关联的View将会销毁而保留下Fragment的实例,在新的Activity创建时,新的FragmentManager会找到留存的Fragment,并重建它的View。

留存的Fragment没有被销毁,而只是从快要销毁的Activity上解绑了。表示为retain状态的Fragment仍然存在,只是不被任何Activity持有。整个转屏过程,对应生命周期的变化可见下图:

代码寻踪

按照上述Fragment实现的逻辑,架构组件中的实现又是怎样的呢?让我们从源码中寻找线索, 以Activity中的ViewModel创建为例。

ViewModelProviders.of(activity).get(YourViewModel.class)ViewModelProviders.of(activity)    -> new ViewModelProvider(ViewModelStores.of(activity), factory) // # factory 为默认的 AndroidViewModelFactory            ViewModelStores.of(activity)                -> ViewModelStore                HolderFragment#holderFragmentFor(activity).getViewModelStore()                    HolderFragmentManager#holderFragmentFor(activity)                        FragmentManager fm = activity.getSupportFragmentManager();                        HolderFragment holder = fm.findFragmentByTag(HOLDER_TAG)                        if (holder != null) {                            return holder;                        }                        holder = new HolderFragment();                            public HolderFragment() {                                setRetainInstance(true); // retained!                            }                            public void onDestroy()                                 super.onDestroy();                                mViewModelStore.clear();                                    for (ViewModel vm : mMap.values()) {                                        vm.onCleared(); // notifies ViewModels that they are no longer used.                                    }                        fm.beginTransaction().add(holder, HOLDER_TAG).commitAllowingStateLoss();                holder.getViewModelStore()viewModelProvider.get(modelClass) //YourViewModel.class    get(key, modelClass)        ViewModel viewModel = mViewModelStore.get(key); // 检查是否已经有缓存        if (modelClass.isInstance(viewModel)) {            return (T) viewModel        }        viewModel = mFactory.create(modelClass);            AndroidViewModelFactory#create(modelClass)                if(AndroidViewModel.class.isAssignableFrom(modelClass)) // 检查是否为AndroidViewModel类别                    return modelClass.getConstructor(Application.class).newInstance(mApplication);                super.create(modelClass)                    NewInstanceFactory#create(modelClass)                        return modelClass.newInstance(); // 调用默认构造方法构造实例        mViewModelStore.put(key, viewModel); // 添加ViewModel到缓存中        return (T) viewModel;

可以发现存储的ViewModel实际也是利用Fragment 设置为retained的特性实现的。它的生命周期与内置的不可见的HolderFragment是紧密联系的。在Fragment最终被销毁时,ViewModelonCleared用于执行清理动作的回调方法同时被触发。

与 onSaveInstanceState() 的异同

ViewModel保持的实际是暂存的UI中的数据,但是它们并没有被持久化(persisted)。一旦相关的UI控制器被销毁或是App进程被暂停(由于系统资源的限制),ViewModel和所包含的数据将被GC处理。

onSaveInstanceState() 该方法在以下2种情况下用来保留少量的UI相关数据。

  • App在后台由于资源限制被暂停(stopped)
  • 配置变化 (configuration change)

系统在UI层面已经利用onSaveInstanceState() 去保存View的状态信息(如 EditText中已输入的文本, ListView中的滑动位置等)。由于该方法在主线程中执行,并且序列化本身会有一定的内存开销,即要求此方法能较快执行,以免出现UI掉帧或是更严重的性能的问题。所以它的设计并不适合大数据的存储。

Fragment#setRetainInstance(true) 方法利用retained Fragment 实例,可以保留较大规模的数据 如 bitmap 或是像网络连接(network connections)这样复杂的对象。

值得注意的是,ViewModel 只在由配置信息变化改变(configuration change)引起的销毁过程中留存,当进程被暂停时,它也被销毁了。

ViewModel 以及 Retained Fragment 测试源码

Github: ArchComponentPlayground

其它ViewMode实现

inloop/AndroidViewModel

参考

  • AndroidViewModel

  • 官方 ViewModel 文档

  • Android Programming: The Big Nerd Ranch Guide

  • Medium ViewModels: Persistence, onSaveInstanceState(), Restoring UI State Loaders

网友评论