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


    MVC

    img

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

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

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

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

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

    img

    MVP

    img

    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。

      img

    img

    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
    
    • 1
    • 2
    • 3
    • 4
    • 5

    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
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    单向数据流:

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

    img

    img

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

    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()
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94

    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: lin2.lv@lvlin.com
     * @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)
                }
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54

    MainState:

    package com.lvlin.mvidemo.ui.viewstate
    
    import 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()
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    MainIntent:

    package com.lvlin.mvidemo.ui.intent
    
    /**
     * @author: lvlin
     * @email: lin2.lv@lvlin.com
     * @date: 2022/7/12
     */
    sealed class MainIntent {
    
        object FetchUser : MainIntent()
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    demo见github mvidemo

    总结

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

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

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

  • 相关阅读:
    水仙花数(熟悉Python后再写)
    【Leetcode hot 100】96. 不同的二叉搜索树
    HTML5期末考核大作业——学生网页设计作业源码HTML+CSS+JavaScript 中华美德6页面带音乐文化
    京东怎么上架产品,定时上下架教程
    MIPI CSI-2笔记(11) -- Low Level Protocol(统一串行链路,Unified Serial Link)
    人工智能 AI 概念梳理
    asp毕业设计——基于asp+access的在线考试系统设计与实现(毕业论文+程序源码)——在线考试系统
    HackTheBox-Starting Point--Tier 1---Bike
    【Unity入门计划】用双血条方法控制伤害区域减血速度
    前端React项目的Next.js项目通过CSS引入自定义字体文件
  • 原文地址:https://blog.csdn.net/qq_36390114/article/details/126160017