一步步基于ViewModel协程搭建通用网络请求工具

本文主要介绍如何在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
image.png

HttpServiceMethod中:

一步步基于ViewModel协程搭建通用网络请求工具_第1张图片

具体的源码就不带大家看了,关键就在于SuspendForBody方法中的adapt方法中,内部会调用OkhttpClient#Call的方法enqueue开启线程去执行网络请求,执行完毕之后会再切换到调用请求的位置所在的线程,感兴趣的请自行查看。

你可能感兴趣的:(android网络请求协程)