文章展示的项目地址:https://gitee.com/QingDian_Fan/MVVMDemo
自从官方mvpSample出来后,闹得热火朝天的mvp,小编也未能幸免加入MVP大坑中, MVP 架构在安卓界非常流行,几乎已经成为主流框架,它让业务逻辑 和 UI操作相对独立,使得代码结构更清晰。但是Presenter层与View层交互频繁,使的Presenter层特别的臃肿,尤其是在一个界面要进行多次的网络交互时,而且一旦View层需要改变时,Presenter也需要跟着进行相应的改变.将项目架构切换到MVVM,整个项目将会变得清爽多了.
对于MVVM,最经典的解释不过是这张图了:
Model-View-ViewModel:
View便是这里的activity和fragment,主要负责UI界面的展示,不参与任何逻辑和数据的处理
Viewmodel 主要负责业务逻辑和数据处理,本身不持有 View 层引用,通过 LiveData 向 View 层发送数据(liveData对数据具有监听作用)
Model:便是指这里的Repository ,主要负责从本地数据库或者远程服务器来获取数据,Repository统一了数据的入口,获取到数据,将数据发送给ViewModel
我在这个框架中没有使用到图中Room数据库,大家可以参考Room学习了解.
MVP的优点
以上就是MVP的优点了,但是对于一个框架咱们不能只了解它的优点,还需要知道他的缺点,利于在项目开发中更好的去避免不必要的麻烦和调试一些BUG.下面就说下MVP的缺点
MVP缺点:
看完MVP的优缺点之后,下面咱们去了解一下MVVM架构,
MVVM的优点:
MVVM的缺点:
也可以说MVVM的缺点基本上就是Databinding的缺点,其次我感觉DataBinding污染了xml.考虑到这些问题在此架构中我并没有使用DatBinding.
首先这次的网络结构我是采用 Kotlin +协程+retrofit 进行搭建的,相信好多朋友会好奇为什么不适用rxjava+retrofit呢?
我感觉rxjava+retrofit进行网络请求有点大材小用了,并且使用协程+retrofit的一定程度上可以减少Apk体积.
1.首先我们看下View层
在这里不管是Fragment还是Activity我都分为两层BaseActivity和BaseVMActivity.
BaseVMActivity继承BaseActivity,BaseActivity我在这里主要是处理一些较为基本的操:动态权限的申请以及状态栏的处理.如果页面不需要进行刀剑MVVM框架,可自行继承BaseActivity,互不影响.
abstract class BaseViewModelActivity : BaseActivity() {
protected lateinit var viewModel: VM
override fun onCreate(savedInstanceState: Bundle?) {
initVM()
super.onCreate(savedInstanceState)
startObserve()
}
private fun initVM() {
providerVMClass()?.let {
viewModel = ViewModelProviders.of(this).get(it)
lifecycle.addObserver(viewModel)
}
}
open fun providerVMClass(): Class? = null
private fun startObserve() {
//处理一些通用异常,比如网络超时等
viewModel.run {
getError().observe(this@BaseViewModelActivity, Observer {
requestError(it)
})
getFinally().observe(this@BaseViewModelActivity, Observer {
requestFinally(it)
})
}
}
open fun requestFinally(it: Int?) {
}
open fun requestError(it: Exception?) {
//处理一些已知异常
it?.run {
when (it) {
is TimeoutCancellationException -> showToast("请求超时")
is BaseRepository.TokenInvalidException -> showToast("登陆超时")
is UnknownHostException -> showToast("没有网络")
is HttpException -> showToast("网络错误")
is JSONException -> showToast("解析错误")
is ConnectException -> showToast("连接失败")
is ServerException -> showToast(it.message.toString())
}
}
}
override fun onDestroy() {
super.onDestroy()
lifecycle.removeObserver(viewModel)
}
}
在providerVMClass方法中通过BaseViewModel子类泛型类型参数获取Class,在通过 ViewModelProviders.of(this).get(it)实例化ViewModel
2.其次再看下BaseViewModel层
open class BaseViewModel : ViewModel(), LifecycleObserver {
private val error by lazy { MutableLiveData() }
private val finally by lazy { MutableLiveData() }
//运行在UI线程的协程
fun launchUI(block: suspend CoroutineScope.() -> Unit) = viewModelScope.launch {
try {
withTimeout(5000){
block()
}
} catch (e: Exception) {
error.value = e
} finally {
finally.value = 200
}
}
/**
* 请求失败,出现异常
*/
fun getError(): LiveData {
return error
}
/**
* 请求完成,在此处做一些关闭操作
*/
fun getFinally(): LiveData {
return finally
}
}
网络请求必须在子线程中进行,这是Android开发常理,使用协程进行网络请求在代码上可以让异步代码看起来是同步执行,这很大得提高了代码得可读性,不过理解挂起的确需要时间。BaseViewModel中最终得事情就是要搭建关于协程对于Retrofit网络请求代码块得try…catch。
正常开发一般不建议直接通过ViewModel获取网络数据,这里我们将工作交给一个新的模块Repository。Repository只负责数据处理,提供干净的api,方便切换数据来源。
3.然后再看看BaseRepository层
BaseRepository中内容相对简单,主要是获取ApiService和网络请求订阅容器,方便管理网络请求
open class BaseRepository {
suspend fun request(call: suspend () -> ResponseData): ResponseData {
return withContext(Dispatchers.IO) { call.invoke() }.apply {
//这儿可以对返回结果errorCode做一些特殊处理,比如上传参数错误等,可以通过抛出异常的方式实现
//例:当上传参数错误时,后台返回errorCode 为 1001,下面代码实现,再到baseActivity通过观察error来处理
when (errorCode) {
1001 -> throw ParameterException()
0 -> Log.e("请求状态值:$errorCode", "请求成功" );
}
}
}
class ParameterException(msg: String = "Parameter error") : Exception(msg)
}
4-1.LoginActivity:
class LoginActivity : BaseViewModelActivity<LoginViewModel>(), View.OnClickListener {
private lateinit var data: loginData
override fun getLayoutId(): Int = R.layout.activity_login
override fun providerVMClass(): Class<LoginViewModel>? = LoginViewModel::class.java
override fun initView() {
}
override fun initData() {
login.setOnClickListener(this)
viewModel.getLogin().observe(this, Observer {
if (it.errorCode == 0) {
showToast("登录成功")
} else {
showToast("账号或密码错误")
}
})
}
override fun onClick(v: View?) {
when (v!!.id) {
R.id.login -> {
val name = username.text.toString()
val pwd = password.text.toString()
viewModel.loginDatas(name, pwd);
}
}
}
}
LoginActivity中只有UI初始化,发请网络请求意图以及数据观察更新UI
4-2.LoginViewModel:
class LoginViewModel : BaseViewModel() {
private var data:MutableLiveData<ResponseData<loginData>> = MutableLiveData()
private val repository = ArticleRepository()
fun getLogin(): LiveData<ResponseData<loginData>> {
return data
}
fun loginDatas(name: String, pwd: String)= launchUI {
val result = repository.loginDatas(name, pwd)
data.postValue(result)
}
}
LoginViewModel中持有数据观察容器LiveData和真正发起网络请求动作,在接收到服务端返回的数据通过
data.postValue(result)通知Observer数据的更改,此处需注意的是,setValue方法只能在主线程中调用,postValue可以在任何线程中调用,如果是在后台子线程中更新LiveData的值,必须调用postValue。
4-3.LoginRepository :
class LoginRepository : BaseRepository() {
suspend fun loginDatas(userName: String, passWord: String): ResponseData<LoginData> = request {
RetrofitClient.reqApi.login(userName, passWord)
}
}
最后我们的LoginRepository 中就提供数据,此处只提供了网络层的数据,在实际应用中可拆分为本地数据和网络数据,可根据项目需求自行处理
到此咱们一个简单业务代码就完成了,效果图奉上: