欢迎访问移动开发之家(rcyd.net),关注移动开发教程。移动开发之家  移动开发问答|  每日更新
页面位置 : > > > 内容正文

Android开发框架MVC-MVP-MVVM-MVI的演变Demo,

来源: 开发者 投稿于  被查看 46512 次 评论:137

Android开发框架MVC-MVP-MVVM-MVI的演变Demo,


目录
  • Android框架的历史演变
  • 一. MVC框架
  • 二. MVP框架
  • 三. MVVM框架
    • 3.1 半MVVM框架
    • 3.2 带DataBinding的MVVM框架
  • 四. MVI框架

    Android框架的历史演变

    记得最开始入门Android的时候,还未流行MVP,都是MVC一把梭,后面工作了就是使用了MVP,当时学习的时候好难理解它的回调。

    到目前主流的MVVM,其实就是MVP的升级版,再到最新的MVI使用意图传输,隔离各层级的直接调用。我算是经历了Android框架变迁的全过程。

    这里记录一下各框架的简单Demo用例。

    一. MVC框架

    经典MVC分为:

    Model 模型层 : 数据和网络

    View 视图层 : 视图的展示

    Controller 控制层 : 逻辑控制,调用模型驱动视图

    一般我们是把一个xml看作一个View层, Activity看作一个Control层 , Model层则是由相关的数据操作类。

    Model层:

    class OtherModel : BaseRepository() {
        /**
         * 使用扩展方法,请求网络
         */
        suspend inline fun getIndustry(): OkResult<List<Industry>> {
            return extRequestHttp {
                DemoRetrofit.apiService.getIndustry(
                    Constants.NETWORK_CONTENT_TYPE,
                    Constants.NETWORK_ACCEPT_V1
                )
            }
        }
    }
    

    Controller层:

    class MVCActivity : AbsActivity() {
        private val mOtherModel: OtherModel by lazy { OtherModel() }
        override fun setContentView() {
            setContentView(R.layout.activity_demo14_1)
        }
        override fun init() {
            val btnGetData = findViewById<Button>(R.id.btn_get_data)
            btnGetData.click {
                requestIndustry()
            }
        }
        private fun requestIndustry() {
            //MVC中Activity就是Controller,直接调用接口,获取数据之后直接操作xml控件刷新
            lifecycleScope.launch {
                //开始Loading
                LoadingDialogManager.get().showLoading(this@MVCActivity)
                val result = mOtherModel.getIndustry()
                result.checkSuccess {
                    //处理成功的信息
                    toast("list:$it")
                    //doSth...
                }
                LoadingDialogManager.get().dismissLoading()
            }
        }
    }
    

    XML就是View层,获取到信息展示到XML中。

    这样分工其实也是很明确的,但是一旦逻辑过多,会导致Activity太臃肿。Activcity中又是Model又是View,耦合性太强,记得那时候一个Activity中上千行代码都是平平常常。

    为了解决这个问题,大家开始使用MVP架构。

    二. MVP框架

    MVP框架的出现,各个模块权责分明,各干各的活,降低了耦合,减少Activity的臃肿。

    Model层:还是MVC那个Model。
    View层:接口定义由Activity实,用于操作相应的UI。
    Presenter层:用于Model和View的桥梁,负责Model与View的交互

    那更复杂的一点的,就是其中加入Contract契约类,把指定页面的Presenter和View等关联起来,方便维护。

    View接口定义

    interface IDemoView {
        fun showLoading()
        fun hideLoading()
        fun getIndustrySuccess(list: List<Industry>?)
        fun getIndustryFailed(msg: String?)
    }
    

    Presenter的实现:

    class DemoPresenter(private val view: IDemoView) {
        private val mOtherModel: OtherModel by lazy { OtherModel() }
        //获取行业数据
        fun requestIndustry(lifecycleScope: LifecycleCoroutineScope) {
            lifecycleScope.launch {
                //开始Loading
                view.showLoading()
                val result = mOtherModel.getIndustry()
                result.checkResult({
                    //处理成功的信息
                    toast("list:$it")
                    view.getIndustrySuccess(it)
                }, {
                    //失败
                    view.getIndustryFailed(it)
                })
                view.hideLoading()
            }
        }
    }
    

    Activity的实现:

    class MVPActivity : AbsActivity(), IDemoView {
        private lateinit var mPresenter: DemoPresenter
        override fun setContentView() {
            setContentView(R.layout.activity_demo14_1)
        }
        override fun init() {
            //创建Presenter
            mPresenter = DemoPresenter(this)
            val btnGetData = findViewById<Button>(R.id.btn_get_data)
            btnGetData.click {
                //通过Presenter调用接口
                mPresenter.requestIndustry(lifecycleScope)
            }
        }
        //回调再次触发
        override fun showLoading() {
            LoadingDialogManager.get().showLoading(this)
        }
        override fun hideLoading() {
            LoadingDialogManager.get().dismissLoading()
        }
        override fun getIndustrySuccess(list: List<Industry>?) {
            //popupIndustryData
        }
        override fun getIndustryFailed(msg: String?) {
            //showErrorMessage
        }
    }
    

    当时MVP框架是火遍一时,当时面试要不会这个,那都不好意思说是做安卓的。

    虽然它有一些缺点,比如太复杂,每次都要写重复的View,修改麻烦,回调地狱,数据交互体验不佳,无法感知生命周期,重建页面无法自动恢复数据,耦合还是有很多,等等。但是在当时没有替代品的选择下,它是当之无愧的王。

    但是当谷歌出了Jetpack,当ViewModel+LiveData+Lifecycles的出现给了我们新的选择 MVVM框架开始出现并迅猛发展。

    三. MVVM框架

    这里先说一点有争议的点。 有些人认为,只要用上ViewModel+LiveData这些就算MVVM框架 Model+View+ViewModel嘛。 有些人认为,MVVM的意思是数据驱动,最大的亮点是数据绑定,使用DataBinding的才算MVVM。 其实这个也没有官方的定义,世上本无框架,用的人多了才出现框架名字,约定俗成的东西,你想怎么定义就怎么定义,那我姑且称为前者为半MVVM后者为MVVM吧

    3.1 半MVVM框架

    其实可以理解为MVP的升级版,去掉了View的接口回调,保存了ViewModel的特性

    Model层:还是MVC那个Model。
    View层:Activity,用于操作相应的UI。
    ViewModel:还是MVP那个Presenter,只是用ViewModel实现。

    ViewModel实现: 可以看到代码确实相比MVP少了很多

    class DemoViewModel @ViewModelInject constructor(
        private val mRepository: Demo5Repository,
        @Assisted val savedState: SavedStateHandle
    ) : BaseViewModel() {
        val liveData = MutableLiveData<List<Industry>?>()
        //获取行业数据
        fun requestIndustry() {
            viewModelScope.launch {
                //开始Loading
                loadStartLoading()
                val result = mRepository.getIndustry()
                result.checkResult({
                    //处理成功的信息
                    toast("list:$it")
                    liveData.value = it
                }, {
                    //失败
                    liveData.value = null
                })
                loadHideProgress()
            }
        }
    }
    

    Activity的实现:

    @AndroidEntryPoint
    class MVVMActivity : BaseVMActivity<DemoViewModel>() {
        override fun getLayoutIdRes(): Int = R.layout.activity_demo14_1
        override fun init() {
            //自动注入ViewModel,调用接口通过LiveData回调
            mViewModel.requestIndustry()
        }
        override fun startObserve() {
            //获取到网络数据之后改变xml对应的值
            mViewModel.liveData.observe(this) {
                it?.let {
                    // popopIndustryData
                }
            }
        }
    }
    

    3.2 带DataBinding的MVVM框架

    特别是现在kotlin那种直接拿id使用的插件已经被官方标记为过时,还不赶紧用DataBinding或ViewBinding?

    ViewModel实现:

    class DemoViewModel @ViewModelInject constructor(
        private val mRepository: Demo5Repository,
        @Assisted val savedState: SavedStateHandle
    ) : BaseViewModel() {
        val liveData = MutableLiveData<List<Industry>?>()
        //获取行业数据
        fun requestIndustry() {
            viewModelScope.launch {
                //开始Loading
                loadStartLoading()
                val result = mRepository.getIndustry()
                result.checkResult({
                    //处理成功的信息
                    toast("list:$it")
                    liveData.value = it
                }, {
                    //失败
                    liveData.value = null
                })
                loadHideProgress()
            }
        }
    }
    

    Repository的实现:其实和Model差不多的意思,数据仓库而已,下面的一些注解是用到了Hilt依赖注入,不用直接new对象也是可以的,不要在意一些细节。

    @Singleton
    class Demo5Repository @Inject constructor() : BaseRepository() {
        suspend inline fun getIndustry(): OkResult<List<Industry>> {
            return extRequestHttp {
                DemoRetrofit.apiService.getIndustry(
                    Constants.NETWORK_CONTENT_TYPE,
                    Constants.NETWORK_ACCEPT_V1
                )
            }
        }
    }
    

    Activity的实现: 内部做了一些基类的封装,事件处理封装为对象,viewmodel和事件对象在xml中做了引用

    @AndroidEntryPoint
    class MVVM2Activity : BaseVDBActivity<DemoViewModel, ActivityDemo142Binding>() {
        private val clickProxy: ClickProxy by lazy { ClickProxy() }
        override fun getDataBindingConfig(): DataBindingConfig {
            return DataBindingConfig(R.layout.activity_demo14_2, BR.viewModel, mViewModel)
                .addBindingParams(BR.click, clickProxy)
        }
        override fun init() {
        }
        override fun startObserve() {
        }
        /**
         * DataBinding事件处理
         */
        inner class ClickProxy {
            fun getData() {
                //MVVM直接调用网络请求,结果在xml中自动显示
                mViewModel.requestIndustry()
            }
        }
    }
    

    Xml的实现: 注意引用指向的包名要写对,写对了可以直接跳转过去的。

    <?xml version="1.0" encoding="utf-8"?>
    <layout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:binding="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools">
        <data>
            <variable
                name="viewModel"
                type="com.guadou.kt_demo.demo.demo14_mvi.mvvm1.DemoViewModel" />
            <variable
                name="click"
                type="com.guadou.kt_demo.demo.demo14_mvi.mvvm2.MVVM2Activity.ClickProxy" />
        </data>
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="@color/picture_color_blue"
            android:orientation="vertical">
            <com.guadou.lib_baselib.view.titlebar.StatusbarGrayView
                android:id="@+id/status_view"
                android:layout_width="match_parent"
                android:layout_height="wrap_content" />
            <Button
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="获取数据"
                binding:clicks="@{click.getData}" />
            <TextView
                android:id="@+id/tv_message"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="@{viewModel.liveData.toString()}" />
        </LinearLayout>
    </layout>
    

    这样就完成了一个基于数据驱动的DataBinding的MVVM。 如果上面一些代码如果看不太明白,后面我可能会出DataBinding的封装并开源。

    截止到发稿日期为止,目前市面上最流行的还是MVVM框架,此框架唯一的槽点可能就是Databinding的不好调试吧,一旦出问题,有时候报错信息莫名其妙的,没有指向XML中某个数据或语法的错误,需要对DataBinding有一定的了解。 不过AS现在貌似越来越智能了,报错信息都还指向蛮清晰的。MVVM完全可用的。

    四. MVI框架

    由于是出来没多久,具体是不是叫MVI框架这个名字还不确定,大家都这么叫,姑且就叫MVI吧。伴随Compose出现的框架,主流用于Compose应用。

    MVI框架是由Model View Intent组成的。可以算上MVVM的升级版,在之前我们都是通过在Activity中直接调用ViewModel的方法,现在改为发出操作指令,由ViewModel解析指令,调用对应的方法,回调给Activity。

    比如一个DemoActivity需要获取行业数据,学校数据,等。那么就可以把数据和操作都封装成指定的对象。

        //当前页面所需的数据与状态
        data class Demo14ViewState(
            val industrys: List<Industry> = emptyList(),
            val schools: List<SchoolBean> = emptyList(),
            var isChanged: Boolean = false
        ) : BaseViewState()
        //当前页面需要的事件定义
        sealed class DemoAction {
            object RequestIndustry : DemoAction()
            object RequestSchool : DemoAction()
            object RequestAllData : DemoAction()
            data class UpdateChanged(val isChange: Boolean) : DemoAction()
        }
    

    Activity调用相关的接口就不是直接调用ViewModel的方法,而是:

       override fun init() {
            //发送Intent指令,具体的实现由ViewModel实现
            mViewModel.dispatch(Damo14ViewModel.DemoAction.RequestAllData)
        }
    

    那么ViewModel就需要解析指令:

        //Action分发入口
        fun dispatch(action: DemoAction) {
            when (action) {
                is DemoAction.RequestIndustry -> requestIndustry()
                is DemoAction.RequestSchool -> requestSchool()
                is DemoAction.RequestAllData -> getTotalData()
                is DemoAction.UpdateChanged -> changeData(action.isChange)
            }
        }
        //获取行业数据
        private fun requestIndustry() {
          //xxx
        }
    

    完整的代码如下:

    ViewModel的实现:

    class Damo14ViewModel @ViewModelInject constructor(
        private val mRepository: Demo5Repository,
        @Assisted val savedState: SavedStateHandle
    ) : BaseViewModel() {
        private val _viewStates: MutableLiveData<Demo14ViewState> = MutableLiveData(Demo14ViewState())
        //只需要暴露一个LiveData,包括页面所有状态
        val viewStates: LiveData<Demo14ViewState> = _viewStates
        //Action分发入口
        fun dispatch(action: DemoAction) {
            when (action) {
                is DemoAction.RequestIndustry -> requestIndustry()
                is DemoAction.RequestSchool -> requestSchool()
                is DemoAction.RequestAllData -> getTotalData()
                is DemoAction.UpdateChanged -> changeData(action.isChange)
            }
        }
        //获取行业数据
        private fun requestIndustry() {
            viewModelScope.launch {
                //开始Loading
                loadStartLoading()
                val result = mRepository.getIndustry()
                result.checkSuccess {
                    _viewStates.setState {
                        copy(industrys = it ?: emptyList())
                    }
                }
                loadHideProgress()
            }
        }
        //获取学校数据
        private fun requestSchool() {
            viewModelScope.launch {
                //开始Loading
                loadStartLoading()
                val result = mRepository.getSchool()
                result.checkSuccess {
                    _viewStates.setState {
                        copy(schools = it ?: emptyList())
                    }
                }
                loadHideProgress()
            }
        }
        //获取全部数据
        private fun getTotalData() {
            //默认执行在主线程的协程-必须用(可选择默认执行在IO线程的协程)
            launchOnUI {
                //开始Loading
                loadStartProgress()
                val industryResult = async {
                    mRepository.getIndustry()
                }
                val schoolResult = async {
                    mRepository.getSchool()
                }
                //一起处理数据
                val industry = industryResult.await()
                val school = schoolResult.await()
                //如果都成功了才一起返回
                if (industry is OkResult.Success && school is OkResult.Success) {
                    loadHideProgress()
                    //设置多种LiveData
                    _viewStates.setState {
                        copy(industrys = industry.data ?: emptyList(), schools = school.data ?: emptyList())
                    }
                }
            }
        }
        //改变状态
        private fun changeData(isChanged: Boolean) {
            _viewStates.setState {
                copy(isChanged = isChanged)
            }
        }
        //当前页面所需的数据与状态
        data class Demo14ViewState(
            val industrys: List<Industry> = emptyList(),
            val schools: List<SchoolBean> = emptyList(),
            var isChanged: Boolean = false
        ) : BaseViewState()
        //如果想再度封装,也可以把回调的结果封装成类似Action的对象,由页面判断回调的是哪一种类型,进行相关的操作
        //这样就不需要使用LiveData回调了,LiveData就只是作为保存数据的功能,由DemoEvent回调
    //    sealed class DemoEvent {
    //        object PopBack : DemoEvent()
    //        data class ErrorMessage(val message: String) : DemoEvent()
    //    }
        //当前页面需要的事件定义
        sealed class DemoAction {
            object RequestIndustry : DemoAction()
            object RequestSchool : DemoAction()
            object RequestAllData : DemoAction()
            data class UpdateChanged(val isChange: Boolean) : DemoAction()
        }
    }
    

    Activity的实现:

    @AndroidEntryPoint
    class Demo14Activity : BaseVDBActivity<Damo14ViewModel, ActivityDemo14Binding>() {
        private val clickProxy: ClickProxy by lazy { ClickProxy() }
        companion object {
            fun startInstance() {
                commContext().let {
                    it.startActivity(Intent(it, Demo14Activity::class.java).apply {
                        addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
                    })
                }
            }
        }
        override fun getDataBindingConfig(): DataBindingConfig {
            return DataBindingConfig(R.layout.activity_demo14)
                .addBindingParams(BR.click, clickProxy)
        }
        @SuppressLint("SetTextI18n")
        override fun startObserve() {
            //监听两者数据变化
            mViewModel.viewStates.observeState(
                this,
                Damo14ViewModel.Demo14ViewState::industrys,
                Damo14ViewModel.Demo14ViewState::schools
            ) { industry, school ->
                YYLogUtils.w("industry: $industry ; school: $school")
            }
            //只监听changed的变换
            mViewModel.viewStates.observeState(this, Damo14ViewModel.Demo14ViewState::isChanged) {
                if (it) {
                    val industry = mViewModel.viewStates.value?.industrys
                    val school = mViewModel.viewStates.value?.schools
                    mBinding.tvMessage.text = "industry: $industry ; school: $school"
                }
            }
        }
        override fun init() {
            //发送Intent指令,具体的实现由ViewModel实现
            mViewModel.dispatch(Damo14ViewModel.DemoAction.RequestAllData)
        }
        /**
         * DataBinding事件处理
         */
        inner class ClickProxy {
            fun getData() {
                //发送Intent指令,具体的实现由ViewModel实现
    //            mViewModel.dispatch(Damo14ViewModel.DemoAction.RequestIndustry)
    //            mViewModel.dispatch(Damo14ViewModel.DemoAction.RequestSchool)
                mViewModel.dispatch(Damo14ViewModel.DemoAction.UpdateChanged(true))
            }
        }
    }
    

    注意,有些MVI的写法是回调给Activity的方式也是用对象封装如我注释的代码:

        //如果想再度封装,也可以把回调的结果封装成类似Action的对象,由页面判断回调的是哪一种类型,进行相关的操作
        //这样就不需要使用LiveData回调了,LiveData就只是作为保存数据的功能,由DemoEvent回调
    //    sealed class DemoEvent {
    //        object PopBack : DemoEvent()
    //        data class ErrorMessage(val message: String) : DemoEvent()
    //    }
    

    也可以使用LiveData返回,我这里使用扩展方法observeState方法来监听,这样可以保证只有你监听的对象发生了变化才会收到回调。这个扩展方法在MVVM框架也能使用。

    扩展方法如下:

    import androidx.lifecycle.*
    import kotlin.reflect.KProperty1
    /**
     * @auther Newki
     * @date 2022/2/10
     * @description LiveData的扩展 支持MVI模式 订阅单个LiveData实现监听不同的操作与数据
     */
    //监听一个属性
    fun <T, A> LiveData<T>.observeState(
        lifecycleOwner: LifecycleOwner,
        prop1: KProperty1<T, A>,
        action: (A) -> Unit
    ) {
        this.map {
            StateTuple1(prop1.get(it))
        }.distinctUntilChanged().observe(lifecycleOwner) { (a) ->
            action.invoke(a)
        }
    }
    //监听两个属性
    fun <T, A, B> LiveData<T>.observeState(
        lifecycleOwner: LifecycleOwner,
        prop1: KProperty1<T, A>,
        prop2: KProperty1<T, B>,
        action: (A, B) -> Unit
    ) {
        this.map {
            StateTuple2(prop1.get(it), prop2.get(it))
        }.distinctUntilChanged().observe(lifecycleOwner) { (a, b) ->
            action.invoke(a, b)
        }
    }
    //监听三个属性
    fun <T, A, B, C> LiveData<T>.observeState(
        lifecycleOwner: LifecycleOwner,
        prop1: KProperty1<T, A>,
        prop2: KProperty1<T, B>,
        prop3: KProperty1<T, C>,
        action: (A, B, C) -> Unit
    ) {
        this.map {
            StateTuple3(prop1.get(it), prop2.get(it), prop3.get(it))
        }.distinctUntilChanged().observe(lifecycleOwner) { (a, b, c) ->
            action.invoke(a, b, c)
        }
    }
    internal data class StateTuple1<A>(val a: A)
    internal data class StateTuple2<A, B>(val a: A, val b: B)
    internal data class StateTuple3<A, B, C>(val a: A, val b: B, val c: C)
    //更新State
    fun <T> MutableLiveData<T>.setState(reducer: T.() -> T) {
        this.value = this.value?.reducer()
    }
    

    太干了,一张图都没上,最后总结一下:

    世界上本无框架,用的人多了就成了框架,适合自己的才是好的。不是一定说出了最新框架我就要用最新的框架,理解之后再使用才能得心应手。

    个人目前平时开发中用的也是MVVM框架。后期会出一些MVVM的封装和用法开源。

    以上就是Android开发框架MVC-MVP-MVVM-MVI的演变Demo的详细内容,更多关于Android框架MVC MVP MVVM MVI的资料请关注3672js教程其它相关文章!

    您可能感兴趣的文章:
    • Android车载多媒体开发MediaSession框架示例详解
    • Android边播放边缓存视频框架AndroidVideoCache详解
    • Android开发Compose框架使用开篇
    • Android数据缓存框架内置ORM功能使用教程
    • 低门槛开发iOS、Android、小程序应用的前端框架详解
    • Android实现登录注册界面框架

    用户评论