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等,状况办理或许比较杂乱或冗余 合适需要处理多种异步事情或许有严格要求的用户体验的界面 完成单向数据流动,进步代码可猜测性和稳定性

关注公众号:Android老皮!!!欢迎大家来找我探讨交流