MVC

MVC架构主要分为以下几部分:

1.View: 对应于xm布局文件和java代码动态view部分。

2.Controller: 主要负责业务逻辑,在android中由Activity承担,但xml视图能力太弱,所以Activity既要负责视图的显示又要加入控制逻辑,承担功能过多。

3.Model: 主要负责网络请求,数据库处理,I/O操作,即页面的数据来源。

如2所说,android中xml布局功能性太弱,activity实际上负责了View层与Controller层两者的功能,所以在android的mvc变成了这样:

MVP

MVP主要分为以下几部分:

1.View层:对应于Activity与xml,只负责显示UI,只与Presenter层交互,与Model层没有耦合。

2.Presenter层:主要负责处理业务逻辑,通过接口回调View层。

3.Model层:主要负责网络请求,数据库处理的操作。

MVP解决了MVC的两个问题,即Activity承担了两层职责与View层和Model层耦合的问题。

MVP问题:

1.Presenter层通过接口与View通信,实际上持有了View的引用。

2.业务逻辑的增加,一个页面变得复杂,造成接口很庞大。

MVVM

MVVM改动在于将Presenter改为ViewModel,主要分为以下几部分:

1.View: Activity和Xml,与其他的相同

2.Model: 负责管理业务数据逻辑,如网络请求,数据库处理,与MVP中Model相同

3.ViewModel:存储视图状态,负责处理表现逻辑,并将数据设置给可观察容器。

View和Presenter从双向依赖变成View可以向ViewModel发送指令,但ViewModel不会直接向View回调,而是让View通过观察者的模式去监听数据的改变,有效规避MVP双向依赖的缺点。

MVVM缺点:

  • 多数据流:View与ViewModel的交互分散,缺少唯一修改源,不易于追踪。

  • LiveData膨胀:复杂的页面需要定义多个MutableLiveData,并且都需要暴露为不可变的LivewData。

DataBinding、ViewModel 和 LiveData 等组件是 Google 为了帮助我们实现 MVVM 模式提供的架构组件,它们并不是 MVVM 的本质,只是实现上的工具。

  • Lifecycle: 生命周期状态回调;
  • LiveData: 可观察的数据存储类;
  • databinding: 可以自动同步 UI 和 data,不用再 findviewById();
  • ViewModel: 存储界面相关的数据,这些数据不会在手机旋转等配置改变时丢失。

MVI

mvi的改动在于将View和ViewModel之间的多数据流改为基于ViewState的单数据流,MVI分为四个部分:

  • View: Activity 和xml文件,与其他模式中的View的概念相同。
  • Intent: 定义数据操作,将数据传到Model的唯一来源。
  • ViewModel: 存储视图状态,负责处理表现逻辑,并将ViewState设置给可观察数据容器
  • ViewState: 一个数据类,包含页面状态和对应的数据。

MVI特点

  • 唯一可信源:数据只有一个来源(ViewModel),与MVVM思想相同
  • 单向数据流:状态向下流动,事件向上流动。
  • 响应式:ViewState包含页面当前状态和数据,View通过订阅ViewState就可以完成页面刷新。相比于 MVVM 是新的特性。

// 单数据流: View 和 ViewModel 之间只有一个数据流,只有一个地方可以修改数据,确保数据是安全稳定的。并且 View 只需要订阅一个 ViewState 就可以获取所有状态和数据,相比 MVVM 是新的特性;

响应式编程

响应式编程相对于命令式编程,

命令式编程:

val a = 1val b = 2var c = a + b // 3a = 2b = 2

c = a + b 执行完,后续c的值不会再改变,命令式编程是”一次性赋值”。

响应式编程:响应式编程是一种面向数据流变化传播声明式编程范式 “数据流”和“变化传播”是相互解释的:有数据流动,就意味着变化会从上游传播到下游。变化从上游传播到下游,就形成了数据流。

val flowA = MutableStateFlow(1)val flowB = MutableStateFlow(2)val flowC = flowA.combine(flowB) { a, b -> a + b }coroutineScope.launch {flowC.collect {Log.v("ttaylor","c=$it")}}coroutineScope.launch {delay(2000)flowA.emit(2)flowB.emit(2)}// 打印结果如下// c=3// c=4

单向数据流:

界面变化是数据流的末端,界面消费上游产生的数据,并随上游数据的变化进行刷新。

状态向下流动,事件向上流动的这种模式称为单向数据流

MVI强调数据的单向流动,主要分为几步:

1.用户操作以Intent的形式通知Model.

2.Model基于Intent更新State

3.View接收到State变化刷新UI

数据永远在一个环形结构中单向流动,不能反向流动。

缺点:

State 膨胀: 所有视图变化都转换为 ViewState,还需要管理不同状态下对应的数据。实践中应该根据状态之间的关联程度来决定使用单流还是多流;

内存开销: ViewState 是不可变类,状态变更时需要创建新的对象,存在一定内存开销;

