在写demo之前,我们首先先来学习下MVVM模式。
在我们实际开发中,从项目的可维护性、以及可扩展性等方面考虑,通常我们对于项目的架构会做出合理设计,而目前最为常用的Android开发架构模式就是MVC、MVP和MVVM模式。
MVC模式全称Model-View-Controller,将整个项目结构划分为三个部分模型层(Model),视图层(View)和控制层(Controller),三层分别承担不同任务,如下:
MVC架构相对于将所有业务逻辑都放入Activity中,多了一个Model层,将部分请求数据相关逻辑划分到了Model层,将视图层和业务层进行了一定程度上的分离,降低了耦合性,提高了项目的可维护性。
但是,在Android实际的项目开发中,MVC存在着很大的缺陷。假设,我们需要创建一个登录页面,需要实现登录、自动登录和保存密码等相关功能。如果我们采用MVC模式,那么,我们可能会利用XML完成界面设计作为View层,然后创建一个LoginModel来完成登录和网络请求这就是Model层了,而Activity则复杂相关业务逻辑,界面交互等工作,为Controller层。
这么做是存在着一定的问题的。首先,View与Model之间还存在依赖关系,Controller很重很复杂。由上面的MVC架构图可知,view层和model层是相互可知的,这意味着两层之间存在耦合,耦合对于一个大型程序来说是非常致命的,因为这表示开发,测试,维护都需要花大量的精力。其次,xml作为view层,控制能力实在太弱了,因此,View层很多工作都需要在Activity中进行,这就导致了Activity在实际开发中承担功能过多,即需要承担Controller层的功能,也需要承担View层功能,如果页面功能较为复杂,那么Activity中就会存在大量代码,维护成本很高。
因此,MVC模式只适用于功能简单,业务逻辑相对较少的项目和页面。
MVP模式全称Model-View-Presenter,将项目划分为模型层(Model)、视图层(View)和中间层(Presenter),MVP模式是由MVC模式发展演化而成的,我们从其名称就可以发现,其不同的就只是Controller和Presenter。其各层功能也基本相同,Model层复杂数据处理和获取,View层负责数据展示和交互,而Presenter层则负责业务逻辑处理,联系View层和Model。但是,实际上其View层和Model层的实现也有所不同。其架构图如下:
其实,从上面的架构图来看,我们就能够发现,MVP与MVC最大的区别就是,将View层和Model层完全分离,之间相互不可见,而是以Presenter层作为沟通的桥梁,用于操作view层发出的事件传递到presenter层中,presenter层去操作model层,并且将数据返回给view层,整个过程中view层和model层完全没有联系。
其实,从上面的架构图来看,我们就能够发现,MVP与MVC最大的区别就是,将View层和Model层完全分离,之间相互不可见,而是以Presenter层作为沟通的桥梁,用于操作view层发出的事件传递到presenter层中,presenter层去操作model层,并且将数据返回给view层,整个过程中view层和model层完全没有联系。在MVP模式中,Activity为完成充当View层,只处理视图相关逻辑,其它所有业务逻辑都交由Presenter层处理。
再回看,如果使用MVP模式来完成上述登录界面,我们可能会这么写。首先,创建LoginModel类,该类提供了相关方法,实现了保存、获取密码和登录等相关数据处理的功能,此为Model层;然后,创建LoginActivity,实现相关界面,并且提供了相关接口,用于展示数据,Activity中持有Presenter层引用,通过Presenter层提供的接口完成相关业务逻辑,此为View层;最后,创建LoginPresenter层,在该层中持有LoginModel和LoginActivity引用,通过LoginModel处理数据,并提供了相关接口完成登录、保存密码和自动登录等功能,最后利用LoginActivity的相关接口返回结果,改变界面,这就是Presenter层。当然,实际上,在我们使用MVP模式的时候,我们可能更多的会使用接口的形式来实现,创建Model、View和Presenter层基类,然后在基类中进行注册等工作。
MVP模式主要存在以下几个特点:
MVVM模式全称Model-View-ViewModel,MVVM可以看作是MVP的一个改进版,其不同之处也主要存在是ViewModel层和Presenter层。和Presenter相比,其数据和视图是双向绑定的,ViewModel不再持有View的引用,而是通过DataBanding等方式,在数据发生改变的时候自动更新View层,而视图层发生变化时同时也会改变数据。其实现和各种特点,将在下面的实例中一一展示。
MVVM的特点如下:
接下来,我将使用Jetpack中相关组件ViewModel和LiveData基于MVVM模式,来实现上述的登录界面。当然,实际上使用Jetpack来实现MVVM模式的最佳选择是以DataBinding组件来实现数据的双向绑定,我们现在只是使用ViewModel和LiveData来尝试实现一个简单的Demo。项目中使用fastmock提供的API,利用okhttp来实现网络请求。
首先,我们根据接口返回的json数据,创建对应的Model,如下所示:
data class LoginDataModel(
var code: String,
var data: DataBean,
var desc: String
)
data class DataBean(
var verifySuccess: Boolean,
val userInfo: UserInfoBean
)
data class UserInfoBean(
val username: String,
val email: String,
val address: String
)
在MVVM模式中,Activity充当View层,如下所示:
class MvvmActivity : AppCompatActivity() {
private var viewModel : LoginViewModel? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_mvvm)
MMKV.initialize(this)
viewModel = ViewModelProvider(this).get(LoginViewModel::class.java)
bindViewAndData()
//初始化数据
viewModel?.initData()
initView()
}
private fun initView(){
btLogin.setOnClickListener {
Thread {
viewModel?.login({
Log.e("test_mvvm", "登录失败")
},{
Log.e("test_mvvm", "登录成功")
})
}.start()
}
}
/**
* 双向绑定
*/
private fun bindViewAndData(){
//data->view
viewModel?.account?.observe(this,
Observer<String> { account -> if (account != etAccount.text.toString()) etAccount.setText(account) })
viewModel?.password?.observe(this,
Observer<String> { password -> if (password != etPassword.text.toString()) etPassword.setText(password) })
viewModel?.rememberFlag?.observe(this,
Observer<Boolean>{ flag -> cbRemember.isChecked = flag} )
//view->data
etAccount.addTextChangedListener {
it?.apply { viewModel?.account?.value = it.toString() }
}
etPassword.addTextChangedListener {
it?.apply { viewModel?.password?.value = it.toString() }
}
cbRemember.setOnCheckedChangeListener { _, isChecked ->
viewModel?.rememberFlag?.value = isChecked
}
}
}
我们可以看到,在Activity中没有多余的业务逻辑,其中内容只是进行了视图和数据的双向绑定,并且将点击事件交由View处理Model。因为使用LiveData,界面会自动根据数据变化而更新,从而使View层和ViewModel层完全解耦。
然后就是最重要,包含了所有业务逻辑的ViewModel了,我们继承View Model创建LoginViewModel,如下:
class LoginViewModel : ViewModel() {
var account : MutableLiveData<String> = MutableLiveData()
var password : MutableLiveData<String> = MutableLiveData()
var rememberFlag : MutableLiveData<Boolean> = MutableLiveData(false)
/**
* 登录
*/
fun login(failed : () -> Unit, success : ()->Unit){
val client = OkHttpClient.Builder().build()
val requestBody = FormBody.Builder()
.add("username", account.value ?: "")
.add("password", password.value ?: "")
.build()
val request = Request.Builder()
.url("https://www.fastmock.site/mock/46ef588deb8840f2083cefdad4d760b7/test/api/submit")
.post(requestBody)
.build()
client.newCall(request).enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
}
override fun onResponse(call: Call, response: Response) {
val data = Gson().fromJson(response.body()?.string(), LoginDataModel::class.java)
if (data.data.verifySuccess){
//登录成功
success.invoke()
saveData()
} else {
//登录失败
failed.invoke()
}
}
})
}
/**
* 初始化数据
*/
fun initData(){
val mmkv = MMKV.defaultMMKV()
rememberFlag.value = mmkv.decodeBool("remember")
account.value = mmkv.decodeString("account")
if (rememberFlag.value == true){
password.value = mmkv.decodeString("password")
}
}
/**
* 保存数据
*/
fun saveData(){
val mmkv = MMKV.defaultMMKV()
mmkv.encode("remember", rememberFlag.value ?: false)
mmkv.encode("account", account.value ?: "")
mmkv.encode("password", password.value ?: "")
}
}
我们可以看到,LoginViewModel中包括了登录页面的所有业务逻辑,包括初始化数据,记住密码,和登录。
如此一来,一个简单的MVVM模式demo就完成了,当然这是一个非常简单的Demo,其中包括网络请求等操作都没有进行相关处理。而且也没有使用DataBinding,在实际开发中有许多可以优化的地方。无论是Jetpack还是MVVM模式的使用,这都需要在之后的开发中多加使用,熟练。