Android架构组件(三)——ViewModel

上一篇文章讲到了Android架构组件之LiveData(Android架构组件(二)——LiveData),现在我们再来看看另一个成员ViewModel。

ViewModel是什么?

 ViewModel,从字面上理解的话,我们也能想到它肯定是跟视图(View)以及数据(Model)相关的。正像它字面意思一样,它是负责准备和管理和UI组件(Fragment/Activity)相关的数据类,也就是说ViewModel是用来管理UI相关的数据的。同时ViewModel还可以用来负责UI组件间的通信,这一点后面我们会有例子说明。

为什么需要ViewModel?

 在看过前面两篇介绍Lifecycle和LiveData的内容后,这个问题的基本就不用回答了。既然ViewModel是用来管理和UI组件有关的数据的,而LiveData又是这些数据的持有类,所以在使用LiveData的时候,就自然想到要使用ViewModel了。另外从ViewModel的定义,我们也能看到ViewModel还可以用于UI组件间的通信,回想下当初面试官问你Fragment之间怎么通信的问题,掌握ViewModel后就多了一种高逼格的回答了。

ViewModel基本使用

 前面在讲解LiveData时,我们已经使用了ViewModel,所以它的基本使用,在这里就不在赘述了,忘了的同学可以重新去翻一翻Android架构组件(二)——LiveData。我们主要看下ViewModel的生命周期,以及它用于组件间通信的使用。

ViewModel生命周期

 我们看看ViewModel的生命周期,在官方文档中,用下面这张图来描述ViewModel的生命周期。

 上图是用Activity作为例子,左侧表示Activity的生命周期状态,右侧绿色部分表示ViewModel的生命周期范围。当屏幕旋转的时候,Activity会被recreate,Activity会经过几个生命周期方法,但是这个时候ViewModel还是之前的对象,并没有被重新创建,只有当Activity的finish()方法被调用时,ViewModel.onCleared()方法会被调用,对象才会被销毁。这张图很好的描述了是当Activity被recreate时,ViewModel的生命周期。

  另外,有个注意的地方:在ViewModel中不要持有Activity的引用。为什么要注意这一点呢?从上面的图我们看到,当Activity被recreate时,ViewModel对象并没有被销毁,如果Model持有Activity的引用时就可能会导致内存泄漏。那如果你要使用到Context对象怎么办呢,那就使用ViewModel的子类AndroidViewModel吧。

用于组件间通信

  下面我们看下ViewModel用于Fragment之间通信的例子。

/** * Created by shymanzhu on 2017/12/26. */public class CommunicateViewModel extends ViewModel {    private static final String TAG = "CommunicateViewModel";    private MutableLiveData<String> mNameLiveData;    public LiveData<String> getName(){        if (mNameLiveData == null) {            mNameLiveData = new MutableLiveData<>();        }        return mNameLiveData;    }    public void setName(String name){        if (mNameLiveData != null) {            Log.d(TAG, "setName: " + name);            mNameLiveData.setValue(name);        }    }    @Override    protected void onCleared() {        super.onCleared();        mNameLiveData = null;    }}
/** * Created by shymanzhu on 2017/12/26. */public class FragmentOne extends Fragment {    private CommunicateViewModel mCommunicateViewModel;    public static FragmentOne getInstance(){        return new FragmentOne();    }    @Override    public void onCreate(@Nullable Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        mCommunicateViewModel = ViewModelProviders.of(getActivity()).get(CommunicateViewModel.class);    }    @Nullable    @Override    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {        View view = inflater.inflate(R.layout.fragment_one, container, false);        ButterKnife.bind(this, view);        return view;    }    @OnClick(R.id.btn_set_name)    void onViewClicked(View v){        switch (v.getId()){            case R.id.btn_set_name:                mCommunicateViewModel.setName("Jane");                break;        }    }}
/** * Created by shymanzhu on 2017/12/26. */public class FragmentTwo extends Fragment {    @BindView(R.id.tv_name)    TextView mTvName;    private CommunicateViewModel mCommunicateViewModel;    public static FragmentTwo getInstance(){        return new FragmentTwo();    }    @Override    public void onCreate(@Nullable Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        mCommunicateViewModel = ViewModelProviders.of(getActivity()).get(CommunicateViewModel.class);        mCommunicateViewModel.getName().observe(this, name -> mTvName.setText(name));    }    @Nullable    @Override    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {        View view = inflater.inflate(R.layout.fragment_two, container, false);        ButterKnife.bind(this, view);        return view;    }}

  代码逻辑很简单,首先定义一个CommunicateViewModel类,继承自ViewModel。然后定义两个Fragment:FragmentOne和FragmentTwo。在FragmentOne中改变CommunicateViewModel中LiveData保存的数据,然后在FragmentTwo中会收到数据改变的通知。这样一个过程就完成了FragmentOne和FragmentTwo之间的一次简单的通信。至于其中的原理,相信看过LiveData这篇文章话,这都不是问题了。在上面的例子中有个小陷阱:在初始化mCommunicateViewModel时,ViewModelProviders.of()方法传入的是Activity对象,如果你改成Fragment对象,那FragmentTwo里就只能傻等了,永远不会收到数据改变的通知。因为如果传给方法ViewModelProviders.of()的对象不同时,最终得到的就不是同一个ViewModel对象,这一点也可以通过打印两个Fragment中的mCommunicateViewModel验证。

