Kotlin Multiplatform for OpenHarmony 实战:OkHttp 的 ArkTS 语义复刻——拦截器链的回调世界
三方库适配征文 · 第二批 · 上游 square/okhttp parent-5.5.1
适配代码:https://atomgit.com/oh-tpc/okhttp
摘要
本文是「Kotlin Multiplatform for OpenHarmony 实战」系列第 37 篇,聚焦 OkHttp 在鸿蒙(ArkTS)侧的语义复刻。OkHttp 的核心价值在于拦截器链,而 ArkTS 没有suspend,无法直接照搬 Kotlin 的同步挂起链。本文给出解法:回调式异步链——请求沿链向下传递回调,响应沿链向上complete/fail送达,与同步链的return Response逐行对应。文章依次说明适配分层(语义层 + 引擎层)、RealAsyncChain的链末端出口设计、@ohos.net.http桥接的三个关键细节(ARRAY_BUFFER、会话createHttp/destroy、超时守卫定时器),并附环境搭建版本表、踩坑记录与 FAQ。读完可掌握一套可迁移到任意「链式处理」库的 ArkTS 网络适配骨架。
目录
- 一、OkHttp 是什么
- 二、适配路线
- 三、环境搭建指引
- 四、核心设计:同步链在回调世界的等价物
- 五、引擎层桥接要点
- 六、踩坑记录
- 七、FAQ
- 八、总结
一、OkHttp 是什么
OkHttp 是 JVM 生态使用量最大的 HTTP 客户端(47k stars):连接池、HTTP/2 多路复用、拦截器链、透明 GZIP、响应缓存、WebSocket 与同步/异步调用。KMP 生态大量库以其为默认网络层。
二、适配路线
| OkHttp(Kotlin) | 鸿蒙语义层 | 鸿蒙引擎层 |
|---|---|---|
Request/RequestBody/FormBody | Request/RequestBody/FormBody | — |
Response/ResponseBody | Response/ResponseBody | — |
Interceptor.intercept(chain) | Interceptor(同步)+AsyncInterceptor(异步) | BridgeAsyncInterceptor |
RealInterceptorChain | RealInterceptorChain/RealAsyncChain | — |
Call.execute()/enqueue() | Call接口 | 异步优先(execute 引导用异步) |
OkHttpClient(interceptors) | OkHttpClient(installEngine/addInterceptor) | — |
HttpLoggingInterceptor | — | AsyncLoggingInterceptor |
RetryAndFollowUpInterceptor | — | AsyncRetryInterceptor |
CallServerInterceptor | — | @ohos.net.http桥接 |
整体适配分层如下:
三、环境搭建指引
复现本文代码前,请先对齐以下工具链版本。版本不一致是 KMP 鸿蒙适配最常见的「跑不起来」原因。
| 组件 | 版本 | 说明 |
|---|---|---|
| DevEco Studio | DevEco Studio 26(HarmonyOS 6.0 SDK 配套版本) | 本文全部验证基于 DevEco Studio 26,工程模板与 hvigor 配置以该版本为准 |
| HarmonyOS SDK | API 20(HarmonyOS 6.0) | @ohos.net.http等系统能力随 SDK 提供 |
| Kotlin | 2.1.x | KMP 工程与kotlinx-coroutines版本需与之匹配 |
| Kotlin Multiplatform 插件 | 2.1.x(与 Kotlin 同版本) | Gradle 中kotlin("multiplatform")版本号 |
| Gradle | 8.9+ | 需支持 Kotlin 2.1 与 JDK 17 |
| JDK | 17 | DevEco Studio 26 内置 JDK 17,建议直接使用 |
| Node.js | 18.x 及以上 | hvigor 构建依赖 |
搭建步骤:
- 安装DevEco Studio 26,首次启动时按向导下载 HarmonyOS 6.0(API 20)SDK。
- 用 DevEco Studio 新建一个 Empty Ability 工程,确认
hvigor与 SDK 版本正常编译。 - 在工程根目录引入 KMP 模块,
build.gradle.kts中声明kotlin("multiplatform") version "2.1.x",并配置ohosArm64/ohosX64目标。 - 将本文的
okhttp适配模块作为commonMain+ohosMain源码集接入,同步 Gradle 后即可编译。 - 真机或模拟器运行验收页,先测正常请求,再测不可达地址的超时与取消分支。
开发情况如下:
官方参考链接:
- DevEco Studio 下载与版本说明:https://developer.huawei.com/consumer/cn/deveco-studio/
- HarmonyOS 开发者文档(ArkTS /
@ohos.net.http):https://developer.huawei.com/consumer/cn/doc/ - Kotlin Multiplatform 官方文档:https://kotlinlang.org/docs/multiplatform.html
- 本文适配代码仓库:https://atomgit.com/wdracky/kmp-ohos-adapters/tree/main/okhttp
提示:若使用其他 DevEco Studio 版本,请以对应版本的 SDK 与 hvigor 为准;本文的桥接代码只依赖
@ohos.net.http的稳定接口,跨版本差异主要集中在工程配置而非业务代码。
三、核心设计:同步链在回调世界的等价物
OkHttp 的精髓是拦截器链:每个拦截器拿到 Request,处理后交给下一个,终端拦截器真正发请求。Kotlin 里这是协程挂起函数;ArkTS 没有 suspend——解法是回调式异步链:
// 中间拦截器:把新回调向下传递chain.proceedAsync(req,(resp:Response)=>{this.logs.push('<-- '+resp.code);chain.complete(resp);},(err:Error)=>{this.logs.push('<-- ERR');chain.fail(err);});// 末端拦截器:网络完成后向上送达chain.complete(response);// 或 chain.fail(error)回调沿链向下、响应沿链向上——与同步链的「return Response」逐行对应,只是方向变成了回调。语义层同时保留同步链(测试用),双链并存是 ArkTS 网络适配的通用模式。
拦截器链的请求下行与响应上行时序如下:
四、引擎层桥接要点
consthttpRequest:http.HttpRequest={method:toOhosMethod(request.method),// HttpMethod → http.RequestMethodheader:this.buildHeader(request),// Map → RecordexpectDataType:http.HttpDataType.ARRAY_BUFFER,// 二进制安全readTimeout:this.readTimeoutMs,connectTimeout:this.connectTimeoutMs};if(request.body!==null){httpRequest.extraData=request.body.toBytes();// RequestBody → extraData}三个细节:expectDataType: ARRAY_BUFFER与 ktor 引擎策略一致,避免字符串编码损耗;每次createHttp()新会话、回调里destroy();readTimeout + 500ms守卫定时器兜底(防会话挂起)。
引擎层桥接的请求生命周期如下:
五、踩坑记录
- 链末端的回调出口:最初把 onResponse/onFailure 挂在 Bridge 上,导致「谁注册出口」不清;重构为 RealAsyncChain 持有整链出口,末端经
complete/fail送达,职责清晰。 - 取消语义:CancelToken 命中后回调静默(okhttp 的 canceled 分支不回调 onFailure)——验收页不可达地址测超时、正常请求测取消。
- 重试的边界:只对 5xx 与网络异常重试,4xx 不重试(对应 RetryAndFollowUpInterceptor 的语义);重试计数单独暴露供断言。
七、FAQ
Q1:DevEco Studio 26 与旧版本工程能直接互开吗?
不建议。DevEco Studio 26 对应 HarmonyOS 6.0(API 20),hvigor 与 SDK 版本跨度较大,旧工程建议新建后迁移源码,而不是直接升级打开。
Q2:编译报@ohos.net.http找不到?
检查module.json5是否声明了网络权限ohos.permission.INTERNET,以及 SDK 是否为 API 20;权限缺失时请求会在运行时失败而非编译期报错。
Q3:为什么execute()也走异步?
ArkTS 没有阻塞式网络 API,同步语义只能由异步实现「伪装」。语义层保留同步链仅为测试与兼容,生产路径请统一用enqueue()。
Q4:回调里拿不到响应,日志也没有?
优先排查链末端出口是否注册在RealAsyncChain上;若出口挂在 Bridge 上,取消或异常分支会静默丢弃回调。
Q5:超时时间设了却不生效?@ohos.net.http的readTimeout在部分场景下不会主动中断会话,因此额外加了readTimeout + 500ms的守卫定时器兜底。
Q6:重试次数怎么断言?AsyncRetryInterceptor单独暴露重试计数,测试中直接读取该计数即可,不必依赖日志字符串匹配。
Q7:这套结构能复用到其他库吗?
可以。凡是「链式处理 + 终端执行」的模型(图片加载管道、加解密管道、埋点管道)都能套用同一套回调链骨架。
八、总结
OkHttp 的适配价值不在「发一个 GET」(那@ohos.net.http一行就够了),而在拦截器链的完整语义:应用拦截器→重试→桥接→终端的分层、回调世界的等价变换、取消与超时的精确语义。
回顾全文,可以提炼出三条可复用的经验:
- 语义层与引擎层分离。语义层只描述「链怎么走、回调怎么传」,引擎层只负责「怎么把 Request 变成一次真实网络调用」。两层解耦后,换引擎(
@ohos.net.http→ 其他实现)不影响上层拦截器。 - 同步链与异步链双轨并存。生产路径走
AsyncInterceptor回调链,同步链保留给测试与兼容场景,二者共享同一套Request/Response模型,避免语义漂移。 - 边界语义要显式化。取消后回调静默、只对 5xx 与网络异常重试、超时用守卫定时器兜底——这些「不显眼」的边界恰恰是适配质量的分水岭,也是验收用例该覆盖的地方。
这套结构可以直接迁移到任何「链式处理 + 终端执行」的库(图片加载管道、加解密管道、埋点管道)。下一篇将继续沿 KMP 鸿蒙适配路线,拆解另一个高频三方库的语义复刻。
OkHttp 的适配价值不在「发一个 GET」(那 @ohos.net.http 一行就够了),而在拦截器链的完整语义:应用拦截器→重试→桥接→终端的分层、回调世界的等价变换、取消与超时的精确语义。这套结构可以直接迁移到任何「链式处理」的库(图片加载管道、加解密管道)。
欢迎加入 KMP&CMP 鸿蒙社区:https://atomgit.com/CPF-KMP-CMP