超级管理员

70686

帖子

13

回复

228

积分

楼主
发表于 2019-12-31 10:39:13 | 查看: 273 | 回复: 0

文章展示的项目地址: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的优点

  • 分离了视图逻辑和业务逻辑,降低了耦合
  • Activity只处理生命周期的任务,代码变得更加简洁
  • 视图逻辑和业务逻辑分别抽象到了View和Presenter的接口中去,提高代码的可阅读性
  • Presenter被抽象成接口,可以有多种具体的实现,所以方便进行单元测试
  • 把业务逻辑抽到Presenter中去,避免后台线程引用着Activity导致Activity的资源无法被系统回收从而引起内存泄露和OOM

以上就是MVP的优点了,但是对于一个框架咱们不能只了解它的优点,还需要知道他的缺点,利于在项目开发中更好的去避免不必要的麻烦和调试一些BUG.下面就说下MVP的缺点

MVP缺点:

  • Presenter中除了逻辑以外,还有大量的View->Model,Model->View的逻辑操作,造成 Presenter臃肿,维护困难。
  • 对UI的渲染放在了Presenter中,所以UI和Presenter的交互会过于频繁。
  • Presenter过多地渲染了UI,往往会使得它与特定的UI的交互频繁。一旦UI变动,Presenter也需要变
  • 接口暴增,可以说代码量成倍增长,交互都需要通过接口传递信息,让人无法忍受.

看完MVP的优缺点之后,下面咱们去了解一下MVVM架构,

MVVM的优点:

  • 双向绑定技术,当Model变化时,View-Model会自动更新,View也会自动变化。很好做到数据的一致性,不用担心,在模块的这一块数据是这个值,在另一块就是另一个值了。所以 MVVM模式有些时候又被称作:model-view-binder模式。
  • View的功能进一步的强化,具有控制的部分功能,若想无限增强它的功能,甚至控制器的全部功几乎都可以迁移到各个View上(不过这样不可取,那样View干了不属于它职责范围的事情)。View可以像控制器一样具有自己的View-Model.
  • 由于控制器的功能大都移动到View上处理,大大的对控制器进行了瘦身。不用再为看到庞大的控制器逻辑而发愁了。
  • 可以对View或ViewController的数据处理部分抽象出来一个函数处理model。这样它们专职页面布局和页面跳转,它们必然更一步的简化。

MVVM的缺点:

  • 数据绑定增加Bug调试难度,
  • 复杂的页面,model也会很大,虽然使用方便了也很容易保证了数据的一致性,当时长期持有,不利于释放内存,
  • 数据双向绑定不利于View重用

也可以说MVVM的缺点基本上就是Databinding的缺点,考虑到这些问题在此架构中我并没有使用DatBinding.

MVVM架构分层代码:

首先这次的网络结构我是采用 Kotlin +协程+retrofit 进行搭建的,相信好多朋友会好奇为什么不适用rxjava+retrofit呢?
我感觉rxjava+retrofit进行网络请求有点大材小用了,并且使用协程+retrofit的一定程度上可以减少Apk体积.

1.首先我们看下View层
在这里不管是Fragment还是Activity我都分为两层BaseActivityBaseVMActivity.
BaseVMActivity继承BaseActivity,BaseActivity我在这里主要是处理一些较为基本的操:动态权限的申请以及状态栏的处理.如果页面不需要进行刀剑MVVM框架,可自行继承BaseActivity,互不影响.

abstract class BaseViewModelActivity<VM : BaseViewModel> : 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<VM>? = 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<Exception>() }

    private val finally by lazy { MutableLiveData<Int>() }

    //运行在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<Exception> {
        return error
    }

    /**
     * 请求完成,在此处做一些关闭操作
     */
    fun getFinally(): LiveData<Int> {
        return finally
    }
    /*
    *避免内存泄漏
    */
   override fun onCleared() {
        super.onCleared()
        viewModelScope.cancel()
    }
    }

网络请求必须在子线程中进行,这是Android开发常理,使用协程进行网络请求在代码上可以让异步代码看起来是同步执行,这很大得提高了代码得可读性,不过理解挂起的确需要时间。BaseViewModel中最终得事情就是要搭建关于协程对于Retrofit网络请求代码块得try…catch。
正常开发一般不建议直接通过ViewModel获取网络数据,这里我们将工作交给一个新的模块Repository。Repository只负责数据处理,提供干净的api,方便切换数据来源。

3.然后再看看BaseRepository层
BaseRepository中内容相对简单,主要是获取ApiService和网络请求订阅容器,方便管理网络请求

open class BaseRepository {
    suspend fun <T : Any> request(call: suspend () -> ResponseData<T>): ResponseData<T> {
        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.到这我们的各层的基类已经看得差不多了,就让我们用一个登陆模块来坐下实战吧:

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 中就提供数据,此处只提供了网络层的数据,在实际应用中可拆分为本地数据和网络数据,可根据项目需求自行处理
到此咱们一个简单业务代码就完成了,效果图奉上:
在这里插入图片描述


本帖子中包含资源

您需要 登录 才可以下载,没有帐号?立即注册

您需要登录后才可以回帖 登录 | 立即注册

2018-2020 ©v2.1 冀ICP备19027484号

点击这里给我发消息