欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Android開發(fā)框架MVC-MVP-MVVM-MVI的演變Demo

 更新時間:2022年10月21日 10:41:07   作者:newki  
這篇文章主要為大家介紹了Android開發(fā)框架MVC-MVP-MVVM-MVI的演變Demo,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪

Android框架的歷史演變

記得最開始入門Android的時候,還未流行MVP,都是MVC一把梭,后面工作了就是使用了MVP,當時學習的時候好難理解它的回調(diào)。

到目前主流的MVVM,其實就是MVP的升級版,再到最新的MVI使用意圖傳輸,隔離各層級的直接調(diào)用。我算是經(jīng)歷了Android框架變遷的全過程。

這里記錄一下各框架的簡單Demo用例。

一. MVC框架

經(jīng)典MVC分為:

Model 模型層 : 數(shù)據(jù)和網(wǎng)絡(luò)

View 視圖層 : 視圖的展示

Controller 控制層 : 邏輯控制,調(diào)用模型驅(qū)動視圖

一般我們是把一個xml看作一個View層, Activity看作一個Control層 , Model層則是由相關(guān)的數(shù)據(jù)操作類。

Model層:

class OtherModel : BaseRepository() {
    /**
     * 使用擴展方法,請求網(wǎng)絡(luò)
     */
    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,直接調(diào)用接口,獲取數(shù)據(jù)之后直接操作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架構(gòu)。

二. MVP框架

MVP框架的出現(xiàn),各個模塊權(quán)責分明,各干各的活,降低了耦合,減少Activity的臃腫。

Model層:還是MVC那個Model。
View層:接口定義由Activity實,用于操作相應(yīng)的UI。
Presenter層:用于Model和View的橋梁,負責Model與View的交互

那更復雜的一點的,就是其中加入Contract契約類,把指定頁面的Presenter和View等關(guān)聯(lián)起來,方便維護。

View接口定義

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

Presenter的實現(xiàn):

class DemoPresenter(private val view: IDemoView) {
    private val mOtherModel: OtherModel by lazy { OtherModel() }
    //獲取行業(yè)數(shù)據(jù)
    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的實現(xiàn):

class MVPActivity : AbsActivity(), IDemoView {
    private lateinit var mPresenter: DemoPresenter
    override fun setContentView() {
        setContentView(R.layout.activity_demo14_1)
    }
    override fun init() {
        //創(chuàng)建Presenter
        mPresenter = DemoPresenter(this)
        val btnGetData = findViewById<Button>(R.id.btn_get_data)
        btnGetData.click {
            //通過Presenter調(diào)用接口
            mPresenter.requestIndustry(lifecycleScope)
        }
    }
    //回調(diào)再次觸發(fā)
    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,修改麻煩,回調(diào)地獄,數(shù)據(jù)交互體驗不佳,無法感知生命周期,重建頁面無法自動恢復數(shù)據(jù),耦合還是有很多,等等。但是在當時沒有替代品的選擇下,它是當之無愧的王。

但是當谷歌出了Jetpack,當ViewModel+LiveData+Lifecycles的出現(xiàn)給了我們新的選擇 MVVM框架開始出現(xiàn)并迅猛發(fā)展。

三. MVVM框架

這里先說一點有爭議的點。 有些人認為,只要用上ViewModel+LiveData這些就算MVVM框架 Model+View+ViewModel嘛。 有些人認為,MVVM的意思是數(shù)據(jù)驅(qū)動,最大的亮點是數(shù)據(jù)綁定,使用DataBinding的才算MVVM。 其實這個也沒有官方的定義,世上本無框架,用的人多了才出現(xiàn)框架名字,約定俗成的東西,你想怎么定義就怎么定義,那我姑且稱為前者為半MVVM后者為MVVM吧

3.1 半MVVM框架

其實可以理解為MVP的升級版,去掉了View的接口回調(diào),保存了ViewModel的特性

Model層:還是MVC那個Model。
View層:Activity,用于操作相應(yīng)的UI。
ViewModel:還是MVP那個Presenter,只是用ViewModel實現(xiàn)。

ViewModel實現(xiàn): 可以看到代碼確實相比MVP少了很多

class DemoViewModel @ViewModelInject constructor(
    private val mRepository: Demo5Repository,
    @Assisted val savedState: SavedStateHandle
) : BaseViewModel() {
    val liveData = MutableLiveData<List<Industry>?>()
    //獲取行業(yè)數(shù)據(jù)
    fun requestIndustry() {
        viewModelScope.launch {
            //開始Loading
            loadStartLoading()
            val result = mRepository.getIndustry()
            result.checkResult({
                //處理成功的信息
                toast("list:$it")
                liveData.value = it
            }, {
                //失敗
                liveData.value = null
            })
            loadHideProgress()
        }
    }
}

Activity的實現(xiàn):

@AndroidEntryPoint
class MVVMActivity : BaseVMActivity<DemoViewModel>() {
    override fun getLayoutIdRes(): Int = R.layout.activity_demo14_1
    override fun init() {
        //自動注入ViewModel,調(diào)用接口通過LiveData回調(diào)
        mViewModel.requestIndustry()
    }
    override fun startObserve() {
        //獲取到網(wǎng)絡(luò)數(shù)據(jù)之后改變xml對應(yīng)的值
        mViewModel.liveData.observe(this) {
            it?.let {
                // popopIndustryData
            }
        }
    }
}

3.2 帶DataBinding的MVVM框架

特別是現(xiàn)在kotlin那種直接拿id使用的插件已經(jīng)被官方標記為過時,還不趕緊用DataBinding或ViewBinding?

ViewModel實現(xiàn):

class DemoViewModel @ViewModelInject constructor(
    private val mRepository: Demo5Repository,
    @Assisted val savedState: SavedStateHandle
) : BaseViewModel() {
    val liveData = MutableLiveData<List<Industry>?>()
    //獲取行業(yè)數(shù)據(jù)
    fun requestIndustry() {
        viewModelScope.launch {
            //開始Loading
            loadStartLoading()
            val result = mRepository.getIndustry()
            result.checkResult({
                //處理成功的信息
                toast("list:$it")
                liveData.value = it
            }, {
                //失敗
                liveData.value = null
            })
            loadHideProgress()
        }
    }
}

Repository的實現(xiàn):其實和Model差不多的意思,數(shù)據(jù)倉庫而已,下面的一些注解是用到了Hilt依賴注入,不用直接new對象也是可以的,不要在意一些細節(jié)。

@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的實現(xiàn): 內(nèi)部做了一些基類的封裝,事件處理封裝為對象,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直接調(diào)用網(wǎng)絡(luò)請求,結(jié)果在xml中自動顯示
            mViewModel.requestIndustry()
        }
    }
}

