ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

OkHttp Coroutines 模块指南:用 `Call.executeAsync()` 在 Kotlin 协程中发起 HTTP 请求并理解双向取消语义

OkHttp Coroutines 模块指南:用 `Call.executeAsync()` 在 Kotlin 协程中发起 HTTP 请求并理解双向取消语义 OkHttp Coroutines 模块指南用Call.executeAsync()在 Kotlin 协程中发起 HTTP 请求并理解双向取消语义【免费下载链接】okhttpA meticulous HTTP client for the JVM, Android, and GraalVM.项目地址: https://gitcode.com/gh_mirrors/okh/okhttpOkHttp 的okhttp-coroutines是面向 Kotlin 协程客户端的可选扩展模块它为核心 OkHttp 库的Call增加了一个suspend扩展函数executeAsync()让你能以挂起函数的方式发起异步请求同时保持 OkHttp 自带 Dispatcher 的调度模型。读完本文你将掌握该模块的引入方式、底层实现原理基于suspendCancellableCoroutine、协程与 Call 之间的双向取消行为以及如何借助仓库内测试用例规避响应体泄漏等资源安全问题。模块定位一个为 Kotlin 协程提供挂起式 API 的可选构件在 OkHttp 仓库中okhttp-coroutines是一个独立、可选的模块见 settings.gradle.kts 中的include(:okhttp-coroutines)其模块描述见 okhttp-coroutines/README.mdSupport for Kotlin clients using coroutines.为使用协程的 Kotlin 客户端提供支持。该模块的 Java 模块描述文件 okhttp-coroutines/src/main/java9/module-info.java 显示它只依赖并导出极少的 APImodule okhttp3.coroutines { requires okhttp3; exports okhttp3.coroutines; }也就是说整个模块对外只暴露okhttp3.coroutines包下一个核心能力。从已发布的 API 二进制接口 okhttp-coroutines/api/okhttp-coroutines.api 可以看到全部公共 API 仅此一个public final class okhttp3/coroutines/ExecuteAsyncKt { public static final fun executeAsync (Lokhttp3/Call;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; }这是一个标准的suspend fun的 JVM 形态入参为Call与Continuation返回Response。因此可以概括为okhttp-coroutines 就是把 OkHttp 的异步回调式Call.enqueue(Callback)包装成一个挂起函数供协程场景直接调用。依赖方面仓库在 gradle/libs.versions.toml 中声明coroutines 1.11.0并在 CHANGELOG.md 的 5.4.0 版本说明中明确Upgrade: kotlinx.coroutines 1.11.0. This is used by the optionalokhttp-coroutinesartifact.即该模块是可选的仅当你需要协程扩展时才引入。快速上手README 中的最小用法根据 okhttp-coroutines/README.md 提供的示例最小用法如下val call client.newCall(request) call.executeAsync().use { response - withContext(Dispatchers.IO) { println(response.body?.string()) } }三个关键点需要理解executeAsync()是Call的扩展挂起函数它把一次异步请求包装成可挂起的调用协程挂起直到响应到达或请求失败读取响应体时切换到Dispatchers.IO因为网络栈本身不占用协程线程但读取 body 是阻塞式 IO 操作推荐放到 IO 调度器上执行use {}确保响应被释放Response实现了Closeable用use包裹可以保证无论成功还是异常都会关闭响应体这是防止连接泄漏的基本功。源码剖析suspendCancellableCoroutine背后的完整实现整个模块的核心实现只有几十行位于 okhttp-coroutines/src/main/kotlin/okhttp3/coroutines/ExecuteAsync.ktsuspend fun Call.executeAsync(): Response suspendCancellableCoroutine { continuation - continuation.invokeOnCancellation { this.cancel() } this.enqueue( object : Callback { override fun onFailure( call: Call, e: IOException, ) { continuation.resumeWithException(e) } override fun onResponse( call: Call, response: Response, ) { continuation.resume(response) { _, value, _ - value.closeQuietly() } } }, ) }逐行拆解其设计意图suspendCancellableCoroutine与普通suspendCoroutine不同它是可取消的这为下文双向取消语义提供了基础能力continuation.invokeOnCancellation { this.cancel() }注册取消回调——一旦承载本次调用的协程被取消就同步调用底层Call.cancel()把协程取消传递给 OkHttp 网络栈this.enqueue(Callback)内部仍走 OkHttp 标准的enqueue异步路径。这正是 README 中强调的使用 OkHttp 的标准 Dispatcher——请求调度、连接池、重试等行为与普通异步调用完全一致而不是启动一个协程去执行onFailure→continuation.resumeWithException(e)网络失败IOException如连接被重置、超时时将异常原样抛给协程调用方符合挂起函数的错误处理直觉onResponse→continuation.resume(response) { _, value, _ - value.closeQuietly() }注意这里使用了resume的onCancellation参数。如果响应已经返回、但协程在消费它之前被取消即 continuation 已取消、无法再恢复那么onCancellation会被执行自动调用closeQuietly()关闭响应体。调度模型为什么默认不使用 Kotlin 的 Dispatchers这是 README 中特别强调的一个设计点This is implemented usingsuspendCancellableCoroutinebut uses the standard Dispatcher in OkHttp. This means that by default Kotlins Dispatchers are not used.含义如下executeAsync()只是把Call.enqueue()的异步回调翻译成挂起恢复真正的网络调度仍由 OkHttp 的 Dispatcher默认线程池/连接池体系完成因此你不需要也不应该用withContext(Dispatchers.IO)去包裹executeAsync()本身——发起请求不阻塞任何协程线程唯一建议使用Dispatchers.IO的地方是读取响应体如response.body?.string()因为 body 的流式读取是阻塞操作。README 示例正是这样组织的。这种设计的好处是OkHttp 的连接复用、超时、重试、拦截器等既有机制全部原样生效协程只是薄薄的一层语法糖。双向取消协程取消 Call、Call 取消协程README 用三句话概括了取消语义Cancellation is implemented sensibly in both directions. Cancelling a coroutine scope will cancel the call. Cancelling a call will throw a CancellationException but not cancel the scope if caught.结合源码与测试可以验证这三条方向一取消协程作用域 → 取消 Call。源码中invokeOnCancellation { this.cancel() }保证这一点。ExecuteAsyncTest.kt 的timeoutCall测试用withTimeout(1.seconds)模拟超时服务端通过MockResponse.Builder().bodyDelay(5, TimeUnit.SECONDS)故意延迟响应断言抛出TimeoutCancellationException并验证call.isCanceled()为true——即作用域取消确实传递到了 Call。方向二取消 Call → 协程抛 CancellationException。但注意 README 的措辞Cancelling a call will throw aCancellationExceptionbut not cancel the scope if caught.即主动call.cancel()时挂起点会以取消异常唤醒但如果调用方捕获了这个异常外层作用域不会被牵连取消。这与协程标准的取消协作模型一致。关于取消引发的实际异常类型一个细节值得说明在cancelledCall测试ExecuteAsyncTest.kt中测试调用call.cancel()后断言抛出的是IOException并验证call.isCanceled()为true。这源于 OkHttp 底层enqueue回调在 Call 被取消时通常会以IOExceptionCanceled异常触发onFailure从而经resumeWithException抛出。所以在实际项目中对executeAsync()的异常处理建议同时覆盖IOException网络/取消与CancellationException协程协作取消两类情形。资源安全协程取消时响应体不会泄漏协程场景最容易踩的坑是响应已经返回、协程却在读取前被取消导致ResponseBody从未关闭、连接无法归还连接池。本模块从实现与版本历史两个层面都做了保障实现层面continuation.resume(response) { _, value, _ - value.closeQuietly() }的onCancellation回调会在协程已取消的情况下自动关闭响应体测试层面ExecuteAsyncTest.kt 的responseClosedIfCoroutineCanceled测试专门构造了一个ClosableCall在回调返回后立刻coroutineContext.job.cancel()取消协程最后断言call.canceled为true且call.responseClosed为true证明响应体确实被关闭版本历史层面CHANGELOG.md 中记录了该模块曾经的修复Fix inokhttp-coroutines: Dont leak response bodies inexecuteAsync(). We had a bug where ...——这正说明响应体泄漏曾经是真实缺陷当前实现已针对性修复。此外即使协程没有被取消也强烈建议像 README 示例那样使用use {}显式关闭响应双保险地避免泄漏。错误处理与超时完整的实战模式综合源码与测试executeAsync()的错误传播路径如下场景触发机制协程侧观察到的异常网络失败/连接被关闭底层onFailure→resumeWithException(e)IOException主动call.cancel()OkHttp 取消回调IOException底层Canceled见cancelledCall测试withTimeout/ 作用域取消invokeOnCancellation→call.cancel()TimeoutCancellationException等CancellationException见timeoutCall测试failedCall测试ExecuteAsyncTest.kt使用SocketEffect.ShutdownConnection模拟服务端在响应开始后立刻断开连接验证连接中途被重置时同样抛出IOException。基于以上行为一个健壮的调用范式可以组织为suspend fun fetch(client: OkHttpClient, url: String): String { val request Request.Builder().url(url).build() val call client.newCall(request) // 1) executeAsync 内部走 OkHttp 标准 Dispatcher无需包 IO 上下文 // 2) 需要整体超时保护时用 withTimeout会级联取消 Call return withTimeout(10.seconds) { call.executeAsync().use { response - // 3) 读取 body 属于阻塞 IO放到 Dispatchers.IO withContext(Dispatchers.IO) { response.body!!.string() } } } }版本演进该模块在 OkHttp 中的历史轨迹CHANGELOG.md 记录了okhttp-coroutines模块的关键演进可帮助你理解其成熟度5.4.0随 OkHttp 升级 kotlinx.coroutines 至 1.11.0该依赖仅服务于本可选构件Breaking 变更协程扩展被移入okhttp3.coroutines包此前与 OkHttp 核心包混用使用旧包名的代码需要迁移修复修复executeAsync()泄漏响应体的问题前文已述修复 5.0.0-alpha.13 中 JAR 构件发布不合法的问题API 收敛停止使用协程库的实验性 API对外接口更稳定。小结OkHttp 的okhttp-coroutines模块用最精简的 API一个suspend扩展函数为 Kotlin 协程客户端补齐了挂起式请求能力它基于suspendCancellableCoroutine实现复用 OkHttp 标准 Dispatcher 与完整网络栈提供双向取消语义并通过onCancellation关闭机制规避响应体泄漏。阅读本文后你可以直接查阅 ExecuteAsync.kt 的完整实现并用 ExecuteAsyncTest.kt 中的五个测试用例验证超时、取消、失败与资源关闭等全部行为。【免费下载链接】okhttpA meticulous HTTP client for the JVM, Android, and GraalVM.项目地址: https://gitcode.com/gh_mirrors/okh/okhttp创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表