一步步搭建一个MVVM开发架构,以及常见问题的解决方案

注意,本文介绍的架构基于Dagger2、DataBinding以及Android架构组件,如果对这些不熟悉,建议先去简单了解一下再来看此文章,以免浪费你的时间。

先上地址,你的star是我分享的动力

本文主要记录此架构搭建的思路以及过程中遇到的问题,再到问题的解决,希望给同样在研究MVVM的朋友一点帮助,也希望把自己的思路讲述出来,听取一下大家的建议,共同进步。

整体介绍

MVVM和MVP的区别主要在于把P层换成了VM层,MVP中业务逻辑写在P层,P层持有M层和V层的引用,通过接口主动调用两个层的方法。而MVVM中,业务逻辑依然在VM层,不过它只持有M层的引用,不可持有V层的引用(会内存泄漏),而数据到视图的转换使用DataBinding来自动完成。相同的,两种架构的V层都持有“业务逻辑层”(P或VM)的引用。对MVVM就说这么多,还不清楚的请先去了解一下MVVM。

下面看项目,主要看mvvmarch这个module,如下图

其中di(依赖注入)下面目前只定义了两个Dagger的scope,PerActivity和PerFragment,不必多说。

下面介绍mvvm下的类和接口。首先IModel、IView、IViewModel这三个接口,它们并未定义任何方法,是空接口,主要作用就是单纯地标记M、V、VM三个层,在它们的基础上进一步细化我们的要求,从而细化出了IArchModel、IArchView、ArchViewModel。

那我们有什么要求呢?

M层:暂时没想到什么要求,所以IArchModel接口继承自IModel,也没有添加新的方法。

VM层:首先它是VM层,要持有M层的引用;然后我希望能使用架构组件里的ViewModel(为什么?因为我看到它名字的第一时间想到的就是MVVM里的ViewModel层啊!开玩笑的,主要因为它解决了屏幕旋转时Activity重建,数据保存的问题),所以ArchViewModel要继承自ViewModel;另外我还希望引入架构组件里的LifeCycle,让VM层能感知到V层的生命周期,所以ArchViewModel要实现LifecycleObserver接口。

最终ArchViewModel是这样的

public abstract class ArchViewModel extends ViewModel implements IViewModel, LifecycleObserver {
    @Inject
    protected M mModel;
}
复制代码

V层:在Android中V层主要就是Activity和Fragment,IArchView接口中的方法我并不是一开始就确定好要有哪些了,而是在编写ArchActivity和ArchFragment时,把里面公有的方法提出来而有的一个接口(待会儿来分析ArchActivity)。可以确定的是V层要持有VM的引用,所以有个VM的泛型;要使用DataBinding,所以要有一个对应的B(ViewDataBinding)泛型;最后,要使用Dagger来注入,所以有一个C(Component)的泛型。

下面看一下ArchActivity

public abstract class ArchActivity extends AppCompatActivity implements IArchView {
    protected B mBinding;
    @Inject
    protected VM mViewModel;

    /**
     * 在子类的buildComponent方法中实例化
     */
    protected C mComponent;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //data binding
        mBinding = DataBindingUtil.setContentView(this, getLayoutId());
        //要使用livedata,需要设置lifecycle owner
        mBinding.setLifecycleOwner(this);

        //build dagger component
        mComponent = buildComponent();

        //用mComponent执行inject操作
        executeInject(mComponent); //此方法执行后mViewModel已经有值,但此时只是简单地通过构造器注入的,下面的代码会以标准的factory方式实现

        //获取mViewModel
        mViewModel = ViewModelProviders.of(this, new ViewModelInstanceFactory(mViewModel)).get(getViewModelClazz());

        //为lifecycle添加observer,viewmodel已经实现了LifecycleObserver接口
        this.getLifecycle().addObserver(mViewModel);
    }

    @Override
    public C getComponent() {
        return mComponent;
    }

    @Override
    public Class getViewModelClazz() {
        //注意!!默认通过反射获取ViewModel的class,如果对稳定性与性能有要求,请在子类中重写此方法,返回viewmodel的class
        return (Class) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[1];
    }
}
复制代码

