丢掉EventBus,ViewModel+LiveData用起来

需求背景

ViewModel和LiveData是google官方架构JetPack系列的一个响应式开发框架。ViewModel和LiveData主要用于搭建MVVM架构,能监听组件的生命周期变化,这样一来只会更新处于活跃状态的组件。
在我们工程中,ViewModel和LiveData的使用已经有一段时间,结合这两者的封装,我们在推动项目从MVP到MVVM的过渡。而在之前的过渡过程中,我们还只是用ViewModel来代替Presenter,用LiveData来代替Callback回调。
但最近的项目开发中,遇到了这样一个需求:一个activity的viewpager中有2个Fragment,其中第一个FragmentA支持上下滑动,每次滑动之后要通知到FragmentB刷新数据,这是个类似抖音的交互过程。其实在之前的开发中,我们就遇到过类似的需求,说白了就是一个页面甚至跨页面的多个UI组件的数据联动,这种场景相信在大家的日常开发中也经常遇到。比如在页面的弹框里给影片点击“想看”,那么这个页面甚至所有的“想看”状态都要刷新。而之前的做法无非两种:

  1. 使用回调,将操作回调给Fragment或者activity,再下发给需要更新的组件。这种做法如果只有一两层的回调还好,如果层级多了可能陷入“回调地狱”。 如果是多层次的UI,甚至有用到RecyclerView,是从ViewHolder里发出事件,穿越一层层去到另一个ViewHolder,那就几乎会破坏每一层的封装结构;
  2. 使用EventBus类似机制,或者自己实现的静态全局回调;这种机制通过一种类似全局总线的机制来解决问题。这种观察者模式需要自己去维护监听与移除,如果注册或者更新时机不对有可能造成内存泄漏甚至崩溃;但是EventBus或者类似的机制也存在滥用的痛点,一旦工程中使用太多,有可能造成event满天飞,到处都是收发的地方,给后期的维护带来困难;

用法与原理简介

现在我们有了ViewModel+LiveData,多了一种新思路来解决这个问题,这里首先要介绍一下ViewModel的用法,一个典型的ViewModel的使用示例:

//构建ViewModel实例
final FilmViewModel filmViewModel = ViewModelProviders.of(this).get(FilmViewModel class);

//观察ViewModel中数据的变化,并实时展示
filmViewModel.getFilmInfo().observe(this, new Observer() {
    @Override
    public void onChanged(FilmInfo filmInfo) {
        // reload
    }
});

findViewById(R.id.btn_test).setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        //点击按钮  更新Film
        filmViewModel.switchFilm();
    }
});

上面的示例可以看到,viewmode的对象不是new出来的,而是通过ViewModelProviders的get方法get出来的,下面看一下两个方法的内部实现:

@MainThread
public static ViewModelProvider of(@NonNull FragmentActivity activity,
        @Nullable Factory factory) {
    //检查application是否为空,不为空则接收
    Application application = checkApplication(activity);
    if (factory == null) {
        //构建一个ViewModelProvider.AndroidViewModelFactory
        factory = ViewModelProvider.AndroidViewModelFactory.getInstance(application);
    }
    return new ViewModelProvider(activity.getViewModelStore(), factory);
}

@NonNull
@MainThread
public static ViewModelProvider of(@NonNull Fragment fragment, @Nullable Factory factory) {
    Application application = checkApplication(checkActivity(fragment));
    if (factory == null) {
        factory = ViewModelProvider.AndroidViewModelFactory.getInstance(application);
    }
    return new ViewModelProvider(ViewModelStores.of(fragment), factory);
}

ViewModelProvider只是个包装类,真正的viewmode缓存是在ViewModelSotre中,而ViewModelStore的获取方法实现如下:

    @NonNull
    @MainThread
    public static ViewModelStore of(@NonNull Fragment fragment) {
        if (fragment instanceof ViewModelStoreOwner) {
            return ((ViewModelStoreOwner) fragment).getViewModelStore();
        }
        return holderFragmentFor(fragment).getViewModelStore();
    }

