博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
一步步搭建一个MVVM开发架构,以及常见问题的解决方案
阅读量:5915 次
发布时间:2019-06-19

本文共 7117 字,大约阅读时间需要 23 分钟。

注意,本文介绍的架构基于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 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组织方式

采用的这篇文章()中介绍的第二种依赖方式

参考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 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层拥有最少的业务逻辑,可以看下我这篇文章,采用的也是类似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和架构组件有所了解。

转载地址:http://jdwvx.baihongyu.com/

你可能感兴趣的文章
28. PowerShell -- 注册表操作
查看>>
搭建 android sdk环境
查看>>
第14章 grep、sed、awk 正则表达式
查看>>
ant_Jmeter持续集成测试报告优化之添加throughput显示
查看>>
一个=号引发的错误.......
查看>>
CPU显卡内存与3DMAX渲染的关系
查看>>
Hive(一):Hive的安装部署
查看>>
Tomcat9 多端口 多项目
查看>>
linux tomcat配置https
查看>>
史上最牛最详细的Linux教程 不看后悔终生!
查看>>
极快的正整数排序函数
查看>>
mysql数据库sleep进程过多的处理办法
查看>>
第二次作业
查看>>
opencv 实现图像像素点反转
查看>>
Access denied for user 'root'@'localhost' (using p
查看>>
linux中grep命令
查看>>
H3C模拟器 DHCP Snooping 、中继 实例配置
查看>>
sed工具的使用
查看>>
数据仓库工程师、大数据开发工程师、BI工程师、ETL工程师之间有什么区别?...
查看>>
JVM初识-java类加载器
查看>>