MVC架构主要分为以下几部分:
1.View: 对应于xm布局文件和java代码动态view部分。
2.Controller: 主要负责业务逻辑,在android中由Activity承担,但xml视图能力太弱,所以Activity既要负责视图的显示又要加入控制逻辑,承担功能过多。
3.Model: 主要负责网络请求,数据库处理,I/O操作,即页面的数据来源。
如2所说,android中xml布局功能性太弱,activity实际上负责了View层与Controller层两者的功能,所以在android的mvc变成了这样:
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改动在于将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。
DataBinding、ViewModel 和 LiveData 等组件是 Google 为了帮助我们实现 MVVM 模式提供的架构组件,它们并不是 MVVM 的本质,只是实现上的工具。
mvi的改动在于将View和ViewModel之间的多数据流改为基于ViewState的单数据流,MVI分为四个部分:
// 单数据流: 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
界面变化是数据流的末端,界面消费上游产生的数据,并随上游数据的变化进行刷新。
状态向下流动,事件向上流动的这种模式称为单向数据流
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都可以。