只要是support包26.1.0之后的Fragment和FragmentActivity,都实现了ViewModelStoreOwner接口,而实现的方式也很简单,就是持有了一个ViewModelStore,那这个东西又是什么呢?看下源码就知道:

public class ViewModelStore {

    private final HashMap mMap = new HashMap<>();

    final void put(String key, ViewModel viewModel) {
        ViewModel oldViewModel = mMap.put(key, viewModel);
        if (oldViewModel != null) {
            oldViewModel.onCleared();
        }
    }

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

其实没有什么特别的,就可以理解成一个HashMap的封装,而我们的ViewModel会用类名等参数做为主key被存储在这里。所以挖了大概三层方法,看似很长,结论其实很简单,就是在ViewModelProviders.of(a).getViewModel(XXXViewModel.class) 这样一个调用中,如果你传入的a是一样的,获取同样的XXXViewModel永远是同一个对象,除非activity或者fragment被onDestroy销毁重建;

解决问题

利用这样的特性,我们就可以用来作为同一个activity或者同一个fragment中的通信,比如以下这些场景:

  1. 同一个Fragment上不同UI组件的通信,都通过当前fragment去获取viewmodel,然后操作viewmodel中的数据即可做到一处更改,处处更新;
  2. 同一个activity上fragment之间的通信,都通过当前activity去获取viewmodel,然后操作viewmodel中的数据;
    这种用法,既避免了一层层的回调地狱,也不用定义一个个event,只需要修改数据。而且最重要的是,这一切建立在ViewModel+LiveData基础上,意味着都是对组件的生命周期敏感的,作为开发者我们一不用考虑各种各样的register和unregister场景,避免内存泄漏,也不用管因为生命周期引起的销毁重建导致的数据不一致问题。
    贴一段网上关于LiveData的特点介绍可以帮助理解:

LiveData的特点:
1)采用观察者模式,数据发生改变,可以自动回调(比如更新UI)。
2)不需要手动处理生命周期,不会因为Activity的销毁重建而丢失数据。
3)不会出现内存泄漏。
4)不需要手动取消订阅,Activity在非活跃状态下(pause、stop、destroy之后)不会收到数据更新信息。

深层思考

这里要更深挖一层,对于ViewModel+LiveData的合理使用有一个更深的理解。以往的使用中,我们将ViewModel作为MVP下Presenter的替代,只是将逻辑换了一种写法,但其实ViewModel的封装应该更进一步。比如之前的MVP模式下,P层很少会将数据缓存下来,而是View层调用P层接口,P层通过M层完成数据获取或更新后,直接把数据在回调回V层,P层的每个方法其实是独立的。但在ViewModel的实现中,应该更进一步的将数据封装在ViewModel中,而数据的增查改删都由ViewModel开放接口给V层调用,数据改变之后直接setValue到LiveData中通知到各个观察者。
一个具体的例子,还是影片信息的页面,之前的Presenter或者原来我们写ViewModel的时候会把FilmInfo的load,修改其中某个状态(点赞、想看)抽成一个个相互独立的接口,然后View层调用接口,根据接口的成功失败来做页面的刷新。但其实真正的MVVM里,V层应该更薄,所有的数据处理都在ViewModel中完成,ViewModel在拿到M层的接口数据后,不是简单的处理下就回调给View,而是直接修改FilmInfo数据,而View层的各个组件只管监听FilmInfo的变化就可以做到实时更新。

可能的坑

把LiveData当做event使用也是有一些可能的坑的,下面是通过自己试验和网上资料整理的风险点,但需要说明的是这些都是特定情境下不满足某种需求,需要根据业务的实际需要来决定是否值得考虑:

  1. setValue()在放入User的时候必须在主线程,否则会报错,而postValue则没有这个检查,而是会把数据传入到主线程。
  2. 使用LiveData监听数据变化,LifeCycleOwner在inactive状态下,不会观察到value变化(除非使用observeForever替代observe,但是使用observeForever需要自己管理observer,手动remove),在页面回到active状态后,会收到刚刚变化的值的最后一个value,中间的变化过程会丢失;

你可能感兴趣的:(丢掉EventBus,ViewModel+LiveData用起来)