实现原理

  在讲ViewModel的原理时,我们还是延续前面的传统,先理清ViewModel相关的类图,然后理清ViewModel使用的时序图,最后就是根据时序图分析源码。

类图

 废话不多说,直接上ViewModel相关的类图:

  • ViewModelProviders是ViewModel工具类,该类提供了通过Fragment和Activity得到ViewModel的方法,而具体实现又是有ViewModelProvider实现的。ViewModelProvider是实现ViewModel创建、获取的工具类。在ViewModelProvider中定义了一个创建ViewModel的接口类——Factory。ViewModelProvider中有个ViewModelStore对象,用于存储ViewModel对象。

  • ViewModelStore是存储ViewModel的类,具体实现是通过HashMap来保存ViewModle对象。

  • ViewModel是个抽象类,里面只定义了一个onCleared()方法,该方法在ViewModel不在被使用时调用。ViewModel有一个子类AndroidViewModel,这个类是便于要在ViewModel中使用Context对象,因为我们前面提到是不能在ViewModel中持有Activity的引用。

  • ViewModelStores是ViewModelStore的工厂方法类,它会关联HolderFragment,HolderFragment有个嵌套类——HolderFragmentManager。

类图要说明的内容就是以上几点了,对于UML类图中的关联、聚合、组合的关系,个人觉得比较难区分,在这里找到了一个比较好的回答What is the difference between association, aggregation and composition?

时序图

 在使用ViewModel的例子中,也许你会察觉得到一个ViewModel对象需要的步骤有点多啊,怎么不直接new一个出来呢?在你看到ViewModel的类图关系后,你应该就能明白了,因为是会缓存ViewModel对象的。下面以在Fragment中得到ViewModel对象为例看下整个过程的时序图。

 时序图看起来比较复杂,但是它只描述了两个过程:

  • 得到ViewModel对象。
  • HolderFragment被销毁时,ViewModel收到onCleared()通知。

 读者朋友们可以根据这个时序结合源码看下具体的实现,这个也是我们下面要讲的内容——源码分析

源码分析

 根据上面时序图中的内容,我们从两个方面着手研究下ViewModel相关源码。

获取ViewModel对象

 得到ViweModel对象,首先是通过ViewModelProviders.of()方法得到ViewModelProvider对象,然后调用ViewModelProvider.get()方法得到ViewModel对象。

一、得到ViewModelProvider对象

 直接看ViewModelProviders类的具体实现

public class ViewModelProviders {    @SuppressLint("StaticFieldLeak")    private static DefaultFactory sDefaultFactory;    private static void initializeFactoryIfNeeded(Application application) {        if (sDefaultFactory == null) {            sDefaultFactory = new DefaultFactory(application);        }    }    private static Application checkApplication(Activity activity) {        Application application = activity.getApplication();        if (application == null) {            throw new IllegalStateException("Your activity/fragment is not yet attached to "                    + "Application. You can't request ViewModel before onCreate call.");        }        return application;    }    private static Activity checkActivity(Fragment fragment) {        Activity activity = fragment.getActivity();        if (activity == null) {            throw new IllegalStateException("Can't create ViewModelProvider for detached fragment");        }        return activity;    }    @MainThread    public static ViewModelProvider of(@NonNull Fragment fragment) {        initializeFactoryIfNeeded(checkApplication(checkActivity(fragment)));        return new ViewModelProvider(ViewModelStores.of(fragment), sDefaultFactory);    }    @MainThread    public static ViewModelProvider of(@NonNull FragmentActivity activity) {        initializeFactoryIfNeeded(checkApplication(activity));        return new ViewModelProvider(ViewModelStores.of(activity), sDefaultFactory);    }    @MainThread    public static ViewModelProvider of(@NonNull Fragment fragment, @NonNull Factory factory) {        checkApplication(checkActivity(fragment));        return new ViewModelProvider(ViewModelStores.of(fragment), factory);    }    @MainThread    public static ViewModelProvider of(@NonNull FragmentActivity activity,            @NonNull Factory factory) {        checkApplication(activity);        return new ViewModelProvider(ViewModelStores.of(activity), factory);    }    @SuppressWarnings("WeakerAccess")    public static class DefaultFactory extends ViewModelProvider.NewInstanceFactory {        private Application mApplication;        /**         * Creates a {@code DefaultFactory}         *         * @param application an application to pass in {@link AndroidViewModel}         */        public DefaultFactory(@NonNull Application application) {            mApplication = application;        }        @NonNull        @Override        public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {            if (AndroidViewModel.class.isAssignableFrom(modelClass)) {                //noinspection TryWithIdenticalCatches                try {                    return modelClass.getConstructor(Application.class).newInstance(mApplication);                } catch (NoSuchMethodException e) {                    throw new RuntimeException("Cannot create an instance of " + modelClass, e);                } catch (IllegalAccessException e) {                    throw new RuntimeException("Cannot create an instance of " + modelClass, e);                } catch (InstantiationException e) {                    throw new RuntimeException("Cannot create an instance of " + modelClass, e);                } catch (InvocationTargetException e) {                    throw new RuntimeException("Cannot create an instance of " + modelClass, e);                }            }            return super.create(modelClass);        }    }}

