Retrofit response.errorBody.string() 警告我在挂起函数中调用不适当的阻塞方法

Retrofit response.errorBody.string() gives me warning of inappropriate blocking method call in suspend function

我正在开发一个 Android 应用程序。

我正在使用 Retrofit2 和 Coroutine 从我的 Rest 获取一些数据 API。

当我的 Rest API 中抛出异常时,return 异常代码和异常消息的 HTTP 状态 = 4xx 或 5xx,如下面的屏幕截图

如果响应是异常,那么我会按照下面的代码将 exceptionCode 和 exceptionMessage 映射到我的 Android 应用程序中的响应。

val exceptionBody = Gson().fromJson(response.errorBody()?.string(), ExceptionResponse::class.java)

这是ExceptionResponseclass

data class ExceptionResponse(
    val exceptionCode: String,
    val exceptionMessage: String
)

这里有个问题。当我执行 response.errorBody()?.string()、Android Studio 时,Studio 会警告我“不适当的阻止方法调用”

这里是我的repository调用网络调用

override fun fetchData() {
    CoroutineScope(Dispatchers.IO).launch {
        val fetchedData = myRemoteDataSource.fetchData()
    }
}

这里是MyRemoteDataSourceclass

class MyRemoteDataSourceImpl(
    private val myAPIService: MyAPIService
): BaseRemoteDataSource(), MyRemoteDataSource {

    override suspend fun fetchData(): Resource<List<Data>> {
        return getResult {
            myAPIService.fetchData()
        }
    }
}

这是我的 BaseRemoteDataSource class,它有 getResult(),其中 errorBody.string 被称为

正如你在上面的截图中看到的,唯一没有给我警告的协程是最后一个

GlobalScope.launch(Dispatchers.IO) {
    Gson().fromJson(response.errorBody()?.string(), ExceptionResponse::class.java)
}

所以我对这个警告和 coroutineScope 有几个问题

  1. 为什么最后一个不给我警告,但其他协程作用域都给我警告?
GlobalScope.launch(Dispatchers.IO) {
    Gson().fromJson(response.errorBody()?.string(), ExceptionResponse::class.java)
}

  1. 看来我需要结构化并发,因为我需要解析 JSON 和 return 它。如果我 CoroutineScopeGlobalScope 那么我将 return null 除非我使用 scope.join。那么我不应该使用 coroutineScope() 吗,它使用 parent/caller 的协程作用域来实现结构化并发?

  1. 看起来 response.errorBody()?.string() 正在解析应该在 Dispatchers.Default 中的 JSON,不是吗?仅供参考,我包含 string()
  2. 的源代码
  public final String string() throws IOException {
    try (BufferedSource source = source()) {
      Charset charset = Util.bomAwareCharset(source, charset());
      return source.readString(charset);
    }
  }

  1. 使用最后一个可以吗,因为那时我正在 CoroutineScope 中创建 GlobalScope。

  1. withContext() 是像 coroutineScope() 一样使用调用者的协程作用域,还是使用不同的调度程序创建新的作用域?

抱歉一下子抛出了问题,但它们都是相关的。谢谢你们!!!

IO 上下文中调用阻塞执行是正常的,因为它旨在执行此类任务。因此,我认为最好的方法是在 ResponseBody 上编写一个扩展函数,以便在 IO 上下文中适当地执行它。不过在这个函数上,也出现了warning,不过我们知道我们处理的很好,所以可以抑制它。

@Suppress("BlockingMethodInNonBlockingContext")
suspend fun ResponseBody.stringSuspending() =
    withContext(Dispatchers.IO) { string() }

所以我们确定 ResponseBody.string() 将在 IO 上下文中执行,无论它是否可能被另一个上下文包裹:

Gson().fromJson(response.errorBody()?.stringSuspending(), ExceptionResponse::class.java)