Xml的實現(xiàn): 注意引用指向的包名要寫對,寫對了可以直接跳轉(zhuǎn)過去的。

<?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="獲取數(shù)據(jù)"
            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>

這樣就完成了一個基于數(shù)據(jù)驅(qū)動的DataBinding的MVVM。 如果上面一些代碼如果看不太明白,后面我可能會出DataBinding的封裝并開源。

截止到發(fā)稿日期為止,目前市面上最流行的還是MVVM框架,此框架唯一的槽點可能就是Databinding的不好調(diào)試吧,一旦出問題,有時候報錯信息莫名其妙的,沒有指向XML中某個數(shù)據(jù)或語法的錯誤,需要對DataBinding有一定的了解。 不過AS現(xiàn)在貌似越來越智能了,報錯信息都還指向蠻清晰的。MVVM完全可用的。

四. MVI框架

由于是出來沒多久,具體是不是叫MVI框架這個名字還不確定,大家都這么叫,姑且就叫MVI吧。伴隨Compose出現(xiàn)的框架,主流用于Compose應(yīng)用。

MVI框架是由Model View Intent組成的??梢运闵螹VVM的升級版,在之前我們都是通過在Activity中直接調(diào)用ViewModel的方法,現(xiàn)在改為發(fā)出操作指令,由ViewModel解析指令,調(diào)用對應(yīng)的方法,回調(diào)給Activity。

