网络请求可以说是构封Android开发中最常见的需求之一,基本上每个页面都需要发起几个网络请求。装快因此大家通常都会对网络请求进行一定的速优实现封装,解决模板代码过多,雅地重复代码,异常捕获等一些问题。网络 前面我们介绍了MVI架构的请求主要原理与更佳实践。相关文章如下所示: MVVM进阶版:MVI架构了解一下 我们这次一起来看下MVI架构下如何对网络请求进行封装,构封以及相对于MVVM架构有什么优势。装快 本文主要包括以下内容: 相信大家都看过不少MVVM架构下的网络请求封装,一般是雅地这样写的。 # MainViewModel class MainViewModel { private val _userLiveData = MutableStateLiveData val userLiveData : StateLiveData fun login(username: String, password: String) { viewModelScope.launch { _userLiveData.value = repository.login(username, password) } } } class MainActivity : AppCompatActivity() { fun initViewModel(){ // 请求网络 mViewModel.login("username", "password") // 注册监听 mViewModel.userLiveData.observeState(this) { onLoading { showLoading() } onSuccess { data -> mBinding.tvContent.text = data.toString() } onError { dismissLoading() } } } } 如上所示,就是请求最常见的MVVM架构下网络请求封装,主要思路如是构封: 这种封装的本质其实就是将请求的高防服务器回调逻辑处理迁移到View层了,这其实并不是我们想要的,我们的理想状况应该是逻辑尽量放在ViewModel中,View层只需要监听ViewModel层并更新UI。 既然这种封装其实违背了不在View层写逻辑的原则,那么为什么还有那么多人用呢? 本质上是因为ViewModel层与View层的通信成本比较高。 想象一下,如果我们不使用StateLiveData,针对每个请求就需要新建一个LiveData来表示请求状态,如果成功或失败后需要弹Toast或者Dialog,或者页面中有多个请求,就需要定义更多的LiveData, 同时为了保证对外暴露的LiveData不可变,每个状态都需要定义两遍LiveData。 这就是为什么这种封装其实违背了不在View层写逻辑但仍然流行的原因,因为在MVVM架构中每处理一种状态,就需要添加两个LiveData,成本较高,大多数人并不愿意支付这个成本。 而MVI架构正解决了这个问题。 之前已经介绍过了MVI架构,MVI架构使用方面我们就不再多说,源码库我们直接来看下MVI架构下怎么发起一个简单网络请求。 简单的网络请求class NetworkViewModel : ViewModel() { / * 页面请求,通常包括刷新页面loading状态等 */ private fun pageRequest() { viewModelScope.rxLaunch onRequest = { _viewStates.setState { copy(pageStatus = PageStatus.Loading) } delay(2000) "页面请求成功" } onSuccess = { _viewStates.setState { copy(content = it, pageStatus = PageStatus.Success) } _viewEvents.setEvent(NetworkViewEvent.ShowToast("请求成功")) } onError = { _viewStates.setState { copy(pageStatus = PageStatus.Error(it)) } } } } } # Activity层 class MainActivity : AppCompatActivity() { private fun initViewModel() { viewModel.viewStates.let { state -> //监听网络请求状态 state.observeState(this, NetworkViewState::pageStatus) { when (it) { is PageStatus.Success -> state_layout.showContent() is PageStatus.Loading -> state_layout.showLoading() is PageStatus.Error -> state_layout.showError() } } //监听页面数据 state.observeState(this, NetworkViewState::content) { tv_content.text = it } } //监听一次性事件,如Toast,ShowDialog等 viewModel.viewEvents.observe(this) { when (it) { is NetworkViewEvent.ShowToast -> toast(it.message) is NetworkViewEvent.ShowLoadingDialog -> showLoadingDialog() is NetworkViewEvent.DismissLoadingDialog -> dismissLoadingDialog() } } } } 如上,代码很简单: 通过使用MVI架构,所有的逻辑都在ViewModel中处理,同时添加新状态时不需要添加LiveData,降低了View与ViewModel的通信成本,解决了MVVM架构下的一些问题。 我们页面中通常会有一些局部网络请求,例如点赞,收藏等,这些网络请求不需要刷新整个页面,服务器托管只需要处理单个View的状态或者弹出Toast。下面我们来看下MVI架构下是如何实现的: / * 页面局部请求,例如点赞收藏等,通常需要弹dialog或toast */ private fun partRequest() { viewModelScope.rxLaunch onRequest = { _viewEvents.setEvent(NetworkViewEvent.ShowLoadingDialog) delay(2000) "点赞成功" } onSuccess = { _viewEvents.setEvent(NetworkViewEvent.DismissLoadingDialog) _viewEvents.setEvent(NetworkViewEvent.ShowToast(it)) _viewStates.setState { copy(content = it) } } onError = { _viewEvents.setEvent(NetworkViewEvent.DismissLoadingDialog) } } } 如上,针对局部网络请求,我们也是通过_viewStates与_viewEvents更新UI,并不需要添加额外的LiveData,使用起来比较方便。 页面中通常也会有一些多数据源的请求,我们可以利用协程的async操作符处理。 / * 多数据源请求 */ private fun multiSourceRequest() { viewModelScope.rxLaunch onRequest = { _viewEvents.setEvent(NetworkViewEvent.ShowLoadingDialog) coroutineScope { val source1 = async { source1() } val source2 = async { source2() } val result = source1.await() + "," + source2.await() result } } onSuccess = { _viewEvents.setEvent(NetworkViewEvent.DismissLoadingDialog) _viewEvents.setEvent(NetworkViewEvent.ShowToast(it)) _viewStates.setState { copy(content = it) } } onError = { _viewEvents.setEvent(NetworkViewEvent.DismissLoadingDialog) } } } 我们的APP中通常需要一些通用的异常处理,我们可以封装在rxLaunch扩展方法中。 如上: class CoroutineScopeHelper fun rxLaunch(init: LaunchBuilder val result = LaunchBuilder val handler = NetworkExceptionHandler { result.onError?.invoke(it) } return coroutineScope.launch(handler) { val res: T = result.onRequest() result.onSuccess?.invoke(res) } } } 如上: 我们上面通过自定义扩展函数实现了rxLaunch,其实是将协程转化为类RXJava的写法,但其实kotin协程已经有了自己的RXJava : Flow。我们完全可以利用Flow来实现同样的功能,不需要自己自定义。 简单的网络请求/ * 页面请求,通常包括刷新页面loading状态等 */ private fun pageRequest() { viewModelScope.launch { flow { delay(2000) emit("页面请求成功") }.onStart { _viewStates.setState { copy(pageStatus = PageStatus.Loading) } }.onEach { _viewStates.setState { copy(content = it, pageStatus = PageStatus.Success) } _viewEvents.setEvent(NetworkViewEvent.ShowToast(it)) }.commonCatch { _viewStates.setState { copy(pageStatus = PageStatus.Error(it)) } }.collect() } } Flow中提供了多个操作符,可以将多个Flow的结果组合起来。 / * 多数据源请求 */ private fun multiSourceRequest() { viewModelScope.launch { val flow1 = flow { delay(1000) emit("数据源1") } val flow2 = flow { delay(2000) emit("数据源2") } flow1.zip(flow2) { a, b -> "$a,$b" }.onStart { _viewEvents.setEvent(NetworkViewEvent.ShowLoadingDialog) }.onEach { _viewEvents.setEvent(NetworkViewEvent.DismissLoadingDialog) _viewEvents.setEvent(NetworkViewEvent.ShowToast(it)) _viewStates.setState { copy(content = it) } }.commonCatch { _viewEvents.setEvent(NetworkViewEvent.DismissLoadingDialog) }.collect() } } 如上,我们通过zip操作符组合两个Flow,它将合并两个Flow的结果并回调,我们在onEach中将得到数据源1,数据源2。 跟上面一样,有时我们需要配置一些能用的异常处理,可以看到,我们在上面调用了commonCatch,这其实也是我们自定义的一个扩展函数。 fun return this.catch { if (it is UnknownHostException || it is SocketTimeoutException) { MyApp.get().toast("发生网络错误,请稍后重试") } else { MyApp.get().toast("请求失败,请重试") } action(it) } } 如上所示,其实是对Flow.catch的一个封装,读者可以根据自己的需求封装处理。 可以看到,我上面都没有使用到Repository,都是直接在ViewModel层中处理。平常在项目开发中也可以发现,一般的页面并没有写Repository的需要,直接在ViewModel中处理即可。 但如果数据获取比较复杂,比如同时从网络与本地数据获取,或者需要复用网络请求等时,也可以添加一个Repository。我们可以通过Repository获取数据后,再通过_viewState更新页面状态,如下所示: private fun fetchNews() { viewModelScope.launch { flow { emit(repository.getMockApiResponse()) }.onStart { _viewStates.setState { copy(fetchStatus = FetchStatus.Fetching) } }.onEach { _viewStates.setState { copy(fetchStatus = FetchStatus.Fetched, newsList = it.data)} }.commonCatch { _viewStates.setState { copy(fetchStatus = FetchStatus.Fetched) } }.collect() } } 在MVVM架构下一般使用StateLiveData来进行网络架构封装,并在View层监听回调,这种封装方式的问题在于将网络请求回调处理逻辑转移到了View层,违背了尽量不在View层写逻辑的原则。 但这种写法流行的原因在于MVVM架构下View与ViewModel交互成本较高,如果每个请求的回调都在ViewModel中处理,则需要定义很多LiveData,这是很多人不愿意做的。而MVI架构解决了这个问题,将页面所有状态放在一个ViewState中,对外也只需要暴露一个LiveData。 MVI配合Flow或者自定义扩展函数,可以将页面逻辑全部放在ViewModel中,View层只需要监听LiveData的属性并刷新UI即可。当页面需要添加状态时,只需要给ViewState添加一个属性而不是添加两个LiveData,降低了View与ViewModel的交互成本。 如果你也觉得在View层监听网络请求回调不是一个很好的设计的话,那么可以尝试使用一下MVI架构。前言
MVVM架构下的网络请求封装与问题
MVI架构下封装网络请求
MVI架构与Flow结合实现网络请求
关于Repository
总结