在学习一个技术之前,我们首先要搞清为什么要用它、用它以后会有什么好处,这样我们才能有兴趣的学习下去。
我为什么要用这个什么MVVM,我就平常写和它有什么不同吗?
首先我们要说一下,使用MVVM后,程序会有哪些变化:
1.MVVM并不会提升程序的性能,甚至如果用不好还会降低性能。
2.MVVM会增大代码的总量。
3.阅读MVVM的代码你必须不停的跳来跳去,跳到你恶心想吐。
啥?你在搞笑吗?那我为什么还要用它?且慢,我还没说最重要的一条。
4.MVVM将让你的程序完全解耦。当然,是正确使用的前提下。
为了最后这一条,我们用前3条的牺牲完全不过分,真正写代码的时候,我们面对的往往不是性能的些许提升,也不是几十或几百K的包大小增大带来的问题,而是程序不停迭代所带来的程序稳定性要求。而这也是MVVM大显身手的地方,MVVM能够完全让你的业务功能组件化,让我们需要什么就调什么,并且组件可以在不同页面之间复用;在写业务组件的时候,我们可以完全集中精力,只管写组件要完成的功能,而无需分神它顾。
读到这,相信你应该对MVVM稍微有点兴趣了吧。
MVVM,是Model、View、ViewModel三者的缩写,它是一种程序的设计框架,是一种设计思路。不同的人实现MVVM,所用的构成技术、实现以后的性能都是不一样的。千万不要觉得它有多深奥,他其实就是由一系列代码(或技术)构成的一个程序的底座。我们在良好底座上开发出来的程序稳定性更高,可扩展性更强。
下面我们挨个来来说Model、View、ViewModel。
Model在程序中专门用于提供数据,不管是网络请求获得的数据,还是数据库获得的数据,统统写在Model里。Model层独立性相当强,它只用来提供数据,而不管数据是用来做什么的。
View在Android中指代的就是我们常见的布局文件和Activity中的元素初始化部分。总之,所有一切我们在Android上肉眼能看见的东西都是View。在View层里,我只对UI做初始化,比如将TextView设置字体大小,为Banner控件设置滚动速度等等,这些大多可以直接在布局文件中完成。
ViewModel根据业务需要,从Model层调取相关数据,然后更新View层相关元素。
说起来有点抽象,别急,下面我们用一个简单的例子来解释它们怎样互相配合,你很快就会明白。
在例子中,我们使用Google提供的DataBinding技术来完成数据绑定,以实现View和ViewModel层的交互。
【🌰时间】
例子非常非常简单,一个仓库管理的页面,一共三个元素,其中更新时间和仓库物品列表是从服务器动态获取的。
平常写的话,我们会在Activity内完成所有的操作,先进行网络请求,得到数据后为TextView设置文字、为RecycleView设置Adapter。但使用了MVVM后,代码将被拆分为三个部分,我们按View、ViewModel、Model的顺序直接上代码,随后在一一解释:
View层:
<?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android"> <data> <variable name="stockViewModel" type="com.ben.shop.viewmodel.StockManagementViewModel" /> </data> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@color/white" android:gravity="center" android:padding="@dimen/dp_5" android:text="仓库管理系统" android:textStyle="bold" /> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="@dimen/dp_10" android:gravity="center" android:text="@{stockViewModel.bindingData.component2()}" android:textColor="@color/red" android:textSize="@dimen/sp_15" /> <androidx.recyclerview.widget.RecyclerView android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" android:stockGoodsViewLoader="@{stockViewModel.bindingData.component1()}" /> </LinearLayout> </layout>ViewModel层:
class StockManagementViewModel(mvvmViewModel: MVVMViewModel?) : BaseViewModel(mvvmViewModel) { fun load() { val newData = getModel(StockModel::class.java).getStockList() val time = getModel(StockModel::class.java).getUpdateTime() bindingData.data.clear() bindingData.data.addAll(newData) bindingData.updateTime = "更新时间:${time}" bindingData.notifyChange() } override fun onResumed() { super.onResumed() load() } val bindingData: BindingData = BindingData(mutableListOf(), "") data class BindingData( @Bindable val data: MutableList<StockGood>, @Bindable var updateTime: String ) : BaseObservable() } @BindingAdapter("android:stockGoodsViewLoader") fun stockGoodsViewLoader(recyclerView: RecyclerView, data: List<StockGood>) { recyclerView.apply { layoutManager = LinearLayoutManager(recyclerView.context).apply { orientation = LinearLayoutManager.VERTICAL } adapter = StockManagementAdapter(recyclerView.context, data) } }Model层:
class StockModel(mvvmModel: MVVMModel?) : HttpModel(mvvmModel) { fun getStockList(): List<StockGood> { return mutableListOf<StockGood>().apply { add(StockGood("1", "钳子", 3)) add(StockGood("2", "扳手", 12)) } } fun getUpdateTime(): String { return Utils.DateUtil.getCurrentlyTimeByFormatter(Constant.DateFormat4) } }View层在我们这个例子中只有一个布局文件,布局文件是DataBinding的标准写法,在标签中写好控件需要的ViewModel。
<data> <variable name="stockViewModel" type="com.ben.shop.viewmodel.StockManagementViewModel" /> </data>然后将TextView和RecyclerView与ViewModel中的元素绑定好。
android:text="@{stockViewModel.bindingData.component2()}" android:stockGoodsViewLoader="@{stockViewModel.bindingData.component1()}"View没看明白没关系,先来看ViewModel。
还记得ViewModel的作用吗,我来重复一遍:它从Model层获取数据,然后更新视图。
这里ViewModel实现了Activity的生命周期函数,onResumed是ViewModel开始执行的地方
调用Model获取数据:
val newData = getModel(StockModel::class.java).getStockList() val time = getModel(StockModel::class.java).getUpdateTime()更新视图
bindingData.data.clear() bindingData.data.addAll(newData) bindingData.updateTime = "更新时间:${time}" bindingData.notifyChange()这里注意, notifyChange方法是BaseObservable中的方法,用于通知视图刷新
因为我们已经在View层将数据绑定到了data和updateTime两个变量上,因此,我们只需要更新这两个变量,就可以完成View层视图的刷新,这也正是MVVM强大的地方。从这里就可以看出,View层和ViewModel层是高度解耦的,只通过两个变量相互关联,这样,ViewModel就有了非常高的独立性,可以轻而易举的被其他View调用,也可以从例子中的View层瞬间剔除,就像插上或拔出一个U盘那样简单。
Model层比较容易理解,由于只是一个例子,我们在这里并没有请求网络,实际开发过程中,你只需要使用你想使用的网络工具请求网络或调用数据库,获取到数据后返回给ViewModel层即可。
这里值得一题的是Activity中的代码:
class StockManagementActivity : MVVMActivity<ActibityStockManagementBinding>() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentViewByDataBinding(R.layout.actibity_stock_management) dataBinding.stockViewModel = getViewModel(StockManagementViewModel::class.java) } }可以看到,Activity中几乎没有逻辑代码,仅仅有一行来实例化ViewModel。MVVM就这样,将业务逻辑完整从Activity中拆分为独立的三块,不仅解放了Activity,更使整个代码的逻辑清晰,可读性极强,并且功能块之间互不干扰,这也就实现了我们开头所说的组件化开发。
怎么样,MVVM还不错吧?如果您曾经了解过MVC、MVP,相信本文一定很容易理解,MVVM只不过的它们的“升级版”,实现过程不同,目标却是相同的。这些设计思想并不是十分深奥的东西,本质就是让软件质量变得更好,如果不明白它们的原理,不仅不会让代码质量提高,反而会使程序变得不知所云,得不偿失。
另外说一下,本文主讲MVVM,DataBinding的用法和原理没有过多涉及,但相信看过本文以后,您应该对它也有了一定的了解了吧。我开头说过,MVVM实现的技术各不相同。DataBinding是Goodle出品,并且Android Studio中支持的相当不错,因此被我采用。在了解了MVVM的机制以后,你完全可以选择你想使用的技术,构建你自己的MVVM框架。如果各位有兴趣,我可以写一个关于DataBinding的文章。当然网络上关于DataBinding的也很多,大家也可以作为参考。
小生才疏学浅,文中必有遗漏和错误,十分欢迎阅读过的同学指正,大家一起讨论!