ArchFragment中也是类似的代码,databinding需要用布局id,所以IArchView中定义一个getLayoutId方法;Dagger注入部分,我把Component的build和inject分开了,向外提供getComponent方法,主要是考虑这样一种情况,一个Activity中有一个Fragment,可能Fragment的Component依赖外层Activity的Component,那就可以在Fragment中getActivity().getComponent()方便地获取;ViewModel这里先不说,我会在下边详细介绍。

重点问题

Dagger组织方式

采用的这篇文章(Dagger2实战(详细))中介绍的第二种依赖方式

参考UserComponent

@PerActivity
@Component(modules = arrayOf(UserModule::class,CommonActivityModule::class), dependencies = arrayOf(AppComponent::class))
interface UserComponent {
    fun inject(activity: UserActivity)
}
复制代码

AppComponent在自定义的Application中初始化,提供全局的实例,如gson、application等。CommonActivityModule在BaseActivity中实例化,提供Activity中通用的实例,如RxPermissions等。UserComponent依赖AppComponent,modules为UserModule(提供一些useractivity独有的东西)和CommonActivityModule(提供Activity通用的东西)。建议不懂的看一下上边那篇文章。

如何用Dagger注入ViewModel

在写如何注入ViewModel时我参考了一些mvp项目中注入Presenter的方式,一般都是用构造器注入。但是ViewModel是这样创建的 MyViewModel model = ViewModelProviders.of(this,factory).get(MyViewModel.class);,如果直接用构造器注入,那它就只是一个普通的对象,不拥有ViewModel的所有特性。ViewModel的源码很简单,核心就是用了一个没有视图的HolderFragment来存放ViewModel,get的时候根据Class去找对应的ViewModel,如果有就返回,没有就用factory的create方法创建一个,存到holderfragment中,并返回。而它之所以能在屏幕旋转Activity重建时幸存下来,是因为HolderFragment调用了setRetainInstance(true),也就是不管你屏幕旋转多少次,得到的holderfragment都是同一个,得到的某个class对应的ViewModel也是同一个。如果希望使用ViewModel的有参构造器,通常是把参数传给factory,在factory的create方法中用这些参数return new MyViewModel(xx,xxx)(可以看下这个)。

我的方案,先用构造器的方式注入,和mvp的presenter一样。然后使用这样一个ViewModelInstanceFactory,在factory的构造器中传入mViewModel,此时的mViewModel只是通过Dagger注入了,但这时还不具有ViewModel的特性,还没存到holderfragment中。在create方法中把mViewModel又原封不动地返回来了。但这时mViewModel就已经被存到holderFragment中了,是一个真正的ViewModel了。后边如果手机屏幕旋转,Activity重建,Dagger会重新注入,在mViewModel = ViewModelProviders.of(this, new ViewModelInstanceFactory(mViewModel)).get(getViewModelClazz());这句代码之前mViewModel是一个通过构造器创建的新ViewModel,但是执行这句代码之后,返回的还是之前第一次的mViewModel,因为这时候从holderfragment根据class找的话是有之前viewmodel的实例的,就直接返回之前的了。

public class ViewModelInstanceFactory extends ViewModelProvider.NewInstanceFactory {
    private VM mViewModel;

    public ViewModelInstanceFactory(VM mViewModel) {
        this.mViewModel = mViewModel;
    }

    @NonNull
    @Override
    public  T create(@NonNull Class modelClass) {
        return (T) mViewModel;
    }
}
复制代码

如何进行页面跳转

如果是简单的不涉及业务逻辑的跳转,可以在View层直接Intent跳转。但是很多情况下跳转时是依赖于业务逻辑的,比如说跳转页面时要携带一些业务数据,这时候就需要在ViewModel层进行页面跳转了,正如我们之前所说,ViewModel不可以持有View的引用,那怎么通过context跳转页面呢?

