Android MVI架构解析以及与其他架构对比

MVC

Android MVI架构解析以及与其他架构对比_第1张图片

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

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

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

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

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

Android MVI架构解析以及与其他架构对比_第2张图片

MVP

Android MVI架构解析以及与其他架构对比_第3张图片

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。

    Android MVI架构解析以及与其他架构对比_第4张图片

Android MVI架构解析以及与其他架构对比_第5张图片

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 = 1
val b = 2
var c = a + b // 3
a = 2
b = 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

单向数据流:

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

Android MVI架构解析以及与其他架构对比_第6张图片

Android MVI架构解析以及与其他架构对比_第7张图片

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

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

@ExperimentalCoroutinesApi
class MainActivity : AppCompatActivity() {

    private lateinit var mainViewModel: MainViewModel
    private var adapter = MainAdapter(arrayListOf())

    private lateinit var buttonFetchUser: Button
    private lateinit var recyclerview: RecyclerView
    private lateinit var progressBar: ProgressBar


    override 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.GONE
                        progressBar.visibility = View.VISIBLE
                    }

                    is MainState.Users -> {
                        progressBar.visibility = View.GONE
                        buttonFetchUser.visibility = View.GONE
                        renderList(it.user)
                    }

                    is MainState.Error -> {
                        progressBar.visibility = View.GONE
                        buttonFetchUser.visibility = View.VISIBLE
                        Toast.makeText(this@MainActivity, it.error, Toast.LENGTH_LONG).show()
                    }
                }
            }
        }
    }

    private fun renderList(users: List<User>) {
        recyclerview.visibility = View.VISIBLE
        users.let { listofUsers -> listofUsers.let { adapter.addData(it) } }
        adapter.notifyDataSetChanged()
    }
}

MainViewModel:

package com.lvlin.mvidemo.ui.viewmodel

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.lvlin.mvidemo.data.repository.MainRepository
import com.lvlin.mvidemo.ui.intent.MainIntent
import com.lvlin.mvidemo.ui.viewstate.MainState
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.consumeAsFlow
import kotlinx.coroutines.launch
import java.lang.Exception

/**
 * @author: lvlin
 * @email: [email protected]
 * @date: 2022/7/12
 */
@ExperimentalCoroutinesApi
class MainViewModel(private val repository: MainRepository) : ViewModel() {

    val userIntent = Channel<MainIntent>(Channel.UNLIMITED)
    private val _state = MutableStateFlow<MainState>(MainState.Idle)
    val state: StateFlow<MainState>
        get() = _state

    init {
        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.viewstate

import com.lvlin.mvidemo.data.model.User

/**
 * @author: lvlin
 * @email: [email protected]
 * @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: [email protected]
 * @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都可以。

你可能感兴趣的:(android,架构,mvc)