局部刷新: View 根据 ViewState 响应,不易实现局部 Diff 刷新,可以使用 Flow#distinctUntilChanged() 来刷新来减少不必要的刷新。

Example:

MainActivity:

package com.lvlin.mvidemo.ui.view@ExperimentalCoroutinesApiclass MainActivity : AppCompatActivity() {private lateinit var mainViewModel: MainViewModelprivate var adapter = MainAdapter(arrayListOf())private lateinit var buttonFetchUser: Buttonprivate lateinit var recyclerview: RecyclerViewprivate lateinit var progressBar: ProgressBaroverride fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_main)buttonFetchUser = findViewById(R.id.buttonFetchUser)recyclerview = findViewById(R.id.recyclerView)progressBar = findViewById(R.id.progressBar)setupUI()setupViewModel()observeViewModel()setupClicks()}private fun setupUI() {recyclerview.layoutManager = LinearLayoutManager(this)recyclerview.run {addItemDecoration(DividerItemDecoration(recyclerview.context,(recyclerview.layoutManager as LinearLayoutManager).orientation))}recyclerview.adapter = adapter}private fun setupClicks() {buttonFetchUser.setOnClickListener {lifecycleScope.launch {mainViewModel.userIntent.send(MainIntent.FetchUser)}}}private fun setupViewModel() {mainViewModel = ViewModelProvider(this,ViewModelFactory(ApiHelperImpl(RetrofitBuilder.apiService))).get(MainViewModel::class.java)}private fun observeViewModel() {lifecycleScope.launch {mainViewModel.state.collect {when (it) {is MainState.Idle -> {}is MainState.Loading -> {buttonFetchUser.visibility = View.GONEprogressBar.visibility = View.VISIBLE}is MainState.Users -> {progressBar.visibility = View.GONEbuttonFetchUser.visibility = View.GONErenderList(it.user)}is MainState.Error -> {progressBar.visibility = View.GONEbuttonFetchUser.visibility = View.VISIBLEToast.makeText(this@MainActivity, it.error, Toast.LENGTH_LONG).show()}}}}}private fun renderList(users: List<User>) {recyclerview.visibility = View.VISIBLEusers.let { listofUsers -> listofUsers.let { adapter.addData(it) } }adapter.notifyDataSetChanged()}}

MainViewModel:

package com.lvlin.mvidemo.ui.viewmodelimport androidx.lifecycle.ViewModelimport androidx.lifecycle.viewModelScopeimport com.lvlin.mvidemo.data.repository.MainRepositoryimport com.lvlin.mvidemo.ui.intent.MainIntentimport com.lvlin.mvidemo.ui.viewstate.MainStateimport kotlinx.coroutines.ExperimentalCoroutinesApiimport kotlinx.coroutines.channels.Channelimport kotlinx.coroutines.flow.MutableStateFlowimport kotlinx.coroutines.flow.StateFlowimport kotlinx.coroutines.flow.collectimport kotlinx.coroutines.flow.consumeAsFlowimport kotlinx.coroutines.launchimport java.lang.Exception/** * @author: lvlin * @email: lin2.lv@lvlin.com * @date: 2022/7/12 */@ExperimentalCoroutinesApiclass MainViewModel(private val repository: MainRepository) : ViewModel() {val userIntent = Channel<MainIntent>(Channel.UNLIMITED)private val _state = MutableStateFlow<MainState>(MainState.Idle)val state: StateFlow<MainState>get() = _stateinit {handleIntent()}private fun handleIntent() {viewModelScope.launch {userIntent.consumeAsFlow().collect {when (it) {is MainIntent.FetchUser -> fetchUser()}}}}private fun fetchUser() {viewModelScope.launch {_state.value = MainState.Loading_state.value = try {MainState.Users(repository.getUsers())} catch (e: Exception) {MainState.Error(e.localizedMessage)}}}}

MainState:

package com.lvlin.mvidemo.ui.viewstateimport com.lvlin.mvidemo.data.model.User/** * @author: lvlin * @email: lin2.lv@lvlin.com * @date: 2022/7/12 */sealed class MainState {object Idle : MainState()object Loading : MainState()data class Users(val user: List<User>) : MainState()data class Error(val error: String) : MainState()}

MainIntent:

package com.lvlin.mvidemo.ui.intent/** * @author: lvlin * @email: lin2.lv@lvlin.com * @date: 2022/7/12 */sealed class MainIntent {object FetchUser : MainIntent()}

demo见github mvidemo

总结

-优点-缺点
MVC职责划分vc耦合严重
MVP引入P层,解耦VC页面复杂时,接口增多。
MVVM引入VM,替代接口回调数据流增多,livedata膨胀,模板代码增多
MVI借鉴前端框架,引入State,解决Livedata膨胀问题。响应式编程范式State膨胀,局部刷新,内存开销

**选择:**1.项目简单,未来改动也不大,不选择架构模式或方法,将模块封装好方便调用即可。

2.业务逻辑处理多的,mvp,mvvm都可以。