一.几种常见架构模式简介
MVC架构:
M指的是Model,数据模型;
V指的是界面,一般是xml布局,或者有些界面我们直接使用代码书写的,所以也包含Activity和Fragment;
C指的是Controller,控制逻辑,一般控制逻辑都在Activity,Fragment中。
优点:
把逻辑拆分开,避免耦合。
缺点:
我们通过上面的简单介绍就可以看到一些问题,首先Activity,Fragment担当的职责过多,违背职责单一原则。实际应用中,MVC架构下的Activity,Fragment代码往往几千行,极其臃肿。
MVP架构:
M指的是Model,还是数据模型;
V指的是IView,视图逻辑接口,而不是单纯的视图了;
P指的Presenter,向导交流中心,处理核心逻辑。
我们也总结一下MVP的优点和缺点:
优点:
1.完全的解耦。其原因是我们是面向接口编程的,职责十分清晰,属于设计上的依赖倒置。
2.Presenter和Model是可以复用的。单纯的界面变化,我们只需要修改IView的实现即可。
3.由于相互之间是完全解耦的,所以可以针对M,V,P分开单独测试。
4.三者之间相互不耦合,所以方便后续扩展和升级。
缺点:
1.面向接口编程,所以各种界面渲染场景比较多的时候,会造成接口爆炸的问题。
2.接口升级时,不单单要修改实现类,有时甚至需要修改接口。
MVVM架构:
M指的是Model,仍然是数据模型;
V指的是View,视图;
VM指得是ViewModel,属于关联视图和Model的。View和Model相互不关联,通过ViewModel进行双向绑定。修改Model后,View则对应发生变化。
优点:
1.代码书写上的简洁。因为代码中少了setText这样的代码。
2.职责的清晰。相关的业务逻辑都在每个ViewModel中,不同的ViewModel承担不同的了逻辑。
3.针对数据的改变会自动反馈到界面上,使用起来会方便的多。
缺点:
缺点这块也是个人的见解。
1.因为数据数双向自动绑定的,所以监控到底是哪个ViewModel最终去修改的text值变的困难。当然,可以通过监控数据的改变来解决这问题。
小结:
MVC和MVP都属于命令式的UI,所以都需要找到一个需要设置的对象,比如setText,setBackground等等。
而MVVM和MVI属于声明式UI,只需要修改对应的参数,而不需要给到被设置的对象,所以在前端交互上,会让我们感觉到更便捷。
二.MVI架构
MVI架构是面向意图编程,我们把各种诉求转化为一个个的意图,请求列表数据是一个意图,请求详细信息也是一个意图。
M:ViewModel,指的是可以被观测数据;
V:View,指的是视图;
I:Intent,指的是意图,比如获取列表数据,获取详情搜索数据等。
具体流程我们可以用下面这张图来概括:
三.使用MVI架构来写Compose
接下来,我们会按照上面图中的顺序,一步一步的来使用MVI框架编写一个完整的界面流程。
首先.我们先引入Compose框架。创建MVIComposeActivity。
ContentViewModel的话不着急,这一步只要先创建一个ContentViewModel的类即可。
class MVIComposeActivity : ComponentActivity() {
private val viewModel by viewModels<ContentViewModel>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
ContentView(viewModel)
}
}
}
接下来,我们就要开始构建我们的意图了。
ContentViewModel中,构建我们的意图,目前先定义两个,获取列表和获取详情。
sealed class ContentIntent {
//传递消息
object GetContent : ContentIntent()
class GetItemDetail(val select: Int) : ContentIntent()
}
这里肯定有人会问,为什么一个是object类型,一个class类型。因为第二个是带参数的,所以每次都需要重新构建一个意图,而第一个无参,则复用同一个就可以了。
第三步,我们来构建ViewModel
创建信道newsChannel。信道来接受各种用户行为发过来的意图。
创建状态管理uiState,由其完成数据的绑定。
最后在初始化的时候,进行意图和获取数据的关系绑定(handleIntent方法)。
class ContentViewModel : ViewModel() {
//Channel信道,意图发送别ViewModel,
val newsChannel = Channel<ContentIntent>(Channel.UNLIMITED)
//状态管理
var uiState by mutableStateOf(NaviViewState())
init {
handleIntent()
}
private fun handleIntent() {
viewModelScope.launch {
newsChannel.consumeAsFlow().collect() {
when (it) {
is ContentIntent.GetContent -> getContent()
is ContentIntent.GetItemDetail -> getNewsDetail(it);
}
}
}
}
private val newsFlow: Flow<List<String>> = flow {
val list = mutableListOf<String>()
list.add("111")
list.add("222")
emit(list)
}
private val detailFlow: Flow<List<String>> = flow {
val list = mutableListOf<String>()
list.add("111-详情")
list.add("222-详情")
emit(list)
}
private fun getContent() {
viewModelScope.launch {
newsFlow.flowOn(Dispatchers.Default).collect { contents ->
uiState = uiState.copy(dataList = contents)
}
}
}
private fun getNewsDetail(select: ContentIntent.GetItemDetail) {
viewModelScope.launch {
detailFlow.flowOn(Dispatchers.Default).collect { contents ->
uiState = uiState.copy(detailContent = contents[select.select])
}
}
}
}
第四步,回过头来,我们进入到View的流程。
进入到页面后,首先进行的意图是请求所有数据。
@Composable
fun ContentView(viewModel: ContentViewModel) {
//请求数据
val viewState = viewModel.uiState
//请求数据的意图
LaunchedEffect(key1 = true) {
viewModel.newsChannel.send(ContentIntent.GetContent)
}
//数据与View的绑定
Column(
Modifier
.fillMaxSize()
.verticalScroll(rememberScrollState())
) {
viewState.dataList!!.forEachIndexed { index, it ->
NewsItem(viewModel, position = index, news = it)
}
}
viewState.detailContent?.let {
ToastUtil.showCenterToast(it)
}
}
则此时我们上一步中构造的handleIntent方法中的newsChannel就会收到这个意图,并进行对应的处理:
private fun handleIntent() {
viewModelScope.launch {
newsChannel.consumeAsFlow().collect {
when (it) {
is ContentIntent.GetContent -> getContent()//这里进行处理
is ContentIntent.GetItemDetail -> getNewsDetail(it);
}
}
}
}
getContent方法中去请求数据(这里可以进行网络请求,我这里就不进行了),返回结果后,把数据绑定到uiState上。
private fun getContent() {
viewModelScope.launch {
newsFlow.flowOn(Dispatchers.Default).collect { contents ->
uiState = uiState.copy(dataList = contents)
}
}
}
至此,流程上就结束了。MVI架构下,我们只需要修改uiState就可以了。
完整代码地址:
android_all_demo/MVIComposeActivity.kt at master · aa5279aa/android_all_demo · GitHub