前言网络请求可以说是Android开发中最常见的需求之一。基本上,每个页面都需要发起若干次网络请求。因此,大家通常都会对网络请求进行一定程度的封装,以解决一些模板代码过多、代码重复、异常捕获等问题。前面我们介绍了MVI架构的主要原则和最佳实践。相关文章如下:MVVM进阶版:MVI架构下面我们就来看看MVI架构下是如何封装网络请求的,相比MVVM架构有哪些优势。本文主要包含以下内容:MVVM架构下网络请求封装及问题MVI架构下网络请求封装MVI架构与Flow结合实现网络请求MVVM架构下网络请求封装及问题相信大家见多了MVVM架构下网络的Request封装一般是这样写的。#MainViewModelclassMainViewModel{privateval_userLiveData=MutableStateLiveData()valuserLiveData:StateLiveData=_userLiveDatafunlogin(username:String,password:String){viewModelScope.launch{_userLiveData.value=repository.login(username),password)}}}classMainActivity:AppCompatActivity(){funinitViewModel(){//请求网络mViewModel.login("username","password")//注册监视器mViewModel.userLiveData.observeState(this){onLoading{showLoading()}onSuccess{data->mBinding.tvContent.text=data.toString()}onError{dismissLoading()}}}}如上图,是MVVM架构下最常见的网络请求封装。主要思路如下:添加一个StateLiveData,一个LiveData支持加载、加载成功、加载失败等多种状态。监听页面中的StateLiveData,处理页面中的onLoading、onSuccess、onError等逻辑。这种封装的本质其实就是把请求的回调逻辑处理迁移到了View层,这不是我们想要的。我们理想的情况应该是尽可能把逻辑放在ViewModel中,View层只需要监控ViewModel层,更新UI,既然这种封装其实违背了不在View层写逻辑的原则,为什么还有那么多人在用呢?本质上是因为ViewModel层和View层的通信成本比较高。想象一下,如果我们不使用StateLiveData,我们需要为每个请求创建一个新的LiveData来表示请求状态。如果我们需要在成功或失败后弹出Toast或Dialog,或者页面中有多个请求,我们需要定义更多的LiveData。同时,为了保证对外暴露的LiveData是不可变的,每个状态都需要定义两次LiveData。这也是为什么这种封装其实违背了逻辑不写在View层的原因,但是依然流行,因为在MVVM架构中每处理一个state,都需要添加两个LiveData,开销很大,而且大多数人都不愿意为此买单。成本。MVI架构正在解决这个问题。MVI架构下封装网络请求前面已经介绍过MVI架构,这里不再讲MVI架构的使用。下面直接看MVI架构下如何发起一个简单的网络请求。简单网络请求类NetworkViewModel:ViewModel(){/***页面请求,通常包括刷新页面加载状态等*/privatefunpageRequest(){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))}}}}}#ActivitylayerclassMainActivity:AppCompatActivity(){privatefuninitViewModel(){viewModel.viewStates.let{state->//监听网络请求状态state.observeState(this,NetworkViewState::pageStatus){when(it){isPageStatus.Success->state_layout.showContent()isPageStatus.Loading->state_layout.showLoading()isPageStatus.Error->state_layout.showError()}}//监听页面数据state.observeState(this,NetworkViewState::content){tv_content.text=it}}//监听一次性事件,如Toast、ShowDialog等ShowLoadingDialog->showLoadingDialog()isNetworkViewEvent.DismissLoadingDialog->dismissLoadingDialog()}}}}如上,代码很简单:页面的所有状态都存储在NetworkViewState中,如果后面需要添加状态,就不用不需要添加LiveData,只需要添加属性,存储在NetworkViewEvent中的所有事件,ViewModel发起网络请求,监听网络请求回调。ViewModelScope.rxLaunch是我们的自定义扩展方法。后面会介绍ViewModel在请求onRequest、onSuccess、onError时通过_viewStates更新页面。,通过_viewEvents添加一次比如ToastView层只需要监听ViewState和ViewEvent,更新UI即可。页面的逻辑全部写在ViewModel中。通过使用MVI架构,所有的逻辑都在ViewModel中处理。同时,在添加新状态的时候,不需要添加LiveData,降低了View和ViewModel之间的通信成本,解决了MVVM架构下的一些问题本地网络请求我们的页面中通常会有一些局部网络请求,比如点赞、收藏等,这些网络请求不需要刷新整个页面,只需要处理单个View状态或者弹出Toast。我们来看看它在MVI架构下是如何实现的:>{onRequest={_viewEvents.setEvent(NetworkViewEvent.ShowLoadingDialog)delay(2000)“像成功”}onSuccess={_viewEvents.setEvent(NetworkViewEvent.DismissLoadingDialog)_viewEvents.setEvent(NetworkViewEvent.ShowToast(it))_viewStates.setState{复制(content=)}}onError={_viewEvents.setEvent(NetworkViewEvent.DismissLoadingDialog)}}}如上,对于本地网络请求,我们同样通过_viewStates和_viewEvents来更新UI,不需要额外添加LiveData,使用起来更方便。多数据源请求页面中通常会有一些多数据源请求,我们可以使用协程的async算子来处理。/***多数据源请求*/privatefunmultiSourceRequest(){viewModelScope.rxLaunch{onRequest={_viewEvents.setEvent(NetworkViewEvent.ShowLoadingDialog)coroutineScope{valsource1=async{source1()}valsource2=async{source2()}valresult=source1.await()+","+source2.await()结果}}onSuccess={_viewEvents.setEvent(NetworkViewEvent.DismissLoadingDialog)_viewEvents.setEvent(NetworkViewEvent.ShowToast(it))_viewStates.setState{copy(content=it)}}onError={_viewEvents.setEvent(NetworkViewEvent.DismissLoadingDialog)}}}异常处理我们的APP通常需要一些常见的异常处理,我们可以将其封装在rxLaunch扩展方法中。如上:classCoroutineScopeHelper(privatevalcoroutineScope:CoroutineScope){funrxLaunch(init:LaunchBuilder.()->Unit):Job{valresult=LaunchBuilder().apply(init)valhandler=NetworkExceptionHandler{result.onError?.invoke(it)}returncoroutineScope.launch(handler){valres:T=result.onRequest()result.onSuccess?.invoke(res)}}}如上:rxLaunch是什么我们定义的扩展方法的本质是将协程转换为类似RxJava的回调。一般的异常处理可以写在自定义的NetworkExceptionHandler中。如果请求错误,将自动处理。处理完的异常会传递给onError,供我们进一步处理MVI架构。结合Flow实现网络请求我们通过上面的自定义扩展函数实现了rxLaunch,其实就是将协程转换成类似RXJava的写法,但实际上kotin协程已经有了自己的RXJava:Flow。我们可以使用Flow来实现同样的功能,而无需我们自己定制。简单网络请求/***页面请求,通常包括刷新页面加载状态等*/privatefunpageRequest(){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中发起网络请求,通过emit回调传递结果。onStart是请求的开始,在Activity中触发showLoading,在onEach中获取flowemit的结果,即回调成功,在这里更新请求状态和页面数据。commonCatch抓取到的异常部分网络请求与此类似,无需额外添加LiveData,这里不再赘述多数据源网络请求的流程。提供了多个运算符来组合多个流的结果。/***多数据源请求*/privatefunmultiSourceRequest(){viewModelScope.launch{valflow1=flow{delay(1000)emit("datasource1")}valflow2=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把两者结合起来operatorFlow,它会将两个Flow的结果结合起来回调,我们在onEach中得到数据源1和数据源2。异常处理同上。有时我们需要配置一些可用的异常处理。可以看到我们上面调用了commonCatch,其实就是我们自定义的一个扩展函数。funFlow.commonCatch(action:suspendFlowCollector.(cause:Throwable)->Unit):Flow{returnthis.catch{if(它是UnknownHostException||它是SocketTimeoutException){MyApp.get().toast("出现网络错误,请稍后重试")}else{MyApp.get().toast("请求失败,请重试")}action(it)}}as上面表明它其实是对Flow.catch的封装,读者可以根据自己的需要进行封装处理。关于Repository,大家可以看到上面我没有用到Repository,都是直接在ViewModel层处理的。平时在项目开发中,也可以发现一般页面不需要写Repository,直接在ViewModel中处理即可。但是如果数据获取比较复杂,比如同时从网络和本地获取数据,或者需要复用网络请求的时候等等,也可以添加一个Repository。我们可以通过Repository获取数据,然后通过_viewState更新页面状态,如下:(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即可。当页面需要添加state时,只需要在ViewState中添加一个属性即可,无需添加两个LiveData,降低了View与ViewModel的交互成本。如果你也觉得在View层监听网络请求回调不是一个好的设计,可以尝试使用MVI架构。