开源鸿蒙平台 kmp 三方库「Fuel」适配全流程
本文记录
Fuel接入 OpenHarmony 的完整过程,覆盖网络库盘点、鸿蒙 kotlinx 制品线选型、真实核心库编译、ohosArm64/ohosX64目标、NetworkKit 原生 HTTP 引擎、Kotlin/Native 动态库、C ABI/N-API 桥接、ArkUI 真机页面、HAP 构建和真机验收。
Fuel是面向 Kotlin Multiplatform 的 HTTP 客户端库:通过Fuel.get(...)或字符串扩展发起挂起请求,以HttpResponse获取状态码、响应头和kotlinx.io.Source,以Flow<String>消费 SSE。此次适配保留公共 API 和共享请求逻辑,在ohosMain实现基于libnet_http.so的平台引擎,并将真实核心库、共享验收场景和桥接入口链接进双 ABI 动态库。用户提供的真机效果图已显示11 / 11 PASS;其中 POST 请求体项目验证的是当前平台限制下的预期拒绝,不能解读为鸿蒙已支持请求体上传。
项目地址: AtomGit/oh-tpc/fuel.git
上游仓库: kittinunf/fuel
开发工具: 华为云码道
环境安装: 环境安装指南
一、背景
1.1 为什么做 OpenHarmony 平台 KMP/CMP 适配
Fuel 把 HTTP 调用封装为 Kotlin 挂起函数:查询参数、请求头、请求方法由公共 API 建模,返回值统一为 HttpResponse,响应体通过 kotlinx-io 读取。上游 JVM 引擎使用 OkHttp,Apple 引擎使用系统网络能力;同一份业务代码能够复用请求描述,却需要各平台提供实际传输实现。OpenHarmony 应用同样需要这套调用方式,但上游没有对应的平台目标和网络引擎。
如果只把示例页面改成 ArkTS,页面能够显示按钮,却无法证明 Fuel 在鸿蒙设备上完成了请求构造、网络传输、回调处理、响应读取和资源释放。适配需要同时验证公共接口、平台引擎、协程与 IO 依赖、Native 动态库、C ABI、N-API、ArkUI 页面和 HAP 交付链路。
| 障碍 | 具体问题 |
|---|---|
| 目标缺失 | 必须增加 ohosArm64() / ohosX64(),让核心库生成 OpenHarmony KLIB。 |
| 传输实现缺失 | JVM 的 OkHttp 和 Apple 的 NSURLSession 不能直接作为鸿蒙引擎,需要对接 NetworkKit C API。 |
| 依赖矩阵 | 协程与 kotlinx-io 必须使用具有 OHOS 变体、且与选定编译器兼容的制品。 |
| 语言边界不同 | ArkTS 不能直接调用 Kotlin 挂起函数,需要 C ABI、N-API 异步工作和 JSON 结果。 |
| 回调与生命周期 | 原生 HTTP 回调不携带用户上下文,需要路由并发请求,正确处理取消、终态和指针所有权。 |
| 平台能力有边界 | 本次使用的原生 HTTP C API 没有请求体设置接口,不能把 POST/PUT/PATCH 声明为完整支持。 |
| 交付链路复杂 | .so、C++ 运行时、CMake、HAP、签名和设备运行需要分别验证。 |
因此,本项目把适配边界放在:核心库的 OHOS 目标与依赖、NetworkKit 平台引擎、示例桥接和 DevEco 宿主。共享网络调用仍使用 Fuel API;ArkTS 只负责输入地址、触发验收、解析结果和刷新页面。
1.2 库提供的能力
Demo 提供一轮真实网络验收,对应共享层的 11 个场景:
- 普通 HTTP:带查询参数的 GET、带请求头的 GET、HEAD、DELETE 和自定义 OPTIONS 方法;
- 响应读取:暴露响应头、读取
Source、验证响应文本中的 JSON 内容; - SSE 事件流:通过
httpSSE(...).first()等待首个有效事件; - 并发请求:并行发送 3 个 GET,检查状态码;
- 能力边界:鸿蒙端 POST body 应抛出
UnsupportedOperationException,JVM 端同一场景验证正常 POST。
单项响应为只包含一个结果对象的 JSON 数组:
| 字段 | 作用 |
|---|---|
name | 场景名称,例如 GET with query parameters。 |
passed | 本项是否符合验收预期;符合“不支持请求体”的拒绝行为也可通过。 |
detail | 状态码、回显结果、超时或错误详情。 |
公共请求模型与字符串扩展继续由核心库实现。鸿蒙平台补充实际传输、原生响应解析、SSE 数据分帧和资源管理;59 个核心/序列化/共享 JVM 测试结果提供主机侧回归依据,OHOS 专属测试另行编译,二者不混作真机执行结果。
1.3 实现适配
| 维度 | 要求 |
|---|---|
| 代码复用 | 核心公共 API 保留,通过新增 ohosMain 平台实现接入 NetworkKit,不另写一套演示 HTTP 客户端。 |
| 平台目标 | 核心库声明 ohosArm64 + ohosX64;核心库交付 KLIB,example/nativeApp 生成双 ABI 动态库。 |
| 构建隔离 | 使用 -PopenharmonyOnly=true 聚焦 JVM/OHOS 构建,常规模式保留 Apple 和 JVM 伴随模块。 |
| 桥接稳定 | 使用 3 个 C 导出和 UTF-8 JSON,明确返回内存的释放函数,不跨桥传递 Kotlin 对象。 |
| 依赖纪律 | OHOS 单独约束社区协程和 IO 版本,JVM 保留原依赖版本;Apple 使用兼容 Native 编译器的约束。 |
| UI 完整 | 地址输入、逐项结果、进度、运行、停止、超时退出和迟到结果隔离。 |
| 仓库规范 | 项目说明、文章、效果图和源码链接统一使用 AtomGit。 |
本项目的 ArkUI 页面不是 Compose Multiplatform UI 实现。Fuel 属于 KMP 网络基础库,ArkTS Demo 是验收宿主;真正接受验证的是 Kotlin 核心 API、鸿蒙网络引擎以及跨语言调用链。
二、实现路线图
第 1 阶段:项目初始化 ── 盘点模块与平台边界、确定 NetworkKit 路线和交付物
第 2 阶段:目标与依赖打通 ── 社区 Maven、Kotlin 2.2.21-1.0.0、双 OHOS 目标、兼容依赖
第 3 阶段:桥协议建模 ── 共享 11 项场景、单项结果 JSON、超时和错误通道
第 4 阶段:原生桥接 ── NetworkKit 回调、@CName、N-API 异步工作和内存释放
第 5 阶段:ArkUI 宿主 ── 两类地址、逐项进度、停止、超时和迟到结果隔离
第 6 阶段:示例与验证 ── 双 ABI 库、ELF 审计、HAP、手动签名和真机效果图
三、逐步实现过程
第 1 阶段:项目初始化
1.1 工程边界
fuel/ KMP 核心库,保留公共 API,新增 ohosMain 网络引擎
fuel/src/ohosMain/ OhosHttpLoader、HttpUrlFetcher、NetstackBridge 等
fuel/src/ohosTest/ 原生槽位、完成路径、cookie 所有权和原子交换测试
fuel-kotlinx-serialization/ 保留 JVM/Apple 扩展,本次没有 OHOS 产物
fuel-forge-jvm/ JVM 伴随模块,常规构建保留
fuel-jackson-jvm/ JVM 伴随模块,常规构建保留
fuel-moshi-jvm/ JVM 伴随模块,常规构建保留
example/shared/ 公共验收场景与 MockWebServer JVM 测试
example/nativeApp/ Kotlin/Native 动态库和 C 导出
example/harmonyApp/ DevEco Stage、CMake N-API 和 ArkTS 页面
scripts/ 构建、复制工程、本地回显服务和依赖审计
README.OpenHarmony.md / _CN.md 中英文适配说明
docs/openharmony/VALIDATION.md 构建与验证记录
doc/ 本文和 img/ 真机效果图
示例沿用已审核的 cmp_qrose 分层:根目录保留库模块,example/nativeApp 负责 Kotlin/Native,example/harmonyApp 负责 DevEco 宿主。Fuel 的根构建没有整体替换为另一个极简工程,rootProject.name 保持 Fuel-MPP;通过条件配置隔离无关目标,更符合该仓库已有的多模块结构。
1.2 工具链矩阵
| 项目 | 配置 |
|---|---|
| 库版本 | 3.0.0-alpha04-ohos.1,源码基线 57aa891,适配提交 51bf7cd |
| Kotlin Multiplatform / Native | HarmonyOS Kotlin 2.2.21-1.0.0 |
| JDK | 21,通过 JAVA_HOME 指定 |
| OpenHarmony 目标 | ohosArm64 + ohosX64,保留 JVM 作交叉验证 |
| Gradle | 8.14.3 |
| OHOS kotlinx | coroutines 1.10.2-1.0.0、IO 0.9.0-1.0.0 |
| JVM kotlinx | coroutines 1.10.2、IO 0.9.0,serialization-json-io 1.10.0 |
| Apple 兼容约束 | IO 0.9.0-1.0.0、serialization-json-io 1.9.1-1.0.0 |
| 示例 target / compatible SDK | 6.0.0(20) / API 20 |
| ABI | arm64-v8a + x86_64 |
| Bundle | com.example.fuel |
export JAVA_HOME="/path/to/jdk-21"
java -version
./gradlew --version
页面显示的 HarmonyOS 6.0.0 · API 20 是示例配置说明,不是读取设备运行时版本的结果。不能据此判断截图设备的实际系统版本。
1.3 源码依赖度盘点
| 模块 | 依赖形态 | 处置 |
|---|---|---|
fuel/commonMain | 请求模型、公共 API、协程、kotlinx-io | 编译复用,补充可共享测试的 SSE 解析和槽位选择逻辑 |
fuel/jvmMain | OkHttp | 保留 JVM 引擎与本地服务测试 |
fuel/appleMain | Apple 系统网络能力 | 常规构建保留,增加兼容依赖约束 |
fuel/ohosMain | NetworkKit C API、原生回调与内存 | 新增平台引擎及生命周期管理 |
fuel-kotlinx-serialization | serialization-json-io、Result 等 | 保留 JVM/Apple,本次不声明 OHOS 交付 |
| JVM 伴随模块 | Forge、Jackson、Moshi | 常规构建参与,鸿蒙聚焦模式不配置 |
example/shared | project(":fuel")、协程、IO | 同一套场景用于 JVM 与设备验收 |
平台入口由 FuelBuilder 的 OHOS actual 返回 OhosHttpLoader。GET、HEAD、DELETE 和自定义方法进一步转到 HttpUrlFetcher,经 OH_Http_CreateRequest / OH_Http_Request 发起请求;SSE 则通过数据回调组装事件并输出 Flow<String>。
1.4 页面边界
标题区 Fuel(网络请求库)/ Fuel Acceptance / 工具链和目标 API 说明
地址区 HTTP 回显服务地址、SSE 流地址,两者均可修改
状态行 就绪、当前第几项、通过数量、停止或错误
操作区 运行检查;执行期间显示停止按钮
结果区 已完成场景的名称、通过状态和详细信息,逐项追加
默认 HTTP 服务为 https://httpbin.org,默认 SSE 为 Wikimedia recentchange 流。页面不在启动时自动发起网络验收,用户点击运行后才逐项调用 Native;无法访问外部服务时,可替换为项目本地回显服务的局域网地址。
第 2 阶段:目标与依赖打通
2.1 制品线验证(本适配的成败手)
Fuel 核心需要协程和 kotlinx-io。只给 Kotlin 编译器增加 OHOS 目标还不够,依赖本身也必须能解析到相应变体。本次使用以下社区制品:
org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.2-1.0.0
org.jetbrains.kotlinx:kotlinx-io-core:0.9.0-1.0.0
目标:ohosArm64 / ohosX64
仓库:https://maven.eazytec-cloud.com/nexus/repository/maven-public/
依赖处理分平台进行:JVM 保留原始版本,OHOS source set 通过 strictly(...) 约束社区版本。Apple 端还要兼顾 Native 元数据兼容,因此固定使用兼容 IO 与 serialization-json-io 制品。不能把某一个平台解析成功,等同于所有平台都可以共享同一组依赖版本。
2.2 工具链钉版(为什么是 2.2.21-1.0.0)
本次实现使用 HarmonyOS Kotlin 2.2.21-1.0.0,配套 Gradle 8.14.3 与 JDK 21。Kotlin 插件、Native 发行包和 serialization 编译插件使用匹配版本,避免插件与编译器处于不同版本线。
settings.gradle.kts 中的关键配置为:
pluginManagement {
repositories {
maven("https://maven.eazytec-cloud.com/nexus/repository/maven-public/")
mavenCentral()
gradlePluginPortal()
}
includeBuild("plugins")
}
rootProject.name = "Fuel-MPP"
include(":fuel")
include(":fuel-kotlinx-serialization")
include(":example:shared")
include(":example:nativeApp")
根工程的 allprojects.repositories 同样加入社区 Maven。openharmonyOnly 条件块控制 JVM 伴随模块和原生示例的配置;核心模块内部用同一属性决定是否声明 Apple 目标。
2.3 编译真实引擎
核心模块保留已有插件与 JVM 配置,增加以下 OHOS 配置。下列片段摘录目标和依赖部分:
kotlin {
applyDefaultHierarchyTemplate()
ohosArm64()
ohosX64()
sourceSets {
val ohosMain by getting {
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core") {
version { strictly(libs.versions.kotlin.coroutines.ohos.get()) }
}
implementation("org.jetbrains.kotlinx:kotlinx-io-core") {
version { strictly(libs.versions.kotlinx.io.ohos.get()) }
}
}
}
}
}
./gradlew -PopenharmonyOnly=true \
:fuel:compileKotlinOhosArm64 :fuel:compileKotlinOhosX64
两个目标编译的是真实 Fuel 核心库和对应引擎。公共 API 不需要改成示例专用接口,业务侧仍可使用:
val response = Fuel.get(
"https://httpbin.org/get",
parameters = listOf("fuel" to "ohos"),
)
val body = response.source.readString()
2.4 测试依赖的处置:JVM 执行与 OHOS 编译分开
MockWebServer 属于 JVM 测试依赖,放在 jvmTest;公共解析和请求模型测试仍放在 commonTest。OHOS 专属回调、指针和槽位测试放在 ohosTest,本次没有全局禁用 OHOS 测试编译。
./gradlew -PopenharmonyOnly=true \
:fuel:jvmTest :fuel-kotlinx-serialization:jvmTest :example:shared:jvmTest \
:fuel:compileTestKotlinOhosArm64 :fuel:compileTestKotlinOhosX64
OHOS 专属测试共 9 项,已通过双目标编译;这不表示 9 项原生测试已在设备上执行。共享 JVM 套件有 4 个测试,其中一个执行完整的 11 项验收场景,其余覆盖单项请求、超时和外部取消传播。
第 3 阶段:桥协议建模
3.1 JSON 契约
Fuel 公共 API / OhosHttpLoader / NetworkKit
↓ HTTP、Source、SSE、并发和能力限制
AcceptanceChecks.kt(共享 11 项场景)
↓ CheckResult
NativeChecks.kt(C ABI,UTF-8 JSON 数组)
↓
N-API runCheck(index, baseUrl, sseUrl): Promise<string>
↓
ArkUI Index.ets(逐项更新结果)
参数与响应示例:
参数:index = 0
baseUrl = "https://httpbin.org"
sseUrl = "https://stream.wikimedia.org/v2/stream/recentchange"
成功:[{"name":"GET with query parameters","passed":true,
"detail":"status=200, query echoed"}]
失败:[{"name":"GET with query parameters","passed":false,
"detail":"Timed out after 12.0s; check endpoint and network connectivity"}]
HTTP 响应体不直接作为 ArkTS 桥协议:共享场景先用 Fuel 读取并断言,再输出可展示的结果。这样 JVM 与 OHOS 能复用同一个检查函数,ArkTS 不需要复制网络断言。
3.2 载荷类型为什么固定 CheckResult
public class CheckResult(
public val name: String,
public val passed: Boolean,
public val detail: String,
)
示例桥关注验收结果,固定结构足以表达成功、失败和详情。每次 runCheck 只返回一个对象的数组,ArkTS 检查数组长度为 1 后追加到页面。旧的全套 runChecks 仍保留,但页面使用单项入口,避免等完整套件结束才看到结果。
Kotlin 侧 runBlocking 将挂起场景收进同步 C 调用;同步 C 调用由 N-API 工作线程执行,ArkTS 主线程等待 Promise。Source、Flow、协程和 Kotlin 对象均留在 Kotlin 侧,不作为裸对象传递到 ArkTS。
3.3 错误通道
场景层将普通异常变成 CheckResult(passed = false, detail = ...),并通过 withTimeoutOrNull 为每项设置 12 秒时限;外部 CancellationException 继续传播。C 导出在调用边界捕获失败,避免未处理的 Kotlin 异常穿过 FFI。
N-API 负责参数检查和工作项创建,索引必须在 0..10。桥接失败走 Promise 拒绝或同步异常,页面统一捕获,保留已完成结果并退出运行状态。当前 Native 工作未结束时,C++ 原子忙碌标志拒绝新的工作项,避免重复排队。
第 4 阶段:原生桥接(技术难点)
4.1 三层桥接
ArkTS:runCheck(index, baseUrl, sseUrl)
│ Promise<string>
▼
C++ N-API:libfuel_napi.so,napi_async_work
│ 整数 + UTF-8 C 字符串
▼
Kotlin/Native:libohosfuel.so,FuelSampleRunCheck
│ shared 场景 → Fuel → NetworkKit C API
▼
JSON 结果 → C++ 复制 → FuelSampleFree → ArkTS JSON.parse
网络引擎的回调桥同样关键。NetworkKit HTTP C 回调没有用户数据指针,NetstackBridge 使用 32 个固定槽位和对应静态跳板函数,将响应、数据和取消事件路由给本次请求。槽位耗尽时快速失败,不能让两个请求误用同一个处理器。
4.2 Kotlin/Native 导出函数
单项入口的主要实现为:
@CName("FuelSampleRunCheck")
public fun runNativeCheck(
index: Int,
baseUrl: CPointer<ByteVar>,
sseUrl: CPointer<ByteVar>,
): CPointer<ByteVar> {
val result = try {
runBlocking { runAcceptanceCheck(index, baseUrl.toKString(), sseUrl.toKString()) }
} catch (cause: Throwable) {
CheckResult("Check ${index + 1}", false, "${cause::class.simpleName}: ${cause.message}")
}
val bytes = listOf(result).toJson().encodeToByteArray()
val memory = nativeHeap.allocArray<ByteVar>(bytes.size + 1)
bytes.forEachIndexed { offset, byte -> memory[offset] = byte }
memory[bytes.size] = 0.toByte()
return memory
}
| C ABI 符号 | ArkTS 方法 | 作用 |
|---|---|---|
FuelSampleRunCheck | runCheck(index, baseUrl, sseUrl) | 单项验收,页面当前入口 |
FuelSampleRunChecks | runChecks(baseUrl, sseUrl) | 完整套件,保留的兼容入口 |
FuelSampleFree | C++ 内部调用 | 释放由 nativeHeap 分配的返回字符串 |
返回值由 nativeHeap 分配,因此应使用项目自己的 FuelSampleFree。本项目没有使用导出符号表的 DisposeString 协议,不能套用其他仓库的字符串释放方式。
4.3 C++ N-API 方法分发
napi_property_descriptor methods[] = {
{"runChecks", nullptr, RunChecks, nullptr, nullptr, nullptr, napi_default, nullptr},
{"runCheck", nullptr, RunChecks, nullptr, nullptr, nullptr, napi_default,
reinterpret_cast<void*>(1)}
};
napi_define_properties(env, exports, 2, methods);
两个方法复用参数解析与工作项逻辑,通过描述符数据区分单项和全套。执行回调调用 Kotlin C 导出,将返回内容复制到 std::string,随后调用 FuelSampleFree;完成回调再创建 JavaScript 字符串并 resolve/reject Promise。
忙碌标志在 Promise 完成前清除,允许页面正常衔接下一项。页面点击停止或看门狗超时后,后台原生工作仍可能尚未结束,因此忙碌保护放在进程内的 C++ 层,不依赖某个页面实例的布尔状态。
4.4 CMake 和类型声明
add_library(ohosfuel SHARED IMPORTED)
set_target_properties(ohosfuel PROPERTIES
IMPORTED_LOCATION "${FUEL_LIB_DIR}/libohosfuel.so"
IMPORTED_NO_SONAME TRUE)
add_library(fuel_napi SHARED napi_init.cpp)
target_compile_features(fuel_napi PRIVATE cxx_std_17)
target_include_directories(fuel_napi PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/include")
target_link_libraries(fuel_napi PRIVATE ohosfuel libace_napi.z.so)
target_link_options(fuel_napi PRIVATE "-Wl,--no-undefined" "-Wl,-rpath,$ORIGIN")
FUEL_LIB_DIR 按 OHOS_ARCH 指向 entry/libs/<abi>。Kotlin 库或匹配的 C++ 运行时不存在时,CMake 直接报错并提示先运行 prepareOhos;POST_BUILD 将两者复制到 N-API 输出目录。C 头文件使用编译器生成的 libohosfuel_api.h,由 Gradle 自动复制。
export const runChecks: (baseUrl: string, sseUrl: string) => Promise<string>;
export const runCheck: (index: number, baseUrl: string, sseUrl: string) => Promise<string>;
每个 ABI 都必须携带 libfuel_napi.so、libohosfuel.so 和对应的 libc++_shared.so。C++ 运行时从匹配 Kotlin/Native 工具链中定位,不能任意替换为另一个 SDK 中的同名文件。
第 5 阶段:ArkUI 宿主
5.1 注入地址和触发操作
页面从可编辑状态读取两个 URL,HTTP 基地址去除尾部斜杠,开始前检查 http:// 或 https:// 前缀。下面是执行循环中的核心片段:
this.summary = `运行中 ${index + 1} / ${CHECK_NAMES.length}:${CHECK_NAMES[index]}`;
const report: string = await this.runOneWithDeadline(index, baseUrl, sseUrl,
Math.min(BRIDGE_TIMEOUT_MS, remaining));
if (generation !== this.runGeneration) return;
const checks = JSON.parse(report) as CheckResult[];
if (checks.length !== 1) throw new Error('原生检查未返回单项结果');
this.results = [...this.results, checks[0]];
完整函数还负责 try/catch/finally、进度、整轮截止时间和运行标志恢复。UI 不直接调用 ArkTS HTTP 接口,所有验收请求都经过 Kotlin Fuel。
5.2 页面交互
单页由标题、HTTP/SSE 地址、状态行、操作区和结果列表组成。运行期间禁用地址编辑,按钮显示运行状态并提供停止入口。每项结束立即追加名称和详情;全部完成后统计 passed,显示 11 / 11 PASS(通过) 或实际通过数量。
默认回显地址需要提供 /get、/delete、/anything 等兼容路由,SSE 地址则返回 data: 事件流。使用 scripts/local-echo-server.py 时,HTTP 地址填 http://<开发机局域网IP>:8919,SSE 填同一服务的 /stream,设备须能访问该地址。
5.3 对比度和错误边界
通过详情使用绿色,失败详情使用红色,主运行按钮使用蓝色。状态恢复有三层时限:Kotlin 单项 12 秒,ArkTS 单项桥接等待最多 18 秒,整轮最长 60 秒。整轮截止时间是页面看门狗,不能代替底层生命周期正确性。
“停止”递增轮次标识,阻止后续场景启动并忽略旧轮次的迟到结果;它不宣称强制终止已经进入原生层的工作。页面消失时执行同样的停止逻辑。finally 只更新当前轮次,避免旧任务把新一轮运行状态覆盖掉。
第 6 阶段:示例与验证
6.1 工程结构
example/
├── shared/
│ ├── build.gradle.kts JVM + 双 OHOS,依赖真实 :fuel
│ ├── src/commonMain/.../AcceptanceChecks.kt 11 项共享验收场景
│ └── src/jvmTest/.../AcceptanceChecksTest.kt 本地 MockWebServer 回归
├── nativeApp/
│ ├── build.gradle.kts 双 ABI sharedLib + 运行库/头文件复制
│ └── src/ohosMain/
│ ├── kotlin/fuel/sample/NativeChecks.kt C 导出和 JSON 内存管理
│ └── linker/shared-library.map 3 个 C 符号的导出表
└── harmonyApp/
├── build-profile.json5 仓库无签名材料,本机由 DevEco 配置
├── AppScope/app.json5 bundleName: com.example.fuel
└── entry/
├── build-profile.json5 CMake 与双 ABI 配置
├── libs/{arm64-v8a,x86_64}/ Gradle 回填的库和运行时,忽略提交
├── src/main/cpp/CMakeLists.txt IMPORTED Kotlin 库、运行时复制、rpath
├── src/main/cpp/napi_init.cpp 异步工作、参数检查和释放
├── src/main/cpp/include/ 自动生成 libohosfuel_api.h
├── src/main/cpp/types/libfuel_napi/ ArkTS 类型声明
└── src/main/ets/pages/Index.ets ArkUI 验收页面
6.2 构建、签名和安装
在仓库根目录完成 Kotlin 构建与原生库准备:
export JAVA_HOME="/path/to/jdk-21"
./scripts/build-openharmony.sh --console=plain
脚本覆盖本地发布、JVM 测试、OHOS 主代码与测试代码编译,以及 :example:nativeApp:prepareOhos。后者自动生成并复制双 ABI Kotlin 动态库、匹配运行时和 C 头文件。
若已有本机签名配置,直接在 DevEco Studio 打开 example/harmonyApp,重新构建和安装。需要独立工程时可执行:
python3 scripts/prepare-signing-project.py "$HOME/2026UGC/ohos_fuel_signing"
./scripts/build-hap.sh "$HOME/2026UGC/ohos_fuel_signing"
准备脚本保留目标工程已有的 build-profile.json5。签名通过 DevEco Studio 的 File → Project Structure → Signing Configs 手动配置,仓库版本的 signingConfigs 为空,不包含证书、密码或本机材料路径。
以下为手工安装和启动命令示例,不作为本文实际执行过这些命令的日志:
hdc list targets
hdc -t <设备序列号> install -r \
<工程目录>/entry/build/default/outputs/default/entry-default-signed.hap
hdc -t <设备序列号> shell aa start \
-a EntryAbility -b com.example.fuel -m entry
四、完整代码对照
4.1 整体架构
Fuel 公共 API / Request / HttpResponse
│ 平台 actual
OhosHttpLoader → HttpUrlFetcher → NetworkKit C API
│ NetstackBridge 回调、Source / SSE、资源释放
example/shared:AcceptanceChecks
│ 11 项场景和 CheckResult
example/nativeApp:NativeChecks
│ FuelSampleRunCheck / FuelSampleRunChecks / FuelSampleFree
libfuel_napi.so
│ runCheck / runChecks,N-API async work
Index.ets
│ JSON.parse + @State 逐项更新
▼
ArkUI 真机验收页面
4.2 文件清单
| 文件 | 职责 |
|---|---|
settings.gradle.kts | 社区 Maven、示例模块、聚焦构建条件 |
gradle/libs.versions.toml | Kotlin、KSP、serialization 插件与各平台依赖版本 |
fuel/build.gradle.kts | 双 OHOS 目标、OHOS/Apple 兼容依赖 |
fuel/src/ohosMain/kotlin/fuel/FuelBuilder.kt | 平台构建入口 actual |
fuel/src/ohosMain/kotlin/fuel/OhosHttpLoader.kt | HttpLoader 方法映射和请求体限制 |
fuel/src/ohosMain/kotlin/fuel/HttpUrlFetcher.kt | NetworkKit 请求、响应、SSE、取消与指针所有权 |
fuel/src/ohosMain/kotlin/fuel/NetstackBridge.kt | 32 个槽位和静态 C 回调跳板 |
fuel/src/commonMain/kotlin/fuel/SseEventParser.kt | 字节分片、UTF-8 行与多行 data 事件组装 |
example/shared/.../AcceptanceChecks.kt | 11 个场景与 12 秒单项时限 |
example/nativeApp/build.gradle.kts | sharedLib、导出 map、运行库与头文件复制 |
example/nativeApp/.../NativeChecks.kt | 3 个 C 导出、JSON 结果和 nativeHeap 生命周期 |
example/harmonyApp/.../cpp/napi_init.cpp | N-API 异步工作、忙碌保护与结果转换 |
example/harmonyApp/.../ets/pages/Index.ets | 逐项进度、停止、超时和结果显示 |
scripts/check-native-deps.py | 对照 SDK 审计未解析强导入符号 |
README.OpenHarmony.md / README.OpenHarmony_CN.md | 中英文接入与限制说明 |
4.3 关键 API 对照
| 层次 | API | 作用 |
|---|---|---|
| 公共 API | Fuel.get / httpGet / httpHead / httpDelete | 共享请求调用方式 |
| 公共 API | httpMethod | 显式方法,如 OPTIONS |
| 响应 | HttpResponse.statusCode / headers / source | 状态、响应头与内容读取 |
| 流式 | httpSSE / Flow<String> | SSE data 事件消费 |
| 平台 | OH_Http_CreateRequest / OH_Http_Request / OH_Http_Destroy | 创建、发送、取消/销毁原生请求 |
| 回调 | NetstackBridge | 将无上下文 C 回调路由到请求槽位 |
| 共享验收 | runAcceptanceCheck / runAcceptanceChecks | 单项与全套场景 |
| Native | FuelSampleRunCheck / FuelSampleRunChecks / FuelSampleFree | C ABI 入口和释放 |
| N-API | runCheck / runChecks | 向 ArkTS 返回 Promise |
| ArkTS | runAcceptance / stopAcceptance | 页面运行与停止管理 |
4.4 ArkTS 与 Kotlin 的边界
ArkTS 的调用形式为:
const report: string = await runCheck(0, baseUrl, sseUrl);
const checks = JSON.parse(report) as CheckResult[];
对应的 Kotlin 场景实际使用 Fuel 发起网络请求:
val response = "$baseUrl/get".httpGet(parameters = listOf("fuel" to "ohos"))
require(response.statusCode == 200) { "status was ${response.statusCode}" }
val body = response.source.readString()
require(body.contains("fuel") && body.contains("ohos")) { "query was not echoed" }
业务 KMP 工程可以直接使用核心 API,不需要经过示例的 JSON 验收桥。示例层的 runCheck 不是面向业务提供的通用 ArkTS HTTP 客户端。
五、关键决策说明
决策 1:编译真实核心库,新增平台传输引擎
Fuel 的请求 API 与模型可以共享,HTTP 传输则天然具有平台实现。适配编译真实核心库,为鸿蒙补 HttpLoader 实现和 NetworkKit 回调层,使现有 Fuel.get 等 API 可以运行。验收场景依赖 project(":fuel"),能够追溯到实际库代码。
决策 2:工具链固定 Kotlin 2.2.21-1.0.0,Gradle 使用 8.14.3
本次已经验证的编译链是这组版本。OHOS 协程和 IO 使用匹配制品,Apple 额外增加兼容约束,JVM 保留原依赖版本。工具链、Native 元数据和依赖要作为一个矩阵处理,不能直接把其他库文章中的版本套入当前仓库。
决策 3:按 cmp_qrose 分层示例,条件保留上游构建
目录遵循 example/nativeApp 与 example/harmonyApp 的职责划分,另以 example/shared 复用验收逻辑。Fuel 保留已有根构建、发布约定和常规平台模块,使用 openharmonyOnly 收敛鸿蒙验证范围;没有将 JVM 伴随模块物理删除,也没有宣称序列化模块已经具备 OHOS 支持。
决策 4:单项 JSON 结果与 N-API 异步工作
网络验收耗时不固定,一次同步执行完整套件会推迟所有结果的展示。页面改为逐项等待 Promise,每完成一项就显示结果,同时保持全套 C 入口兼容。12/18/60 秒时限、停止标识和进程内忙碌保护共同覆盖网络等待、桥接异常和重复点击。
决策 5:共享场景交叉验证,生命周期问题追到机器码
JVM 使用 MockWebServer 执行共享场景,OHOS 另外编译原生测试;真机页面反馈网络链路的实际结果。曾出现 GET 已返回 200、页面却持续等待的问题,最终通过日志和 ARM64 反汇编定位到 C 指针的引用比较自旋。超时保护负责恢复 UI,原子交换修复负责消除底层阻塞,两项都需要验证。
六、测试与验证
6.1 测试环境
主机验证使用 macOS arm64、JDK 21、Gradle 8.14.3、HarmonyOS Kotlin 2.2.21-1.0.0、DevEco Studio 和 OpenHarmony SDK。示例按 API 20 构建,打包 ARM64 与 x86_64 两种 ABI。
真机签名和运行由用户手动完成。本文采用用户本次提供的 11 / 11 PASS 效果图作为页面验收结果;截图没有完整设备型号和运行时 API 信息,也没有提供签名 HAP 哈希,因此不将它与某个未签名包哈希强行对应。
6.2 JVM 交叉验证
./gradlew -PopenharmonyOnly=true \
:fuel:jvmTest :fuel-kotlinx-serialization:jvmTest :example:shared:jvmTest
已有测试报告中,以下 59 个测试全部通过:
| 测试组 | 用例数 | 验证内容 |
|---|---|---|
fuel:jvmTest | 53 | 请求模型、参数/字符串/URI、加载器、路由、SSE 解析和槽位选择 |
fuel-kotlinx-serialization:jvmTest | 2 | JVM 响应反序列化扩展 |
example:shared:jvmTest | 4 | 完整 11 场景、单项只请求一次、慢请求超时、外部取消传播 |
最后一次仅修改 OHOS 的构建中,上述 JVM 任务为 UP-TO-DATE,沿用已有通过结果;没有将其写成重新执行。常规构建另通过 Forge 1 项、Jackson 2 项、Moshi 4 项,共 7 项伴随模块回归,Apple 序列化测试源码也完成兼容编译。
页面实际控制逻辑经转译后,在 Node 中模拟过全部成功、原生 Promise 不返回、停止后迟到结果、整轮截止时间四类情况。OHOS 专属 9 项测试通过双目标编译,覆盖槽位、异常边界、完成回调、cookie 处理和请求句柄一次转移;本次没有执行设备上的原生单元测试套件。
6.3 HAP 验证
本次记录的构建与审计结果:
:fuel:compileKotlinOhosArm64 / OhosX64: PASS
:fuel:compileTestKotlinOhosArm64 / OhosX64: PASS(仅编译)
:fuel:publishToMavenLocal: PASS
:example:nativeApp:prepareOhos: PASS
build-openharmony.sh: BUILD SUCCESSFUL,71 tasks(35 executed,36 up-to-date)
BuildNativeWithCmake / BuildNativeWithNinja: PASS(双 ABI)
CompileArkTS / PackageHap: PASS
build-hap.sh: BUILD SUCCESSFUL,34 tasks(33 executed,1 up-to-date)
API 20 ELF audit: 两个 ABI 各 3 个 .so,均为 0 unresolved strong imports
对应未签名 HAP 大小为 9,527,099 字节,约 9.09 MiB,包含双 ABI 各三件套。主机构建记录中的 SHA-256 为:
5a14a0d2107fae4711554ed898a3e3de8b86412960f08b2fb3d47e01d7d0032a
签名或重新构建会改变哈希。此值仅标识已审计的未签名包,用户手动运行的签名产物没有提供可核对哈希。ARM64 动态库另完成反汇编检查,确认请求释放使用 AtomicReference.getAndSet(null),旧的装箱/CAS 重试路径已消除。
6.4 功能用例
| 序号 | 场景 | 通过条件 |
|---|---|---|
| 1 | GET 查询参数 | HTTP 200,回显中包含 fuel=ohos 对应内容 |
| 2 | GET 请求头 | HTTP 200,回显中包含 X-Fuel-Sample |
| 3 | HEAD | HTTP 200,响应内容为空 |
| 4 | DELETE | HTTP 200 |
| 5 | OPTIONS 自定义方法 | HTTP 200 |
| 6 | 响应头 | 能读取非空 content-type |
| 7 | 响应 Source | 通过 kotlinx.io 读取到非空文本 |
| 8 | JSON 内容 | 文本以 JSON 对象开始并包含 args 字段,不等同于序列化扩展验收 |
| 9 | SSE | 获取首个非空事件 |
| 10 | 并发 GET | 3 个并行请求均返回 200 |
| 11 | POST body | OHOS 按文档抛出 UnsupportedOperationException;JVM 正常发送并返回 200 |
页面对上述 11 项计算汇总。截图直接可见前四项的成功详情和 11 / 11 PASS(通过) 总结;未展示的列表内容由汇总状态反映,不额外声称已逐条取得完整滚动截图。重复运行、停止、前后台切换和所有不可达网络组合仍可按项目说明继续补充验证。
6.5 验证结论
真实 Fuel 核心、鸿蒙 NetworkKit 引擎、Native 动态库、C ABI、N-API、ArkUI 页面和 HAP 已完成构建链路验证。用户提供的设备效果图显示 11 项场景全部符合预期,证明本轮页面验收已经结束并给出通过结果。
结论限定在当前验收集合:核心 GET/HEAD/DELETE/OPTIONS、响应读取、SSE 首事件和并发调用,以及请求体明确拒绝的行为。x86_64 本次完成编译、链接和依赖审计;不能据 ARM64 设备效果图宣称 x86_64 环境已运行通过,也不能据此宣称所有 HTTP 功能均完整支持。
七、运行效果
7.1 真机截图

7.2 命令速查
git clone https://atomgit.com/oh-tpc/fuel.git
cd fuel
export JAVA_HOME="/path/to/jdk-21"
./scripts/build-openharmony.sh --console=plain
# 直接使用原 example/harmonyApp 时,在 DevEco 中配置签名并重新构建安装。
# 需要独立工程时,准备脚本保留目标已有的签名配置。
python3 scripts/prepare-signing-project.py "$HOME/2026UGC/ohos_fuel_signing"
./scripts/build-hap.sh "$HOME/2026UGC/ohos_fuel_signing"
# 可选:局域网回显服务;SSE 路由为 /stream。
python3 scripts/local-echo-server.py
# 以下为签名完成后的手动设备操作示例。
hdc -t <设备序列号> install -r <工程目录>/entry/build/default/outputs/default/entry-default-signed.hap
hdc -t <设备序列号> shell aa start -a EntryAbility -b com.example.fuel -m entry
hdc -t <设备序列号> shell hilog -x -T FuelEngine
八、遗留问题与改进方向
8.1 踩坑复盘
-
只改 ArkTS 页面不能证明 Fuel 已适配,需要真正编译核心 OHOS KLIB,并让验收请求经过它;
-
Kotlin 插件仓库和项目依赖仓库都要可解析社区制品,不能只加入其中一处;
-
协程、IO 和 Native 编译器要对齐元数据,JVM、Apple、OHOS 的约束不能机械统一;
-
普通 HTTP 以响应回调完成,不再等待额外的
onDataEnd;普通请求不注册该终止事件,减少迟到事件误路由; -
C 回调不带用户数据指针,需要固定槽位跳板;同时进行的原生请求超过 32 个必须明确失败;
-
GET 返回 200 后仍会卡在清理:
CPointer经过拆箱再装箱后,引用身份比较的 CAS 循环可能永远失败。修复为一次原子交换:internal fun takeNativeRequest( reference: AtomicReference<CPointer<Http_Request>?>, ): CPointer<Http_Request>? = reference.getAndSet(null) -
日志只到
cleanup slot=0时,需要继续检查是否真正进入系统销毁函数;本次 ARM64 反汇编确认旧循环发生在 Kotlin 层,系统回调路径核对未发现所猜测的锁重入; -
较早的系统库检查发现 API 24 cookie 指针借用与释放不一致,兼容分支在 API 20–24 响应销毁前清空 cookie 指针;API 26 保留系统正常销毁流程,这与上述 CAS 自旋是不同问题;
-
页面超时只能恢复显示,不能证明 Native 工作已结束。停止后通过轮次标识忽略迟到结果,并由 C++ 忙碌保护阻止新工作排队;
-
N-API 必须先复制返回 JSON,再调用
FuelSampleFree;生成头文件和动态库必须来自同一套构建; -
每个 ABI 的三件套和匹配 C++ 运行时缺一不可,编译通过后还要审计最终 HAP 的 ELF 依赖;
-
本机签名配置、证书和密码不能提交;修复原生库后要停止旧应用并重新构建安装,仅重新点击旧页面不会加载新库。
8.2 已知问题
- 当前使用的 NetworkKit HTTP C API 没有请求体设置接口,POST/PUT/PATCH 及携带 body 的自定义方法明确拒绝;
- 本次 OHOS 产物不包含
fuel-kotlinx-serialization扩展,页面 JSON 检查只验证响应文本结构; - 原生并发上限为 32;SSE 解析输出 data 内容,不宣称实现了完整 EventSource 自动重连、事件 ID 恢复等机制;
FuelBuilder没有额外的鸿蒙专属配置项,JVM 和 Apple 的平台配置方式继续保留;- 默认外部回显服务和 SSE 服务受网络可达性影响,应允许替换为设备可达的兼容端点;
- “停止”不强制打断已进入桥接层的工作,原生未结束时立即重试可能收到忙碌错误;
- cookie 兼容判断使用 API 区间,静态确认过 API 24 与 API 26,其他系统补丁版本仍需扩展验证;
- x86_64、不同设备版本、长时间 SSE 和高并发压力尚需补齐运行验收矩阵;
- 签名产物必须与
com.example.fuel和设备配置匹配,签名信息由本机 DevEco 管理。
8.3 未来优化方向
- 评估具有请求体能力的传输路线,补齐 POST/PUT/PATCH,并同步修改拒绝行为测试和文档;
- 完善 OHOS 序列化扩展的依赖适配及独立发布;
- 增加完整 SSE 行为、取消竞态、连续运行和长时间并发压力测试;
- 为 x86_64 环境与更多系统版本补充真机或模拟器记录;
- 在 CI 中加入 OHOS 主代码/测试编译、导出符号检查、最终 HAP 依赖审计;
- 评估可复用 ArkTS API 或 HAR 交付,明确其与当前验收专用桥的职责差异。
九、总结
9.1 核心难点回顾
Fuel 公共请求 API 与模型
→ 对齐 Kotlin 2.2.21-1.0.0、Gradle 8.14.3 和 OHOS kotlinx 制品
→ ohosArm64 / ohosX64 KLIB + NetworkKit 平台引擎
→ 固定槽位回调、Source 读取、SSE 和原子请求所有权
→ example/shared 复用 11 项场景
→ C ABI(2 个运行入口 + 1 个释放入口)
→ C++ N-API 异步工作与忙碌保护
→ ArkTS 逐项结果、停止与超时
→ 双 ABI HAP、依赖审计和用户真机 11 / 11 PASS 效果图
9.2 封装层次
- 公共层:Fuel 请求 API、模型、参数与响应类型;
- 平台层:
OhosHttpLoader、NetworkKit HTTP、回调槽位、SSE 和资源释放; - 共享验收层:相同场景在 JVM 和 OHOS 调用同一套 Fuel 接口;
- Native 层:生成双 ABI 动态库,通过 C ABI 输出 JSON 并明确释放协议;
- N-API 层:参数检查、异步工作、忙碌保护、结果转换;
- ArkTS 层:地址输入、进度、结果、停止、超时和轮次隔离;
- DevEco 层:完成 CMake、HAP、签名和用户手动设备运行。
9.3 三条经验
- 平台适配要保留公共接口,同时接受底层能力边界。Fuel 的 HTTP API 可以继续复用,但请求体上传必须明确说明限制,预期拒绝不能写成上传成功;
- 生命周期问题要结合日志、源码和产物定位。HTTP 200、UI 退出等待、请求真正释放是不同检查点,原子交换修复消除了仅靠看门狗无法解决的自旋;
- 验证结论应分层记录。JVM 测试、OHOS 测试编译、HAP 审计和用户设备截图分别回答不同问题,组合起来才能清楚表达交付范围。
9.4 适配成果
当前适配已完成:
- Fuel
3.0.0-alpha04-ohos.1核心库双 OHOS 目标编译,以及 NetworkKit 平台实现; example/shared、example/nativeApp、example/harmonyApp的分层验收工程;- 3 个 C 导出、N-API 异步桥接、双 ABI 动态库和匹配运行时准备;
- 59 个核心/序列化/共享 JVM 测试通过记录、7 个伴随模块测试通过记录、9 个 OHOS 专属测试双目标编译;
- 未签名 HAP 构建、六个动态库 API 20 依赖审计和请求清理机器码复核;
- 用户手动设备验收效果图显示
11 / 11 PASS,请求体限制按预期拒绝验证; - AtomGit 仓库、中英文 OpenHarmony README、本篇适配文章和
doc/img本地效果图。
适配过程 FAQ
以下问题来自
ohosArm64目标接入、NAPI 桥接、HAP 构建与真机验收的实际流程,按出现频率排序。
Q1:DevEco Studio Sync 失败或 ohpm 依赖下载超时,怎么处理?
- 确认 DevEco Studio 版本与工程
build-profile.json5的compatibleSdkVersion一致; - 在
File > Settings > Build, Execution, Deployment > Ohpm配置可用的 ohpm 仓库镜像或代理; - 删除
oh_modules、oh-package-lock.json5与.hvigor缓存后重新 Sync; - 公司网络需要代理时,同时给 ohpm 与 hvigor 配置
http.proxy。
Q2:CMake 报 Missing .../libxxx.so 或找不到 Kotlin/Native 动态库?
- 先在
example目录执行./gradlew :nativeApp:linkDebugSharedOhosArm64(模拟器用linkDebugSharedOhosX64); - 确认
libxxx.so与libc++_shared.so已复制到ohos/entry/libs/<abi>/; - 检查
entry/build-profile.json5的abiFilters与设备 ABI 是否一致(真机arm64-v8a,模拟器x86_64); - 清理
entry/.cxx与entry/build后重试。
Q3:ArkTS 报 Cannot find module 'libxxx_napi.so' 或调用返回 undefined?
- 确认 CMake 的
add_library(<target> SHARED ...)名称与import ... from 'libxxx_napi.so'完全一致; - 解包 HAP,确认
libs/<abi>/libxxx_napi.so已打包; - 删除
entry/build、entry/.cxx与oh_modules后重新构建; - 检查
module.json5的srcEntry与页面路径是否正确。
Q4:真机安装报 signature verification failed 或 install failed?
- 在 DevEco Studio 的
Signing Configs配置证书、Profile 与密码; bundleName必须与签名 profile 中的 bundle name 一致;- 设备需加入 profile 的
deviceIds,调试证书有效期需覆盖当前时间; - 用
hdc list targets确认设备在线,再执行hdc install -r xxx-signed.hap。
Q5:应用启动白屏、闪退或 NAPI 调用崩溃?
- 用
hdc hilog | grep -i "napi\|kotlin"查看原生日志; - 确认
libc++_shared.so与 Kotlin/Native 工具链版本匹配,且 ABI 与设备一致; - 检查 C ABI 返回的字符串是否由 Native 侧分配、ArkTS 侧复制后是否调用
XxxFree释放; - 在 DevEco 的 Native 调试配置中附加
lldb,定位空指针与越界访问。
Q6:Gradle 报 ohosArm64 目标不存在或 Kotlin/Native 编译失败?
- 使用鸿蒙发行版 Kotlin(如
2.2.21-1.0.0),并在settings.gradle.kts配置 eazytec 仓库; - Gradle 建议 8.x、JDK 21,版本不匹配时先统一工具链;
- 确认
kotlin { ohosArm64() }已应用,且local.properties的 SDK 路径有效; - 清理
~/.gradle/caches中对应版本的 Kotlin/Native 缓存后重试。
Q7:HAP 构建报 SDK 版本不匹配或 compatibleSdkVersion 错误?
- 对齐 DevEco Studio、HarmonyOS SDK 与
build-profile.json5中的compatibleSdkVersion; - 升级 DevEco Studio 后同步升级 hvigor 与 SDK,避免
modelVersion与 IDE 不匹配; - 命令行构建时设置
DEVECO_SDK_HOME指向 DevEco 的sdk目录。
Q8:命令行 hvigor 构建失败,但 DevEco 里能构建?
- 使用 DevEco 自带的 node 与 hvigor:
"$DEVECO/Contents/tools/node/bin/node" "$DEVECO/Contents/tools/hvigor/bin/hvigorw.js" ...; - 设置
JAVA_HOME、NODE_HOME、DEVECO_SDK_HOME; - 加上
--no-daemon排除守护进程缓存影响,并清理entry/build与.hvigor。
参考文档
更多推荐


所有评论(0)