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

转载请注明出处:

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

地址:http://www.jianshu.com/p/0eddb079e0c9

目录

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();

   @Override
public 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}
 */
@MainThread
public 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}
 */
@MainThread
public static ViewModelStore of(Fragment fragment) {
    return holderFragmentFor(fragment).getViewModelStore();
}
}

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

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

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

    @MainThread
public 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中的实例,用来监听通信数据交流等。

好了~这次就讲到这里吧

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,319评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,801评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,567评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,156评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,019评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,090评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,500评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,192评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,474评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,566评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,338评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,212评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,572评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,890评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,169评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,478评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,661评论 2 335

推荐阅读更多精彩内容