You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
classBaseResponse<T> {
@SerializedName("result")
var result =0
@SerializedName("message")
var message =""
@SerializedName("data")
var data:T?=nullval success:Boolean
get() = result ==0
}
网络请求
下面处理 okhttp 的 retrofit2:
classHttpClient {
//模拟请求,通过拦截器模拟请求数据var base_url ="https://apitest.com"//val okHttpClient by lazy {
OkHttpClient.Builder()
.build()
}
val retrofit by lazy {
Retrofit.Builder().client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create())
.baseUrl(base_url).build()
}
}
classCoroutinesViewModel : ViewModel() {
/** * This is the job for all coroutines started by this ViewModel. * Cancelling this job will cancel all coroutines started by this ViewModel.*/privateval viewModelJob =SupervisorJob()
/** * Cancel all coroutines when the ViewModel is cleared*/overridefunonCleared() {
super.onCleared()
viewModelJob.cancel()
}
/** * This is the main scope for all coroutines launched by ViewModel. * Since we pass viewModelJob, you can cancel all coroutines * launched by uiScope by calling viewModelJob.cancel()*/privateval uiScope =CoroutineScope(Dispatchers.Main+ viewModelJob)
privateval apiService =HttpClient.retrofit.create(ApiService::class.java)
funfetchData() {
/** * 启动一个协程*/
uiScope.launch {
val timeDiff = measureTimeMillis {
val responseOne = apiFetchOne()
val responseTwo = apiFetchTwo()
Logger.d("responseOne:${GsonUtils.getGson().toJson(responseOne)}")
Logger.d("responseTwo:${GsonUtils.getGson().toJson(responseTwo)}")
}
Logger.d("timeDiff: $timeDiff")
}
}
privatesuspendfunapiFetchOne(): BaseResponse<String> {
/** * 模拟网络请求,耗时 5s,打印请求线程*/Logger.d("apiFetchOne current thread: ${Thread.currentThread().name}")
delay(5000)
return apiService.fetchData()
}
privatesuspendfunapiFetchTwo(): BaseResponse<String> {
Logger.d("apiFetchTwo current thread: ${Thread.currentThread().name}")
delay(3000)
return apiService.fetchData()
}
}
2019-12-25 17:05:54.444 29664-29664/com.swensun.potato E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.swensun.potato, PID: 29664
retrofit2.HttpException: HTTP 500 server error
at retrofit2.KotlinExtensions$await$2$2.onResponse(KotlinExtensions.kt:53)
at retrofit2.OkHttpCall$1.onResponse(OkHttpCall.java:129)
at okhttp3.RealCall$AsyncCall.execute(RealCall.java:206)
at okhttp3.internal.NamedRunnable.run(NamedRunnable.java:32)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1162)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:636)
at java.lang.Thread.run(Thread.java:764)
D/Logger: apiFetchTwo current thread: DefaultDispatcher-worker-3
D/Logger: apiFetchOne current thread: DefaultDispatcher-worker-2
D/Logger: response error: HTTP 500 server error
缺点在于每次启动协程都需要进行处理,并且代码处理方式不优雅。
处理二: 最佳实践
对于所有异常,不仅需要知道它是什么异常,并且还需要方便的进行处理。
利用 Okhttp 的拦截器:
classErrorHandleInterceptor : Interceptor {
overridefunintercept(chain:Interceptor.Chain): Response {
try {
val request = chain.request()
// 无网络异常if (!NetworkUtils.isConnected()) {
throwNetworkErrorException("no network")
}
// 服务器处理异常val res = chain.proceed(request)
if (!res.isSuccessful) {
throwRuntimeException("server: ${res.message()}")
}
return res
} catch (e:Exception) {
val httpResult =BaseResponse<String>().apply {
result =900// 901, 902
data =null
message = e.message ?:"client internal error"
}
val body =ResponseBody.create(null, GsonUtils.getGson().toJson(httpResult))
returnResponse.Builder()
.request(chain.request())
.protocol(Protocol.HTTP_1_1)
.message(httpResult.message)
.body(body)
.code(200)
.build()
}
}
}
如上所示,定义异常处理,将上述异常转换为服务器正确响应的结果,自定义错误码,每个协议单独处理。
此时不用对协程异常进行捕获处理。
privateval okHttpClient by lazy {
OkHttpClient.Builder()
.addInterceptor(ErrorHandleInterceptor())
.addInterceptor(MockResponseInterceptor())
.build()
}
uiScope.launch {
val timeDiff = measureTimeMillis {
withContext(Dispatchers.IO) {
val responseOne = apiFetchOne()
val responseTwo = apiFetchTwo()
Logger.d("responseOne:${GsonUtils.getGson().toJson(responseOne)}")
Logger.d("responseTwo:${GsonUtils.getGson().toJson(responseTwo)}")
}
}
Logger.d("timeDiff: $timeDiff")
}
打印结果如下, 保证了代码的逻辑性。
D/Logger: apiFetchOne current thread:DefaultDispatcher-worker-1D/Logger: apiFetchTwo current thread:DefaultDispatcher-worker-3D/Logger: responseOne:{"message":"server: server error","result":900}
D/Logger: responseTwo:{"message":"server: server error","result":900}
D/Logger: timeDiff:8096
D/Logger: loading
D/Logger: apiFetchOne current thread:DefaultDispatcher-worker-1D/Logger: apiFetchTwo current thread:DefaultDispatcher-worker-2D/Logger:ERRORD/Logger: timeDiff:8137
Uh oh!
There was an error while loading. Please reload this page.
Android:How to make network requests gracefully
[TOC]
Retrofit2
在 retrofit2 的 2.6.0 版本中,增加了对 kotlin coroutines 的支持。
前提
在一般的业务中,请求服务器返回的结果都有如下的格式:
第一种情况表示请求成功,服务器根据业务返回响应的结果。这里暗含的前提是服务器可以处理请求,不包括网络错误等异常情况(后续处理)。
第二种情况表示请求失败,服务器给出对应的错误码进行后续处理。
因此可以定义以下类型:表示服务器的响应结果。
网络请求
下面处理 okhttp 的 retrofit2:
下面开始进行模拟的网络请求:
注意这里的区别,函数前加上 suspend 关键字,返回值为服务器返回的数据即可。
下面利用 okhttp 的拦截器去模拟服务器的响应数据。
下面利用retrofit2 的协程进行网络请求。viewModel 内容如下:
UI 端负责网络请求,打印结果。
结果如上,可以看到协程请求在 主线程执行,两个任务顺序执行,共计花费8s。 其中响应结果有成功和失败的情况。(MockResponseInterceptor)
其中 uiScope 开启协程,Dispatchers.Main + viewModelJob 前者指定执行线程, 后者可以取消协程操作。
并行处理
可以指定协程运行在 IO 线程,并通过 Livedata 通知 UI 线程更新UI。
如果两个请求无关联,可以通过 async 并行处理。
修改如下:
可以看到,两个任务并行处理,花费时间为处理任务中耗时最多的任务。
异常处理
目前 MockResponseInterceptor 模拟响应都是服务器正确处理(code:200)的结果,但还会有其他异常,比如请求时网络异常,服务器内部错误,请求地址不存在等。
接着模拟一个服务器内部错误:
此时继续请求,发生崩溃。协程执行中无法处理崩溃。
处理一:
协程处理异常:
在启动协程出进行异常捕获,处理异常。
缺点在于每次启动协程都需要进行处理,并且代码处理方式不优雅。
处理二: 最佳实践
对于所有异常,不仅需要知道它是什么异常,并且还需要方便的进行处理。
利用 Okhttp 的拦截器:
如上所示,定义异常处理,将上述异常转换为服务器正确响应的结果,自定义错误码,每个协议单独处理。
此时不用对协程异常进行捕获处理。
打印结果如下, 保证了代码的逻辑性。
补充: Important
在上述处理二的基础上,apiFetchOne 是同步执行, 执行过程中依然会抛出 IO 异常。
同样,一种方式是对每一个请求进行 try catch。坏处在于破坏了函数执行顺序。还是按照处理二的方式,在抛出异常的情况下,依然返回相对应的 HttpResult,直接进行判断。
对 retrofit 接口进行安全判断。
Important, Important, Important
ViewModel 的交互
在每个网络请求中,UI 可能都对应不同的状态,比如加载中,加载成功,加载失败。
此时可使用 LiveData, 将状态通知到 UI ,UI 根据不同的页面显示不同的页面。
定义状态
定义状态,定义viewModel 的扩展属性,用于观察数据,决定 UI 的显示。
在 UI 端就可以观察数据变动,改变 UI。
下面在网络请求过程中发出不同的状态:
打印结果如下:
新版本 ViewModel
在下面的版本中,添加 KTX 依赖项
本主题中介绍的内置协程范围包含在每个相应架构组件的 KTX 扩展程序中。请务必在使用这些范围时添加相应的依赖项。
ViewModelScope
,请使用androidx.lifecycle:lifecycle-viewmodel-ktx:2.1.0-beta01
或更高版本。LifecycleScope
,请使用androidx.lifecycle:lifecycle-runtime-ktx:2.2.0-alpha01
或更高版本。liveData
,请使用androidx.lifecycle:lifecycle-livedata-ktx:2.2.0-alpha01
或更高版本。改动如下:viewModel 中使用,viewModelScope,所在的viewModel 被销毁时,自动取消所有协程的执行。
代码地址:https://github.com/yunshuipiao/Potato
总结
The text was updated successfully, but these errors were encountered: