Android Architecture Components 系列(四)ViewModel

带着下面的这个问题开始ViewModel的学习:
    ViewModel的生命周期是如何控制的,并且如何保证在一定范围内的唯一性?
 
官方文档里这样写到:
    The ViewModel class is designed to store and manage UI-related data in a lifecycle conscious way. The ViewModel class allows data to survive configuration changes such as screen rotations.
    ViewModel 简单来说 这个类是设计用来存储UI层的数据,以及管理对应的数据,并且这些数据不受配置变化的影响。能够做到当数据修改的时候,可以马上刷新Ui效果,比如屏幕的旋转操作。
引言
    Android系统本身提供控件,比如Activity 和Fragment ,这些控件都是具有生命周期方法,这些生命周期方法被系统调用。
  •      activity or Fragment 不适于保存大量数据
    但是当这些控件因为一些原因被系统随时销毁或是重新创建时候,任何存放在这里的数据都有可能会丢失。举个栗子,Activity中有一个查询得到的用户列表,这时候Activity被重建,新的Activity需要再次去获取用户数据。如果简单的数据可以使用控件自带的方法,将数据保存到onSaveInstances()方法中,在下次OnCreate()中重新将数据取出来,比如UI状态这类少量数据是可以的,但是对于上述提到的大量的数据,比如列表数据,这样做就很不合时宜了。
  •    在Activity中进行大量的耗时操作和数据的回调管理会造成大量的资源浪费
    另一个问题,经常需要在Activty中加载数据,这些数据一般是异步耗时操作,因为获取数据需要联网或是花费很长时间。当前的Activity就需要管理这些数据调用,否则可能产生内存泄露的问题。这些回调事件可能会非常耗时,这时候Ui组件管理这些调用的同时,在UI组件销毁时候还需要清除这些调用。这就造成需要花费更多成本进行维护管理,而且在UI重建时候如configuration change,又需要再次重新调用,造成了很多资源的浪费。
  •     Activity的代码臃肿造成了维护和测试的不友好
    同时Ui组件不仅仅只是用来加载数据,更要对用户的操作作出响应和处理,还要加载其他资源,导致Ui类变的越来越大,越来越臃肿,这就是常说的上帝类。这种情况对代码的维护和 测试 都是非常不友好的。
    前人在这些问题的基础上开发出了MVP框架 ,创建相同类似于生命周期函数做代理,一方面减少Activity的代码量,一方面优化了各个功能模块的逻辑。
    
ViewModel
Google官方提出的AAC 的ViewModel 就是用于解决上述问题。
    ViewModel 用于为Ui组件提供管理数据,并且能够在需要的时候扔能保持里面的数据。其提供的自动绑定的形式,当数据源有更新的时候可以自动立即的更新Ui效果。
下面看一个官方的小代码实例:
    publicclass MyViewModel extends ViewModel { 
    privateMutableLiveData<List<User>> users; 
    publicLiveData<List<User>>getUsers() { 
        if(users ==null) { 
        users =newMutableLiveData<List<Users>>(); 
            loadUsers(); 
        } 
        returnusers; 
    } 
        privatevoidloadUsers() { 
        // do async operation to fetch users 
        } 
   }
You can then access the list from an activity as follows:
    Activity 访问User List 数据 
publicclass MyActivity extends AppCompatActivity { 
    publicvoidonCreate(Bundle savedInstanceState) { 
        MyViewModel model = ViewModelProviders.of(this).get(MyViewModel.class); 
        model.getUsers().observe(this, users -> { 
            // update UI 
         }); 
       }
        @Override
        protected void onDestroy() {
            super.onDestroy();
            mViewModelStore.clear()
        }
  }
      当我们获取ViewModel实例的时候,ViewModel 对象是通过ViewModelProvider保存在LifeCycle中,ViewModel会一直保存在LifeCycle中,直到Activity或是Fragment被销毁掉,Framework会调用ViewModelStore的clear方法,也就是调用ViewModel的onCleared()方法来进行资源的清理,那么ViewModel 也会被销毁的。
Caution: A ViewModel must never reference a view, Lifecycle, or any class that may hold a reference to the activity context.
ps:因为ViewModel的生命周期是和Activity分开的,所以在ViewModel中禁止引用任何View对象或者任何引用了Activity的Context的实例对象。如果ViewModel中需要Application的context可以继承AndroidViewModel类。
那么思考 用户主动按了返回Home键,主动销毁了这个Activity呢?
这时候系统会调用ViewModel的onClear()方法 清除ViewModel中的数据。
    先上一张ViewModel的生命周期示意图:
Android Architecture Components 系列(四)ViewModel
    如图 ,VIewModel相对于Activity 或是Fragment 的生命周期来说非常简单,就一个生命周期函数:onCleared(),会在Activity的onDestroy()之后执行,那么是不是可以说在Fragment的生命周期函数内也是在onDestroy之后执行呢?
ViewModel的实现过程
    //获取当前类的ViewModel提供者,之后在传入需要获得的ViewModel的类型
 MyViewModel model = ViewModelProviders.of(this) .get(MyViewModel.class); 
Android Architecture Components 系列(四)ViewModel
 
    解析:如果传入的是this 是Fragment 就先判断是否已经关联到Activity上,没有就抛出非法参数异常。之后在初始化一个sDefaultFactory对象,用于创建ViewModelProvider,并在viewModelProvider的构造函数中初始化一个ViewModelStores对象
 
Android Architecture Components 系列(四)ViewModel
 