如果只是跳转,不涉及startActivityForResult可以采用Arouter,虽然没用过,但看了一下使用说明,它是不需要activity或context作为参数的。但涉及到forResult的,还是需要一个Activity参数,那怎么解决呢?首先你当然可以选用EventBus或RxBus的方式,其次,如果我非要使用Activity呢?接下来会讲如何在ViewModel中获取Activity。

如何在ViewModel中获取Activity

几乎所有介绍架构组件ViewModel的文章都告诉你不能引用Activity,但却没告诉你在你非用Activity不可的情况该怎么办。这里提供两种方法,推荐第二种

  1. 使用弱引用。但是要注意屏幕旋转Activity重建的情况,在activity销毁的时候,弱引用get到的就是null了,所以在新的activity创建的时候要在onCreate时及时为弱引用赋值。
  2. 正如之前介绍,ViewModel是存在于holderfragment中的,而holderfragment是不受activity重建影响的,那如果我们能持有holderfragment的引用,在需要activity的时候调用holderFragment.getActivity()就可以得到此时的Activity了。获取holderFragment的方法,HolderFragment.holderFragmentFor(activity),可以在CommonActivityModule中写一个provideHolderFragment方法提供holderFragment,在ViewModel中用@Inject注入就可以了。这种方式我在网上没找到先例,目前暂时没想到会有什么潜在的问题,如果你发现什么问题的话,欢迎评论告诉我。

VM如何处理Activity的回调方法,如onActivityResult、onNewIntent、onXXX……

比如我们在VM层startActivityForResult,该怎么处理获取到的结果呢?首先我们知道V层是持有VM层的引用的,我们当然可以在Activity的onActivityResult方法中调用VM的public方法,但是为了保证V层拥有最少的业务逻辑,可以看下我这篇文章如何避免使用onActivityResult,以提高代码可读性,采用的也是类似holderFragment的方式,让fragment去startForResult。请求权限可以用RxPermissions的方式,也是这种原理。如果万不得已的话,也可以直接调用VM的public的方法,但是推荐不要滥用。

多状态视图的处理

MVVM一般通过数据来驱动UI(当然也可以做到双向),例子中使用的是LoadSir,当然你也可以使用其他的,只是在我收藏的此类库中只有三个(另两个是MultiStateView、MultipleStatusView),我就随意选了个star数多点的,如果你有更好的,欢迎推荐给我。

loadsir的话是用的MutableLiveData> state,然后在Activity中

val loadService = LoadSir.getDefault().register(this)
        mViewModel.state.observe(this@UserActivity,object :Observer>{
            override fun onChanged(t: Class<*>?) {
                loadService.showCallback(t)
            }
        })
复制代码

当然不同的库可能需要使用的方式不同,loadsir用的livedata,而有些库是使用xml的方式,可以通过DataBinding的BindingAdapter或BindingMethod。

使用

可以参考demo,在自定义的Application中初始化AppComponent,按照BaseActivity的方式封装你自己的基类,demo中的BaseActivity只简单地初始化了CommonActivityModule(注意要在super.onCreate之前),你可以按照自己的习惯再进行其他的封装。当然不止Activity,你还可以为M层和VM层创建基类。

然后参照UserComponent、UserModule、UserModel、UserViewModel、UserActivity的方式编写你的应用。

本来打算能够在实际项目中使用过完善之后再发出来的,但是想了想短时间内可能没有机会了,就先发出来这个半成品吧!后续如果有改动或补充,我会直接在此文章中更新,所以有兴趣的话可以收藏一下。

最后还是要说,使用之前希望你对Dagger2、DataBinding和架构组件有所了解。

有帮助的话,点个star吧

你可能感兴趣的:(一步步搭建一个MVVM开发架构,以及常见问题的解决方案)