本文包含Android
中MVVM
体系中的很多部分,主要对ViewModel
+DataBinding
+RxJava
+LiveData
+Lifecycle
等笔者所使用的技术体系进行解析.
本文字数较多,内容较为完整并且后续还会追加更新,阅读本篇文章需要较长时间,建议读者分段阅读.
所有文字均为个人学习总结和理解,仅供参考,如有纰漏还请指出,笔者不胜感激.
Android Studio
版本=3.2
Jetpack
最低兼容到Android
=2.1
,API
=7
为什么要选择MVVM?要回答这个问题首先就要介绍MVC
与MVP
这两种模式,从MVC
到MVVM
其实大家想的都是怎么把Model
和View
尽可能的拆开(熟悉三者定义的朋友可以跳过该节).
MVC
(Model
-View
-Controller
)即传统Android
开发中最常用的模式:
Activity
/Fragment
作为Controller
层,android.view.View
的子类以xml
构建文件构建起的布局
作为View
层SQLite
数据库,网络请求作为Model
层.但由于Activity
/Fragment
的功能过于强大
并且实际上包含了部分View
层功能,导致最后Activity
/Fragment
既承担了View
的责任,又承担了Controller
的责任.所以一般较复杂的页面,Activity
/Fragment
很容易堆积代码,最终导致Controller
混杂了View
层和业务逻辑(也就是你们所知道的一个Activity
三千行)
在MVC
中View
层与Model
几乎几乎完全没有隔离,View
层可以直接操作Model
层,Model
层的回调
里也可能会直接给View
赋值.Controller
的概念被弱化,最后只剩下MV
没有C
了.
这也将导致但你想把某个界面上的元素进行更新时,他会牵扯到一堆跟Model
层相关的代码,这个问题在你变更Model
层的时候同样也会出现,这个问题其实是没有很好的将逻辑分层导致的.
MVP
(Model
-View
-Presenter
)架构设计,是当下最流行的开发模式,目前主要以Google
推出的TodoMVP
为主,MVP
不是一种框架,它实际上更类似一种分层思想
,一种接口约定
,具体体现在下面:
IView
接口,并且在接口中约定View
层的各种操作,使用android.view.View
的子类以xml
构建文件构建起的布局
和Activity
/Fragment
作为布局控制器,实现IView
这个View
层的接口,View
层的实际实现类保留一个IPresenter
接口的实例.IPresenter
接口,并且在接口中约定Presenter
层的各种操作.可以使用一个与View
无关的类实现它,一般是XxxPresenterImpl
.通常情况下Presenter
层会包含Model
层的引用和一个IView
接口的引用,但不应该直接或者间接引用View
层android.view.View
的子类,甚至是操作的参数中也最好不要有android.view.View
的子类传进来,因为它应该只负责业务逻辑和数据的处理并通过统一的接口IView
传递到View
层.Model
层定义一个IModel
的接口,这一层是改造最小的.以前该怎么来现在也差不多该怎么来.但是现在Presenter
把它和View
隔开了,Presenter
就可以作为一段独立的逻辑被复用.MVP
模式解决了MVC
中存在的分层问题,Presenter
层被突出强调,实际上也就是真正意义上实现了的MVC
但是MVP
中其实仍然存在一些问题,比如当业务逻辑变得复杂以后,IPresenter
和IView
层的操作数量可能将会成对的爆炸式增长,新增一个业务逻辑,可能要在两边增加数个通信接口,这种感觉很蠢.
并且,我们要知道一个Presenter
是要带一个IView
的,当一个Presenter
需要被复用时,对应的View
就要去实现所有这些操作,但往往一些操作不是必须实现的,这样会留下一堆TODO
,很难看.
MVVM
(Model
-View
-ViewModel
)由MVP
模式演变而来,它由View
层,DataBinding
,ViewModel
层,Model
层构成,是MVP
的升级版并由Google
的Jetpack
工具包提供框架支持:
View
层包含布局,以及布局生命周期控制器(Activity
/Fragment
)DataBinding
用来实现View
层与ViewModel
数据的双向绑定(但实际上在Android Jetpack
中DataBinding
只存在于布局和布局生命周期控制器之间,当数据变化绑定到布局生命周期控制器时再转发给ViewModel
,布局控制器可以持有DataBinding
但ViewModel
不应该持有DataBinding
)ViewModel
与Presenter
大致相同,都是负责处理数据和实现业务逻辑,但是ViewModel
层不应该直接或者间接地持有View
层的任何引用,因为一个ViewModel
不应该直达自己具体是和哪一个View
进行交互的.ViewModel
主要的工作就是将Model
提供来的数据直接翻译成View
层能够直接使用的数据,并将这些数据暴露出去,同时ViewModel
也可以发布事件,供View
层订阅.Model
层与MVP
中一致.MVVM
的核心思想是观察者模式,它通过事件
和转移View
层数据持有权
来实现View
层与ViewModel
层的解耦.
在MVVM
中View
不是数据的实际持有者,它只负责数据如何呈现以及点击事件的传递,不做的数据处理工作,而数据的处理者和持有者变成ViewModel
,它通过接收View
层传递过来的时间改变自身状态,发出事件或者改变自己持有的数据触发View
的更新.
MVVM
解决了MVP
中的存在的一些问题,比如它无需定义接口,ViewModel
与View
层彻底无关更好复用,并且有Google
的Android Jetpack
作为强力后援.
但是MVVM
也有自己的缺点,那就是使用MVVM
的情况下ViewModel
与View
层的通信变得更加困难了,所以在一些极其简单
的页面中请酌情
使用,否则就会有一种脱裤子放屁的感觉,在使用MVP
这个道理也依然适用.
要用一个框架那么就要先说它的坑
点.那就是不建议在使用DataBinding
的模块同时使用apply plugin: 'kotlin-kapt'
.
因为现在kapt
还有很多Bug
,使用kapt
时,在Windows
下DataBinding
格式下的xml
中如果包含有中文,会报UTF-8
相关的错误.
笔者一开始猜想这是由于JVM
启动参数没有设置成-Dfile.encoding=UTF-8
导致的,在gradle.properties
中改过了,无果,Stack Overflow
搜过了,没找到,如果有大佬知道怎么解决,还请指点一二
如果你在模块中同时使用kotlin
和DataBinding
是可以的,但是请一定不要使用kapt
,除非JB
那帮大佬搞定这些奇怪的问题.
这就意味这你所有的kotlin
代码都不能依赖注解处理器来为你的代码提供附加功能,但是你可以把这些代码换成等价的Java
实现,它们可以工作得很好.
先说一点,DataBinding
风格的xml
会有"奇怪"的东西入侵Android
原生的xml
格式,这种格式LayoutInfalter
是无法理解,但是,当你对这些奇怪的xml
使用LayoutInfalter#inflate
时亦不会报错,并且布局也正常加载了,这是为什么呢?
这是因为在打包时,Gradle
通过APT
把你的DataBinding
风格的xml
全部翻译了一遍,让LayoutInfalter
能读懂他们,正是因为这个兼容的实现,而使得我们可以在使用和不使用DataBinding
间自由的切换.
要想使用DataBinding
,先在模块的build.gradle
中添加
android{
//省略…
dataBinding {
enabled = true
}
}
来启用DataBinding
支持.
DataBinding
不需要额外的类库支持,它被附加在你的android
插件中,它的版本号与你的android
插件版本一致.
classpath ‘com.android.tools.build:gradle:3.3.2’
在DataBinding
风格的xml
中,最外层必须是layout
标签,并且不支持merge
标签,编写xml
就像下面这样
data
标签包裹的是变量领域,在这里你可以使用variable
定义这个布局所要绑定的变量类型,使用name
来指定变量名,然后用type
来指定其类型.
如果一些类型比较长,而且由需要经常使用你可以像Java
一样使用import
导入他们(java.lang.*
会被默认导入),然后就不用写出完全限定名了,就像这样
有必要时(比如名字冲突),你还可以用Action
为一个类型指定一个别名,这样你就能在下文中使用这个别名.