本文主要介绍如何在ViewModel封装通用的网络请求,不过在真正介绍封装前先讲解下使用到的基础知识:协程中异常的捕获
1.协程中异常的捕获
协程中异常捕获的方式有两种:
- 常见的
try-catch
相比较于CoroutineExceptionHandler
,这种方式可以灵活的捕捉可能发生异常的代码块:
lifecycleScope.launch {
//省略其他逻辑代码
try {
val result = 8 / 0
} catch (e: Exception) {
}
//省略其他逻辑代码
}
- 协程上下文元素
CoroutineExceptionHandler
这种方式捕捉的异常是直接捕捉的整个协程块的异常,颗粒度比较大,缺少灵活性。
lifecycleScope.launch(CoroutineExceptionHandler { _, throwable ->
Log.e("ChapterActivity", "exception occur: ${throwable.message}")
}) {
//省略其他逻辑代码
val result = 8 / 0
//省略其他逻辑代码
}
2.封装网络请求
首先定义一个类,类中分别定义三种函数类型属性,分别用作:发起请求、请求成功、请求失败
。
class Action {
//发起请求
var request: (suspend () -> Response)? = null
private set
//请求成功
var success: ((T) -> Unit)? = null
private set
//请求失败
var error: ((Throwable) -> Unit)? = null
private set
fun request(block: suspend () -> Response) {
request = block
}
fun success(block: (T) -> Unit) {
success = block
}
fun error(block: (Throwable) -> Unit) {
error = block
}
}
data class Response(val code: Int = -1, val data: T? = null)
定义一个扩展函数netRequest
,以DSL
的方式创建Action
对象:
fun ViewModel.netRequest(block: Action.() -> Unit) {
val action = Action().apply(block)
}
我们就可以这样创建Action
对象并为其函数类型的属性分别设置值:
netRequest {
request {
//模拟执行网络耗时
delay(5* 1000)
Response("dd")
}
success {
//请求成功执行代码逻辑
}
error {
//请求失败执行的代码逻辑
}
}
架子基本上我们已经搭建起来了,在处理网络请求之前先定义一个异常,专门用于处理请求响应失败的问题。
data class CustomException(val code: Int = -1, val throwable: String? = null) : Exception(throwable)
现在就得在netRequest
方法中利用协程
来执行网络请求、处理请求结果回调以及异常的捕获:
fun ViewModel.netRequest(block: Action.() -> Unit) {
val action = Action().apply(block)
viewModelScope.launch(CoroutineExceptionHandler { _, throwable ->
action.error?.invoke(throwable)
}) {
val result = withContext(Dispatchers.IO) {
action.request!!.invoke()
}
if (result.code == 0) {
action.success?.invoke(result.data)
} else {
action.error?.invoke(CustomException(result.code, "请求失败"))
}
}
}
通过viewModelScope
开启一个协程,不过我们并没有直接在外层的launch
就直接指定执行的线程,而是通过withContext
指定分发器Dispatchers.IO
去单独执行耗时请求,请求结束后会自动将线程从withContext
指定的线程中切换到外层launch
协程块所在的线程。
如果直接在外层launch
直接指定Dispatchers.IO
,这就会导致请求成功action.success
和请求失败action.error
的执行的环境为非主线程,避免后续开发者再额外处理这种非主线程回调的情况。
经过上述一步步流程,我们就最终搭建成功了一个简单网络请求工具类。
搭配Retrofit
提供的suspend
适配器
com.squareup.retrofit2:retrofit:2.9.0
这个版本的retrofit
已经适配了协程的suspend
:
判断是否为suspend修饰的请求方法:suspend
修饰方法会在方法参数上增加一个Continuation
参数: #RequestFactory.java
在HttpServiceMethod
中:
具体的源码就不带大家看了,关键就在于SuspendForBody
方法中的adapt
方法中,内部会调用OkhttpClient#Call
的方法enqueue
开启线程去执行网络请求,执行完毕之后会再切换到调用请求的位置所在的线程,感兴趣的请自行查看。