美文网首页
安卓应用开发中的MVC、MVP、MVVM、MVI(2)

安卓应用开发中的MVC、MVP、MVVM、MVI(2)

作者: 长点点 | 来源:发表于2023-03-06 09:40 被阅读0次

    1. MVI架构简介

    MVI是Model-View-Intent的缩写,是一种最新的安卓应用开发架构模式,受到了Cycle.js框架中单向数据流和循环性质的启发。MVI与其它常见的架构模式,如MVC、MVP或MVVM,有着很大的不同。

    1.1 MVI架构的组成

    MVI架构由三个主要组件组成:Model、View和Intent。

    • Model:表示应用程序的状态,包括数据、用户界面和业务逻辑。Model是不可变的,只能通过Intent来改变。
    • View:负责渲染Model,并将用户操作转换为Intent。
    • Intent:表示用户或系统对应用程序状态的改变意图。Intent是唯一能够触发Model更新的方式。

    1.2 MVI架构的工作流程

    MVI架构遵循一个单向数据流和循环反馈机制。其工作流程如下:

    • 用户或系统触发一个事件,例如点击按钮、滑动屏幕或接收通知。
    • View将事件转换为一个Intent,并发送给Model。
    • Model接收到Intent后,根据业务逻辑处理并更新自身状态。
    • Model将更新后的状态发送给View。
    • View根据Model渲染界面。

    这个过程不断重复,形成一个闭环。

    2. MVI架构在安卓开发中的优势

    MVI架构在安卓开发中有以下几个优势:

    2.1 易于测试

    由于MVI架构中Model是不可变且独立于View和Intent的,因此可以方便地对其进行单元测试。同时,由于View只负责渲染Model,并不涉及任何业务逻辑或状态管理,因此也可以轻松地对其进行UI测试。

    2.2 易于调试

    由于MVI架构中所有状态改变都是通过Intent来触发并记录在Model中的,因此可以方便地追踪和重现任何问题或异常。同时,由于Model是不可变且唯一确定界面显示内容和行为的源头,因此可以避免出现视图层级混乱或数据不一致等问题。

    2.3 易于维护

    由于MVI架构中各个组件之间有明确且简单的职责划分,并且遵循单向数据流原则,因此可以降低代码复杂度和耦合度,并提高代码可读性和可扩展性。

    3. MVI架构在安卓开发中的实践

    为了更好地理解和掌握MVI架构在安卓开发中如何实践,在本节我们将以一个简单而实用的计算器应用为例

    3.1 创建项目和布局文件

    首先,我们需要创建一个安卓项目,并在activity_main.xml文件中定义计算器应用的界面布局。布局文件的内容如下:

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">
    
        <TextView
            android:id="@+id/tv_result"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:gravity="end"
            android:textSize="32sp" />
    
        <GridLayout
            android:id="@+id/gl_buttons"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:columnCount="4">
    
            <Button
                android:id="@+id/btn_clear"
                style="@style/Widget.AppCompat.Button.Borderless.Colored"
                android:text="@string/clear" />
    
            <Button
                style="@style/Widget.AppCompat.Button.Borderless.Colored" />
    
            <Button
                style="@style/Widget.AppCompat.Button.Borderless.Colored" />
    
            <Button
                style="@style/Widget.AppCompat.Button.Borderless.Colored" />
    
            <Button
                style="@style/Widget.AppCompat.Button.Borderless.Colored" />
    
            <Button
                style="@style/Widget.AppCompat.Button.Borderless.Colored" />
    
            <Button
                style="@style/Widget.AppCompat.Button.Borderless.Colored" />
    
            <Button
                style="@style/Widget.AppCompat.Button.Borderless.Colored" />
            
           <!-- 省略部分按钮代码 -->
    
        </GridLayout>
    
    </LinearLayout>
    

    这段代码定义了一个垂直方向的线性布局,其中包含一个显示结果的文本视图和一个包含16个按钮的网格布局。每个按钮都有一个唯一的id和文本,分别对应数字、运算符或清除功能。

    3.2 定义Model类

    接下来,我们需要定义Model类,用于表示计算器应用的状态。Model类的内容如下:

    // Model.kt
    
    data class Model(
        val expression: String = "", // 表达式字符串,例如 "2+3*4-5="
        val result: String = "", // 结果字符串,例如 "9.0"
        val error: String = "" // 错误信息字符串,例如 "Invalid input."
    )
    

    这段代码定义了一个数据类Model,其中包含三个属性:expression、result和error。expression表示用户输入或显示的表达式字符串;result表示计算出或显示的结果字符串;error表示发生或显示的错误信息字符串。这三个属性都有默认值为空字符串。

    3.3 定义Intent类

    然后,我们需要定义Intent类,用于表示用户或系统对计算器应用状态改变的意图。Intent类是一个密封类(sealed class),其内容如下:

    // Intent.kt
    
    sealed class Intent {
        object Clear : Intent() // 清除意图,表示用户点击了清除按钮或系统初始化时触发。
        data class Append(val value: String) : Intent() // 追加意图,表示用户点击了数字或运算符按钮时触发。
        object Evaluate : Intent() // 计算意图,表示用户点击了等号按钮时触发。
    }
    

    这段代码定义了一个密封类Intent,并声明了三个子类:Clear、Append和Evaluate。Clear表示清除意图,即用户点击了清除按钮或系统初始化时触发;Append表示追加意图,即用户点击了数字或运算符按钮时触发,并携带一个value参数表示被点击按钮的文本值;Evaluate表示计算意图,即用户点击了等号按钮时触发。

    3.4 定义MainActivity类

    最后,我们需要定义MainActivity类,用于实现View和Intent之间的交互逻辑。MainActivity类继承MainActivity类继承自AppCompatActivity,并实现了一个名为MviView的接口。MviView接口定义了两个方法:render和intents。render方法用于根据Model渲染界面;intents方法用于返回一个Observable对象,该对象发射用户或系统产生的Intent。MainActivity类的内容如下:

    // MainActivity.kt
    
    class MainActivity : AppCompatActivity(), MviView<Model, Intent> {
    
        private lateinit var tvResult: TextView // 结果文本视图
        private lateinit var glButtons: GridLayout // 按钮网格布局
        private val buttons = mutableListOf<Button>() // 按钮列表
        private val calculator = Calculator() // 计算器对象,用于处理业务逻辑
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)
    
            tvResult = findViewById(R.id.tv_result) // 初始化结果文本视图
            glButtons = findViewById(R.id.gl_buttons) // 初始化按钮网格布局
    
            for (i in 0 until glButtons.childCount) { // 遍历按钮网格布局中的所有子视图
                val view = glButtons.getChildAt(i) // 获取当前子视图
                if (view is Button) { // 如果子视图是按钮类型
                    buttons.add(view) // 将按钮添加到按钮列表中
                    view.setOnClickListener { // 设置按钮点击监听器
                        when (view.id) { // 根据按钮id判断点击事件类型
                            R.id.btn_clear -> intents().onNext(Intent.Clear) // 如果是清除按钮,发送清除意图
                            R.id.btn_equal -> intents().onNext(Intent.Evaluate) // 如果是等号按钮,发送计算意图
                            else -> intents().onNext(Intent.Append(view.text.toString())) // 否则,发送追加意图,并携带按钮文本值作为参数
                        }
                    }
                }
            }
    
            bind() // 调用bind方法,将View和Model进行绑定
    
        }
    
        override fun render(model: Model) { 
            tvResult.text = model.expression + model.result + model.error 
            if (model.error.isNotEmpty()) {
                Toast.makeText(this, model.error, Toast.LENGTH_SHORT).show()
            }
        }
    
        override fun intents(): PublishSubject<Intent> {
            return PublishSubject.create()
        }
    
        private fun bind() {
            intents()
                .scan(Model()) { state, intent ->
                    calculator.process(state, intent)
                }
                .distinctUntilChanged()
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(this::render)
        }
    }
    

    这段代码定义了一个名为MainActivity的类,并实现了MviView接口。在onCreate方法中,我们初始化了结果文本视图、按钮网格布局和计算器对象,并为每个按钮设置了点击监听器。在点击监听器中,我们根据不同的事件类型,发送相应的Intent到intents方法返回的Observable对象中。然后我们调用了bind方法,将View和Model进行绑定。

    在bind方法中,我们使用scan操作符对intents发射的每个Intent进行处理,并更新Model状态。然后我们使用distinctUntilChanged操作符过滤掉重复或相同的Model状态。最后我们使用observeOn操作符切换到主线程,并订阅render方法。

    在render方法中,我们根据Model状态更新结果文本视图,并显示错误信息(如果有)。

    4. 优缺点总结

    模式 优点 缺点 应用场景 解决了哪些问题
    MVC 简单易懂,分工明确,有利于团队协作和维护 View和Model之间的耦合度高,导致视图层逻辑复杂,难以测试 适合简单的界面展示和交互 实现关注点分离,提高代码可读性和复用性
    MVP 解决了MVC中View和Model之间的耦合问题,降低了视图层的逻辑复杂度,提高了可测试性 Presenter和View之间的接口过多,增加了代码量和维护成本 适合复杂的界面展示和交互 实现视图和数据的解耦,提高代码可维护性和扩展性
    MVVM 基于数据绑定技术,进一步降低了View和ViewModel之间的耦合度,简化了代码量,提高了可维护性和可扩展性 数据绑定技术有一定的学习成本,可能存在内存泄漏或性能问题 适合需要频繁更新UI或者有复杂业务逻辑的界面 实现双向数据绑定,提高代码可读性和用户体验
    MVI 基于响应式编程思想,将应用程序看作一个状态机器,将用户输入、网络请求等事件统一处理,并生成新的状态更新UI 需要掌握响应式编程框架如RxJava等,状态管理可能比较复杂或冗余 适合需要处理多种异步事件或者有严格要求的用户体验的界面 实现单向数据流动,提高代码可预测性和稳定性

    相关文章

      网友评论

          本文标题:安卓应用开发中的MVC、MVP、MVVM、MVI(2)

          本文链接:https://www.haomeiwen.com/subject/lkzuldtx.html