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老皮!!!欢迎大家来找我探讨交流