使用ViewModel共享页面内的数据:ActivityDataBus

来源:互联网 发布:逆战透视源码 编辑:程序博客网 时间:2024/06/14 21:16

转载请注明出处:

使用ViewModel共享页面内的数据:ActivityDataBus

地址:http://blog.csdn.net/qq_22744433/article/details/78195155

目录

1. 前言

Google lifecycle-component推出了Lifecycle Manager、ViewModel、LiveData、Rom等重要的类。之前也在官网看了ViewModel的介绍,但并不感冒(感觉Lifecycle Manager确实对解耦用处很大,之前也写了文章,感兴趣的可以看看。但LiveData和ViewModel,感觉并不是很实用。LiveData可以用Observable来代替,只是多了生命周期感知。ViewModel只是能在屏幕旋转的时候保存数据。)。但最近回家在火车上没事,又翻了翻之前看的内容,突然觉得ViewModel还是很有用的。可以解耦页面block或fragment之间数据/view等的相互调用。直白点说就是页面block/fragment之间需要使用对方的数据/view时,无需之间硬性的引用,只需要activity的context参数就可以获取对方的数据/view,从而进行数据交流、view访问。而页面的context是系统类型且是很容易获取的,并不存在耦合。

2. 总体概述

如果大家比较忙,没时间看下面的内容,我这里给大家做了一个概述:

使用 Viewholder方式:ViewModelProviders.of(宿主activity).get(A.class) 其中A extend ViewHolder

用处:一个activty内,任何block,adapter,view类中都可“无显式耦合”的获得彼此间的数据。举例:我们可以在activty一开始就存一个movieId的viewHolder,那么这个activty涉及的所有类中都可以使用context来获取movieId。这对于埋点等都是很好帮助的,避免了级联引用。

大致原理:ViewModelProviders.of()用于获取ViewModelProvider实例。ViewModelProvider中含有一个ViewModelStore,ViewModelStore是用来存储viewModel的(ViewModelStore内部含有map)。ViewModelStore对于宿主activity是唯一的。其实质是宿主activity中HoldFragment的一个成员变量。

3. 创建页面Bus来共享界面数据

稍微封装一下ViewModel,使用bus来管理页面内的共享数据。
我们一个activity页面肯定不止一个类。尤其是页面比较复杂的时候,一个页面有很多block。如果一个block中的某些数据/view需要另外一个block中使用,那怎么办呢?我们一般的做法就是把这个数据存成一个成员变量,set到另外一个block中。或另外一个block需要时,直接拿这个成员变量。但这样会造成,两个block之间之间耦合。设想一下,如果两个block层级比较深,那么两个block之间进行共享数据时,需要把两个block之间需要的类都进行之间耦合。

举一个我以前遇到过的例子:一个页面做完了,pm找我做页面的埋点。埋点需要页面的movieId信息,但是需要埋点的那个block中并没有movieId。并且我这个block层级很深。如果想拿到movieId,我需要从activity页面层级一层层传到我这个block中,免不了中间层级的耦合和方法的创建。当时觉得这件事真是让人头大。那时候多么需要有个像事件监听形式的eventbus那样的东西,我只需要把数据放到bus里面,然后这个页面的任何一个地方都能很方便的获取。现在有了viewModel就可以这么做了:

     public class ActivityDataBus {public static <T extends ActivityShareData> T getData(Context context, Class<T> tClass) {    return getData(checkContext(context),tClass);}public static <T extends ActivityShareData> T getData(Activity context, Class<T> tClass) {    return getData(checkContext(context),tClass);}public static <T extends ActivityShareData> T getData(FragmentActivity context, Class<T> tClass) {    return ViewModelProviders.of(context).get(tClass);}private static FragmentActivity checkContext(Context context) {    if(context instanceof FragmentActivity) return (FragmentActivity) context;    throw new IllegalContextException();}public static class ActivityShareData extends ViewModel {}public static class IllegalContextException extends RuntimeException {    public IllegalContextException() {        super("ActivityDataBus 需要FragmentActivity作为上下文!");    }}}

4. ViewModel源码解析

为了深入理解上面功能的具体实现,以及ViewModel怎么在屏幕旋转时仍然保持数据。最好对ViewModel进行源码解析,并且源码并不难,所以这里阐述一下。好的,那么开始吧~
我们从ViewModelProvider入手。ViewModel通过ViewModelProvider的

 * Returns an existing ViewModel or creates a new one in the scope (usually, a fragment or * an activity), associated with this {@code ViewModelProvider}. * <p> * The created ViewModel is associated with the given scope and will be retained * as long as the scope is alive (e.g. if it is an activity, until it is * finished or process is killed).  public <T extends ViewModel> T get(Class<T> modelClass)

方法来获取。如果没有modelClass对应的ViewModel,那么会新生成一个。ViewModel的存在时间和相应的的宿主一致,下面会说。传入的modelClass类型需要是ViewModel的子类。

ViewModelProvider怎么创建?通过ViewModelProviders.of(宿主activty/fragment).为了便于理解。先说下ViewModelProvider构造。
ViewModelProvider构造的时候需要提供ViewModelStore和ViewModelProvider.Factory

其中ViewModelProvider.Factory用了产生viewHolder:

   public interface Factory {    /**     * Creates a new instance of the given {@code Class}.     * <p>     *     * @param modelClass a {@code Class} whose instance is requested     * @param <T>        The type parameter for the ViewModel.     * @return a newly created ViewModel     */    <T extends ViewModel> T create(Class<T> modelClass);}

其实现一般是使用ViewModelProviders#DefaultFactory:

   @Override    public <T extends ViewModel> T create(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);    }