 ViewModelProviders提供了四个of()方法,四个方法功能类似,其中of(FragmentActivity activity, Factory factory)和of(Fragment fragment, Factory factory)提供了自定义创建ViewModel的方法。我们在时序图中使用of(Fragment fragment)最为研究对象,这里我们也从该方法开始研究,在该方法中主要有两个步骤。

1,判断是否需要初始化默认的Factory

 这一过程很简单:通过调用initializeFactoryIfNeeded()方法判断是否需要初始化mDefaultFactory变量。在此之前又会判断Fragment的是否Attached to Activity,Activity的Application对象是否为空。

2,创建一个ViewModelProvider对象。

 创建ViewModel对象看似很简单,一行代码搞定new ViewModelProvider(ViewModelStores.of(fragment), sDefaultFactory)。但是里面的里面的逻辑比较深,我们慢慢把它抽丝剥茧,看看有多深。

 先看看ViewModelStores.of()方法

@MainThreadpublic static ViewModelStore of(@NonNull Fragment fragment) {    return holderFragmentFor(fragment).getViewModelStore();}

 又是一行代码,holderFragmentFor()是HolderFragment的静态方法,HolderFragment继承自Fragment。我们先看holderFragment()方法的具体实现

/** * @hide */@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)public static HolderFragment holderFragmentFor(Fragment fragment) {    return sHolderFragmentManager.holderFragmentFor(fragment);}public ViewModelStore getViewModelStore() {    return mViewModelStore; //返回ViewModelStore对象}

 额,holderFragmentFor()又是一行代码,继续看HolderFragmentManager.holderFragmentFor()方法的具体实现

HolderFragment holderFragmentFor(Fragment parentFragment) {    FragmentManager fm = parentFragment.getChildFragmentManager();    HolderFragment holder = findHolderFragment(fm); // 在activity和back stack中查找parentFragment    if (holder != null) {        return holder;    }    holder = mNotCommittedFragmentHolders.get(parentFragment);     if (holder != null) {        return holder;    }    // 注册Fragment生命周期回调    parentFragment.getFragmentManager()            .registerFragmentLifecycleCallbacks(mParentDestroyedCallback, false);    holder = createHolderFragment(fm); //创建HolderFragment对象。    mNotCommittedFragmentHolders.put(parentFragment, holder);    return holder;}private FragmentLifecycleCallbacks mParentDestroyedCallback =    new FragmentLifecycleCallbacks() {        @Override        public void onFragmentDestroyed(FragmentManager fm, Fragment parentFragment) {            super.onFragmentDestroyed(fm, parentFragment);            HolderFragment fragment = mNotCommittedFragmentHolders.remove(                    parentFragment);            if (fragment != null) {                Log.e(LOG_TAG, "Failed to save a ViewModel for " + parentFragment);            }        }    };private static HolderFragment createHolderFragment(FragmentManager fragmentManager) {    HolderFragment holder = new HolderFragment(); // 创建HolderFragment对象    fragmentManager.beginTransaction().add(holder, HOLDER_TAG).commitAllowingStateLoss();    return holder;}public HolderFragment() {    //这个是关键,这就使得Activity被recreate时,Fragment的onDestroy()和onCreate()不会被调用    setRetainInstance(true); }

 在createHolderFragment()方法中,你也许发现调用FragmentTransaction.add()方法并没有传一个定义在layout文件中的id进去,而且你去看HolderFragment并没有重写Fragment.onCreateView()方法。这个有点反常,这是为什么呢? 这个疑惑可以从HolderFragment()构造方法中得到解答。在构造方法中调用了setRetainInstance(true)方法,该方法会使得HolderFragment所在的Activity被recreate时,HolderFragment不会被recreate,自然它就不会走onDestroy()、onCreate()方法。为什么不让HolderFragment被recreate呢?这里先卖个关子,等到在讲时序图中步骤二时我们再讲。

 到此为止,我们已经得到了ViewStore对象,前面我们在创建ViewModelProvider对象是通过这行代码实现的new ViewModelProvider(ViewModelStores.of(fragment), sDefaultFactory)现在再看下ViewModelProvider的构造方法

public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {    mFactory = factory;    this.mViewModelStore = store; // 这个ViewStore对象实际上是HolderFragment中的ViewStore对象。}
二、得到ViewModel对象

 在上面我们已经得到了ViewModelProvider对象,现在就可以通过ViewModelProvider.get()方法得到ViewModel对象,继续看下该方法的具体实现

public <T extends ViewModel> T get(@NonNull Class<T> modelClass) {    String canonicalName = modelClass.getCanonicalName();    if (canonicalName == null) {        throw new IllegalArgumentException("Local and anonymous classes can not be ViewModels");    }    return get(DEFAULT_KEY + ":" + canonicalName, modelClass);}@MainThreadpublic <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {    ViewModel viewModel = mViewModelStore.get(key);  //从缓存中查找是否有已有ViewModel对象。    if (modelClass.isInstance(viewModel)) {        //noinspection unchecked        return (T) viewModel;    } else {        //noinspection StatementWithEmptyBody        if (viewModel != null) {            // TODO: log a warning.        }    }    viewModel = mFactory.create(modelClass); //创建ViewModel对象,然后缓存起来。    mViewModelStore.put(key, viewModel);    //noinspection unchecked    return (T) viewModel;}

  ViewModelProvider.get()方法比较简单,该说的都在注释中写明了。最后我们看下ViewModelStore类的具体实现

public class ViewModelStore {    private final HashMap<String, ViewModel> mMap = new HashMap<>();    final void put(String key, ViewModel viewModel) {        ViewModel oldViewModel = mMap.get(key);        if (oldViewModel != null) {            oldViewModel.onCleared();        }        mMap.put(key, viewModel);    }    final ViewModel get(String key) {        return mMap.get(key);    }    /**     *  Clears internal storage and notifies ViewModels that they are no longer used.     */    public final void clear() {        for (ViewModel vm : mMap.values()) {            vm.onCleared();        }        mMap.clear();    }}

 ViewModelStore是缓存ViewModel的类,put()、get()方法用于存取ViewModel对象,另外提供了clear()方法用于清空缓存的ViewModel对象,在该方法中会调用ViewModel.onCleared()方法通知ViewModel对象不再被使用。

####ViewModel收到onCleared()通知。

 在前面我们提到,在HolderFragment的构造方法中调用了setRetainInstance(true);目的是为了在其所在的Activity被recreate时,HolderFragment不会被recreate,为什么要这么做呢(之前面卖的关子),那就要看下它的onDestroy()方法了。

@Overridepublic void onDestroy() {    super.onDestroy();    mViewModelStore.clear();}

 在onDestroy()方法中调用了ViewModelStore.clear()方法,我们知道在该方法中会调用ViewModel的onCleared()方法。在你看了HolderFragment源码后,或许你会有个疑问,mViewModelStore保存的ViewModel对象是在哪里添加的呢? 细心的话,你会发现在ViewModelProvider的构造方法中,已经将HolderFragment中的ViwModelStore对象mViewModelStore的引用传递给了ViewModelProvider中的mViewModelStore,而在ViewModelProvider.get()方法中会向mViewModelStore添加ViewModel对象。

总结

 总结,额…还是由你们来完成,抛一个问题来作为总结吧:为什么ViewModel的生命周期比使用它的Activity/Fragment的生命周期更长呢? 如果你有认真看完这篇文章,我相信这个问题是难不倒你,如果没想到,那就多看几遍吧。最后,提供一张图来帮助你想这个问题。

更多相关文章

  1. 浅谈Java中Collections.sort对List排序的两种方法
  2. 类和 Json对象
  3. Python list sort方法的具体使用
  4. python list.sort()根据多个关键字排序的方法实现
  5. 转:[Android]实现静默安装APK的两种方法
  6. [Android]实现静默安装APK的两种方法
  7. Unity3D研究院之与Android相互传递消息(十九)
  8. Android(安卓)ActionBar的源代码分析(一)
  9. Android(安卓)模拟物理按键

随机推荐

  1. RN(react-native)踩坑实录--编写项目
  2. 从头学Android之多媒体--使用SoundPool播
  3. Android(安卓)Studio中编译时遇到Finishe
  4. android之xml对象到json对象的转化
  5. Android(安卓)UI开发第十一篇——右上角
  6. 解决 android 下sqlite3_column_table_na
  7. Android相册中获取图片和路径
  8. Mac安装Android(安卓)Studio并配置环境变
  9. Android(安卓)完全退出程序
  10. Android(安卓)Studio引入Lambda以及介绍