俩个工厂方法用于创建ViewModelStore ,并区分传入的是Activity 还是 Fragment
以传入的是Activity为例:
Android Architecture Components 系列(四)ViewModel
    创建FragmentManager对象,并获取,查找当前的Activity有没有添加过HoldFragment, 如果没有呢则去还没有添加的Activity/Fragment 的 HoldFragment列表中查询,看看有没有已经创建的HoldFragment。如果没有就创建一个新的HoldFragment ,同时给Application注册一个Activity的生命监听器,再把创建饿的HoldFragment添加到缓存列表中。
HoldFragment()又是如何操作的呢?
 
Android Architecture Components 系列(四)ViewModel   在onCreate方法中执行了将在未添加到Activity或是Fragment的HolderFragment列表中删除当前的Activity 或是Fragment。
    在onDestroy方法中执行了ViewModel的clear方法,当Ui组件被销毁的时候自动通知Lifecycle进行解除绑定清除ViewModel资源的操作。
     简单总结以上内容:
 
  • 查找当前的Activity/Fragment中是否有已经添加的HoldFragment,有则返回。
  • 查找当前的Activity/Fragment是否有已经创建但是并未添加的HoldFragment,有则返回。
  • 注册Activity/Fragment的生命周期监听。
  • 创建新的HoldeFragment,并添加的缓存列表。
  • HoldFragment在关联到Activity/Fragment之后会在缓存中去掉当前的Activity/Fragment对应的HoldFragment
  • HoldFragment在onDestory的时候会调用其成员变量mViewStore的clear方法。
回到之前创建ViewModelProvider的地方:
Android Architecture Components 系列(四)ViewModel
/**
* Creates {@code ViewModelProvider}, which will create {@code ViewModels} via the given
* {@code Factory} and retain them in the given {@code store}.
*
* @param store   {@code ViewModelStore} where ViewModels will be stored.
* @param factory factory a {@code Factory} which will be used to instantiate
*                new {@code ViewModels}
*/
public ViewModelProvider(@NonNull ViewModelStorestore, @NonNull Factory factory) {
    mFactory = factory;
    this.mViewModelStore= store;
}
    构造方法中先给两个成员变量赋值,然后通过ViewModelStore的get方法获取ViewModel对象
        viewModel = mFactory .create(modelClass);
        mViewModelStore.put(key,viewModel);
    如果获取不到传入类的ViewModel 就通过工厂类Factory创建一个新的viewModel 通过put方法添加到ViewModelStore中。
简而言之就是利用Fragment的方式去获取生命周期,然后再利用工厂类创建ViewModel。
关于在一定范围内的唯一性,因为ViewModelStore是HoldFragment的成员变量,HoldFragment是通过FragmentManager添加到指定的Activity/Fragment,那么对于当前的宿主,只有一个HoldFragment,也就只有一个ViewModelStore,同时也就只有一个ViewModel。
 
ViewModel的在Fragment间的数据分享
     有时候一个Activity中的两个或多个Fragment需要分享数据或者相互通信,这样就会带来很多问题,比如数据获取,相互确定生命周期。
        ViewModel可以很好的解决该类问题。有两个Fragment,一个Fragment提供点击每个item显示的详情,另一个Fragment提供一个列表。那两个的交互代码应该是如何表现的呢?
实例代码如下:
//ViewModel
public class SharedViewModel extends ViewModel { 
    private final MutableLiveData<Item> selected = new MutableLiveData<Item>(); 
    
    public void select(Item item) { 
        selected.setValue(item);
     } 
    public LiveData<Item> getSelected() { 
        return selected; 
    } 
//第一个Fragment
public class MasterFragment extends Fragment { 
    private SharedViewModel model; 
    public void onCreate(Bundle savedInstanceState) { 
        super.onCreate(savedInstanceState);
         model = ViewModelProviders.of(getActivity())
                                        .get(SharedViewModel.class);           
          itemSelector.setOnClickListener(item -> { 
                        model.select(item); 
                    }); 
        } 
   } 
    //第二个Fragment
public class DetailFragment extends LifecycleFragment { 
    public void onCreate(Bundle savedInstanceState) { 
        super.onCreate(savedInstanceState); 
        SharedViewModel model = ViewModelProviders.of(getActivity())
            .get(SharedViewModel.class); 
        model.getSelected().observe(this, { 
                    item -> // update UI 
            }); 
        }
    }
两个Fragment都是通过getActivity()来获取ViewModelProvider。这意味着两个Activity都是获取的属于同一个Activity的同一个ShareViewModel实例。
这样做优点如下:
  • Activity不需要写任何额外的代码,也不需要关心Fragment之间的通信。
  • Fragment不需要处理除SharedViewModel以外其他的代码。这两个Fragment不需要知道对方是否存在。
  • Fragment的生命周期不会相互影响,即使用其他Fragment替换其中的一个Fragment,另一个依然能也不受影响。
 

ViewModel和SavedInstanceState对比

    最后前文提到保存简单的数据可以使用Activity自带的SavedInstanceState方法,那这个和viewMOdel的区别是?
    ViewModel使得在屏幕旋转等操作时候保存数据变得很便捷,但是这不能用于应用被系统kill时的持久化数据。举个简单的例子,有一个界面展示国家信息。不应该把整个国家信息放到SavedInstanceState里,而是把国家对应的id放到SavedInstanceState,等到界面恢复时,再通过id去获取详细的信息。这些详细的信息应该被存放在数据库中。说到数据库,下篇文章将会介绍Android Architecture Components提供的Room来操作数据库。
 
小结
    ViewModel其实就是通过给宿主添加Fragment的方式来获取宿主的生命周期。在HoldFragment中持有一个集合用于保存当前宿主的ViewModel,只需要在onDestroy方法中调用集合的clear方法,就能间接调用到ViewModel的onCleared方法了,这样实现了对其生命周期的控制。