super.create()为:

  @Override    public <T extends ViewModel> T create(Class<T> modelClass) {        //noinspection TryWithIdenticalCatches        try {            return modelClass.newInstance();        } catch (InstantiationException e) {            throw new RuntimeException("Cannot create an instance of " + modelClass, e);        } catch (IllegalAccessException e) {            throw new RuntimeException("Cannot create an instance of " + modelClass, e);        }    }

即,如果modelClass类型是AndroidViewModel的子类,那么使用带Application参数的构造生成modelClass实例。如果是ViewModel的子类,那么使用空构造生成modelClass实例。

ViewModelStore用来存储ViewModels:

       private final HashMap<String, ViewModel> mMap = new HashMap<>();

ViewModelStore实例怎么来呢?

ViewModelStore不是随便new出来的,而是在HolderFragment中实例化的。ViewModelStore依赖于HolderFragment而存在:

 public class HolderFragment extends Fragment {   private ViewModelStore mViewModelStore = new ViewModelStore();   @Overridepublic void onDestroy() {    super.onDestroy();    mViewModelStore.clear();}public ViewModelStore getViewModelStore() {    return mViewModelStore;}...}

HolderFragment的产生:通过一个宿主activity/fragment获取已经存在的或新产生的一个HolderFragment。HolderFragment的生命周期和其宿主的相同。看下怎么获取的,宿主以activty为例:

   HolderFragment holderFragmentFor(FragmentActivity activity) {        FragmentManager fm = activity.getSupportFragmentManager();        HolderFragment holder = findHolderFragment(fm);        if (holder != null) {            return holder;        }        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;    }

通过findHolderFragment()先看是不是已经存在tag为HOLDER_TAG的Fragment:

           Fragment fragmentByTag = manager.findFragmentByTag(HOLDER_TAG);

注意,旋转屏幕时,activty内置的HolderFragment不会被销毁(为什么旋转时不会被销毁,是因为在HolderFragment中指定了setRetainInstance(true);),且fragmentManager.findFragmentByTag(HOLDER_TAG)能够找到内置的HolderFragment。
因为HolderFragment中存储map ,所以这就解释了为什么activity在旋转的时候,viewModel不会丢失。

旋转屏幕时,原来的activty会被销毁,重新生成一个新的activty。
HolderFragmentManager中map类型的成员变量mNotCommittedActivityHolders是干什么的呢?不是为了屏幕旋转问题而生的。他里面对应的Entry的生存时间是“new出来HolderFragment时刻 到HolderFragment执行onCreate()执行完的时刻”,如果HolderFragment始终没有执行onCreate()那么等到activty销毁的时候,会清除mNotCommittedActivityHolders中对应的Entry。既然不是为屏幕旋转问题而生,那为什么要弄个这个呢?因为fragment在commit()以后不会立即执行commit()动作,会使用handler来把这次的commit放到主线程消息队列中,等待执行。所以如果fragment在没有真正执行commit()动作的时候,使用manager.findFragmentByTag(HOLDER_TAG)找不到对应的HolderFragment,那么用mNotCommittedActivityHolders这样方式来兜底。

回到ViewModelStore那里。
所以我们要想得到一个ViewModelStore,我们需要先根据宿主产生一个HolderFragment,再拿到里面初始化好的ViewModelStore成员变量。使用ViewModelStores工具类即可:

 public class ViewModelStores {private ViewModelStores() {}/** * Returns the {@link ViewModelStore} of the given activity. * * @param activity an activity whose {@code ViewModelStore} is requested * @return a {@code ViewModelStore} */@MainThreadpublic static ViewModelStore of(FragmentActivity activity) {    return holderFragmentFor(activity).getViewModelStore();}/** * Returns the {@link ViewModelStore} of the given fragment. * * @param fragment a fragment whose {@code ViewModelStore} is requested * @return a {@code ViewModelStore} */@MainThreadpublic static ViewModelStore of(Fragment fragment) {    return holderFragmentFor(fragment).getViewModelStore();}}

其中of(FragmentActivity activity)/of(Fragment fragment) 参数就是宿主。

所以ViewModelProvider是这么产生的
(在ViewModelProviders中,ViewModelProviders是生产ViewModelProvider的工具类):

   @MainThreadpublic static ViewModelProvider of(@NonNull FragmentActivity activity) {    initializeFactoryIfNeeded(activity.getApplication());    return new ViewModelProvider(ViewModelStores.of(activity), sDefaultFactory);}    @MainThreadpublic static ViewModelProvider of(@NonNull FragmentActivity activity) {    initializeFactoryIfNeeded(activity.getApplication());    return new ViewModelProvider(ViewModelStores.of(activity), sDefaultFactory);}

所以使用ViewModelProviders of(宿主)方法来获得的ViewModelProvider。ViewModelProvider里面的viewModel的生存时间和宿主一致。

5. serviceloader与ViewModel在解耦方法的异同

我们知道serviceloader.getService(applicationContex,xxx.class)是获取一个全局唯一的服务实例,或者new一个服务实例。并不能获取一个非单例的实例(比如在A block生成了一个view,然后B block想获取这个view,并不能通过serviceloader来实现这种解耦),而如果A block 和B block是同一个activty页面,那么使用
ViewModelProviders.of((FragmentActivity) getContext()).get(MajorCommentViewViewModel.class)是可以做到的。这让我们又多了一种解耦方法。甚至,如果方便的话,在同一个activty页面,我们不使用set() get()方法就可以在不用block类中使用其他block中的实例,用来监听通信数据交流等。

好了~这次就讲到这里吧

阅读全文
1 0