注意,本文介绍的架构基于Dagger2、DataBinding以及Android架构组件,如果对这些不熟悉,建议先去简单了解一下再来看此文章,以免浪费你的时间。
本文主要记录此架构搭建的思路以及过程中遇到的问题,再到问题的解决,希望给同样在研究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 ArchViewModelextends 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组织方式
采用的这篇文章()中介绍的第二种依赖方式
参考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<VM>(mViewModel)).get(getViewModelClazz());
这句代码之前mViewModel是一个通过构造器创建的新ViewModel,但是执行这句代码之后,返回的还是之前第一次的mViewModel,因为这时候从holderfragment根据class找的话是有之前viewmodel的实例的,就直接返回之前的了。
public class ViewModelInstanceFactoryextends 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不可的情况该怎么办。这里提供两种方法,推荐第二种
- 使用弱引用。但是要注意屏幕旋转Activity重建的情况,在activity销毁的时候,弱引用get到的就是null了,所以在新的activity创建的时候要在onCreate时及时为弱引用赋值。
- 正如之前介绍,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层拥有最少的业务逻辑,可以看下我这篇文章,采用的也是类似holderFragment的方式,让fragment去startForResult。请求权限可以用RxPermissions的方式,也是这种原理。如果万不得已的话,也可以直接调用VM的public的方法,但是推荐不要滥用。
多状态视图的处理
MVVM一般通过数据来驱动UI(当然也可以做到双向),例子中使用的是,当然你也可以使用其他的,只是在我收藏的此类库中只有三个(另两个是、),我就随意选了个star数多点的,如果你有更好的,欢迎推荐给我。
loadsir的话是用的MutableLiveData<Class<*>> 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和架构组件有所了解。