比如一個DemoActivity需要獲取行業(yè)數(shù)據(jù),學校數(shù)據(jù),等。那么就可以把數(shù)據(jù)和操作都封裝成指定的對象。

    //當前頁面所需的數(shù)據(jù)與狀態(tài)
    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調(diào)用相關(guān)的接口就不是直接調(diào)用ViewModel的方法,而是:

   override fun init() {
        //發(fā)送Intent指令,具體的實現(xiàn)由ViewModel實現(xiàn)
        mViewModel.dispatch(Damo14ViewModel.DemoAction.RequestAllData)
    }

那么ViewModel就需要解析指令:

    //Action分發(fā)入口
    fun dispatch(action: DemoAction) {
        when (action) {
            is DemoAction.RequestIndustry -> requestIndustry()
            is DemoAction.RequestSchool -> requestSchool()
            is DemoAction.RequestAllData -> getTotalData()
            is DemoAction.UpdateChanged -> changeData(action.isChange)
        }
    }
    //獲取行業(yè)數(shù)據(jù)
    private fun requestIndustry() {
      //xxx
    }

完整的代碼如下:

ViewModel的實現(xiàn):

class Damo14ViewModel @ViewModelInject constructor(
    private val mRepository: Demo5Repository,
    @Assisted val savedState: SavedStateHandle
) : BaseViewModel() {
    private val _viewStates: MutableLiveData<Demo14ViewState> = MutableLiveData(Demo14ViewState())
    //只需要暴露一個LiveData,包括頁面所有狀態(tài)
    val viewStates: LiveData<Demo14ViewState> = _viewStates
    //Action分發(fā)入口
    fun dispatch(action: DemoAction) {
        when (action) {
            is DemoAction.RequestIndustry -> requestIndustry()
            is DemoAction.RequestSchool -> requestSchool()
            is DemoAction.RequestAllData -> getTotalData()
            is DemoAction.UpdateChanged -> changeData(action.isChange)
        }
    }
    //獲取行業(yè)數(shù)據(jù)
    private fun requestIndustry() {
        viewModelScope.launch {
            //開始Loading
            loadStartLoading()
            val result = mRepository.getIndustry()
            result.checkSuccess {
                _viewStates.setState {
                    copy(industrys = it ?: emptyList())
                }
            }
            loadHideProgress()
        }
    }
    //獲取學校數(shù)據(jù)
    private fun requestSchool() {
        viewModelScope.launch {
            //開始Loading
            loadStartLoading()
            val result = mRepository.getSchool()
            result.checkSuccess {
                _viewStates.setState {
                    copy(schools = it ?: emptyList())
                }
            }
            loadHideProgress()
        }
    }
    //獲取全部數(shù)據(jù)
    private fun getTotalData() {
        //默認執(zhí)行在主線程的協(xié)程-必須用(可選擇默認執(zhí)行在IO線程的協(xié)程)
        launchOnUI {
            //開始Loading
            loadStartProgress()
            val industryResult = async {
                mRepository.getIndustry()
            }
            val schoolResult = async {
                mRepository.getSchool()
            }
            //一起處理數(shù)據(jù)
            val industry = industryResult.await()
            val school = schoolResult.await()
            //如果都成功了才一起返回
            if (industry is OkResult.Success && school is OkResult.Success) {
                loadHideProgress()
                //設(shè)置多種LiveData
                _viewStates.setState {
                    copy(industrys = industry.data ?: emptyList(), schools = school.data ?: emptyList())
                }
            }
        }
    }
    //改變狀態(tài)
    private fun changeData(isChanged: Boolean) {
        _viewStates.setState {
            copy(isChanged = isChanged)
        }
    }
    //當前頁面所需的數(shù)據(jù)與狀態(tài)
    data class Demo14ViewState(
        val industrys: List<Industry> = emptyList(),
        val schools: List<SchoolBean> = emptyList(),
        var isChanged: Boolean = false
    ) : BaseViewState()
    //如果想再度封裝,也可以把回調(diào)的結(jié)果封裝成類似Action的對象,由頁面判斷回調(diào)的是哪一種類型,進行相關(guān)的操作
    //這樣就不需要使用LiveData回調(diào)了,LiveData就只是作為保存數(shù)據(jù)的功能,由DemoEvent回調(diào)
//    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的實現(xiàn):

@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() {
        //監(jiān)聽兩者數(shù)據(jù)變化
        mViewModel.viewStates.observeState(
            this,
            Damo14ViewModel.Demo14ViewState::industrys,
            Damo14ViewModel.Demo14ViewState::schools
        ) { industry, school ->
            YYLogUtils.w("industry: $industry ; school: $school")
        }
        //只監(jiān)聽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() {
        //發(fā)送Intent指令,具體的實現(xiàn)由ViewModel實現(xiàn)
        mViewModel.dispatch(Damo14ViewModel.DemoAction.RequestAllData)
    }
    /**
     * DataBinding事件處理
     */
    inner class ClickProxy {
        fun getData() {
            //發(fā)送Intent指令,具體的實現(xiàn)由ViewModel實現(xiàn)
//            mViewModel.dispatch(Damo14ViewModel.DemoAction.RequestIndustry)
//            mViewModel.dispatch(Damo14ViewModel.DemoAction.RequestSchool)
            mViewModel.dispatch(Damo14ViewModel.DemoAction.UpdateChanged(true))
        }
    }
}

注意,有些MVI的寫法是回調(diào)給Activity的方式也是用對象封裝如我注釋的代碼:

    //如果想再度封裝,也可以把回調(diào)的結(jié)果封裝成類似Action的對象,由頁面判斷回調(diào)的是哪一種類型,進行相關(guān)的操作
    //這樣就不需要使用LiveData回調(diào)了,LiveData就只是作為保存數(shù)據(jù)的功能,由DemoEvent回調(diào)
//    sealed class DemoEvent {
//        object PopBack : DemoEvent()
//        data class ErrorMessage(val message: String) : DemoEvent()
//    }

也可以使用LiveData返回,我這里使用擴展方法observeState方法來監(jiān)聽,這樣可以保證只有你監(jiān)聽的對象發(fā)生了變化才會收到回調(diào)。這個擴展方法在MVVM框架也能使用。

擴展方法如下:

import androidx.lifecycle.*
import kotlin.reflect.KProperty1
/**
 * @auther Newki
 * @date 2022/2/10
 * @description LiveData的擴展 支持MVI模式 訂閱單個LiveData實現(xiàn)監(jiān)聽不同的操作與數(shù)據(jù)
 */
//監(jiān)聽一個屬性
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)
    }
}
//監(jiān)聽兩個屬性
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)
    }
}
//監(jiān)聽三個屬性
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()
}

太干了,一張圖都沒上,最后總結(jié)一下:

世界上本無框架,用的人多了就成了框架,適合自己的才是好的。不是一定說出了最新框架我就要用最新的框架,理解之后再使用才能得心應(yīng)手。

個人目前平時開發(fā)中用的也是MVVM框架。后期會出一些MVVM的封裝和用法開源。

以上就是Android開發(fā)框架MVC-MVP-MVVM-MVI的演變Demo的詳細內(nèi)容,更多關(guān)于Android框架MVC MVP MVVM MVI的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

最新評論