本文记录 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 / NativeHarmonyOS Kotlin 2.2.21-1.0.0
JDK21,通过 JAVA_HOME 指定
OpenHarmony 目标ohosArm64 + ohosX64,保留 JVM 作交叉验证
Gradle8.14.3
OHOS kotlinxcoroutines 1.10.2-1.0.0、IO 0.9.0-1.0.0
JVM kotlinxcoroutines 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 SDK6.0.0(20) / API 20
ABIarm64-v8a + x86_64
Bundlecom.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/jvmMainOkHttp保留 JVM 引擎与本地服务测试
fuel/appleMainApple 系统网络能力常规构建保留,增加兼容依赖约束
fuel/ohosMainNetworkKit C API、原生回调与内存新增平台引擎及生命周期管理
fuel-kotlinx-serializationserialization-json-io、Result 等保留 JVM/Apple,本次不声明 OHOS 交付
JVM 伴随模块Forge、Jackson、Moshi常规构建参与,鸿蒙聚焦模式不配置
example/sharedproject(":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 方法作用
FuelSampleRunCheckrunCheck(index, baseUrl, sseUrl)单项验收,页面当前入口
FuelSampleRunChecksrunChecks(baseUrl, sseUrl)完整套件,保留的兼容入口
FuelSampleFreeC++ 内部调用释放由 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.tomlKotlin、KSP、serialization 插件与各平台依赖版本
fuel/build.gradle.kts双 OHOS 目标、OHOS/Apple 兼容依赖
fuel/src/ohosMain/kotlin/fuel/FuelBuilder.kt平台构建入口 actual
fuel/src/ohosMain/kotlin/fuel/OhosHttpLoader.ktHttpLoader 方法映射和请求体限制
fuel/src/ohosMain/kotlin/fuel/HttpUrlFetcher.ktNetworkKit 请求、响应、SSE、取消与指针所有权
fuel/src/ohosMain/kotlin/fuel/NetstackBridge.kt32 个槽位和静态 C 回调跳板
fuel/src/commonMain/kotlin/fuel/SseEventParser.kt字节分片、UTF-8 行与多行 data 事件组装
example/shared/.../AcceptanceChecks.kt11 个场景与 12 秒单项时限
example/nativeApp/build.gradle.ktssharedLib、导出 map、运行库与头文件复制
example/nativeApp/.../NativeChecks.kt3 个 C 导出、JSON 结果和 nativeHeap 生命周期
example/harmonyApp/.../cpp/napi_init.cppN-API 异步工作、忙碌保护与结果转换
example/harmonyApp/.../ets/pages/Index.ets逐项进度、停止、超时和结果显示
scripts/check-native-deps.py对照 SDK 审计未解析强导入符号
README.OpenHarmony.md / README.OpenHarmony_CN.md中英文接入与限制说明

4.3 关键 API 对照

层次API作用
公共 APIFuel.get / httpGet / httpHead / httpDelete共享请求调用方式
公共 APIhttpMethod显式方法,如 OPTIONS
响应HttpResponse.statusCode / headers / source状态、响应头与内容读取
流式httpSSE / Flow<String>SSE data 事件消费
平台OH_Http_CreateRequest / OH_Http_Request / OH_Http_Destroy创建、发送、取消/销毁原生请求
回调NetstackBridge将无上下文 C 回调路由到请求槽位
共享验收runAcceptanceCheck / runAcceptanceChecks单项与全套场景
NativeFuelSampleRunCheck / FuelSampleRunChecks / FuelSampleFreeC ABI 入口和释放
N-APIrunCheck / runChecks向 ArkTS 返回 Promise
ArkTSrunAcceptance / 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:jvmTest53请求模型、参数/字符串/URI、加载器、路由、SSE 解析和槽位选择
fuel-kotlinx-serialization:jvmTest2JVM 响应反序列化扩展
example:shared:jvmTest4完整 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 功能用例

序号场景通过条件
1GET 查询参数HTTP 200,回显中包含 fuel=ohos 对应内容
2GET 请求头HTTP 200,回显中包含 X-Fuel-Sample
3HEADHTTP 200,响应内容为空
4DELETEHTTP 200
5OPTIONS 自定义方法HTTP 200
6响应头能读取非空 content-type
7响应 Source通过 kotlinx.io 读取到非空文本
8JSON 内容文本以 JSON 对象开始并包含 args 字段,不等同于序列化扩展验收
9SSE获取首个非空事件
10并发 GET3 个并行请求均返回 200
11POST bodyOHOS 按文档抛出 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 踩坑复盘

  1. 只改 ArkTS 页面不能证明 Fuel 已适配,需要真正编译核心 OHOS KLIB,并让验收请求经过它;

  2. Kotlin 插件仓库和项目依赖仓库都要可解析社区制品,不能只加入其中一处;

  3. 协程、IO 和 Native 编译器要对齐元数据,JVM、Apple、OHOS 的约束不能机械统一;

  4. 普通 HTTP 以响应回调完成,不再等待额外的 onDataEnd;普通请求不注册该终止事件,减少迟到事件误路由;

  5. C 回调不带用户数据指针,需要固定槽位跳板;同时进行的原生请求超过 32 个必须明确失败;

  6. GET 返回 200 后仍会卡在清理:CPointer 经过拆箱再装箱后,引用身份比较的 CAS 循环可能永远失败。修复为一次原子交换:

    internal fun takeNativeRequest(
        reference: AtomicReference<CPointer<Http_Request>?>,
    ): CPointer<Http_Request>? = reference.getAndSet(null)
    
  7. 日志只到 cleanup slot=0 时,需要继续检查是否真正进入系统销毁函数;本次 ARM64 反汇编确认旧循环发生在 Kotlin 层,系统回调路径核对未发现所猜测的锁重入;

  8. 较早的系统库检查发现 API 24 cookie 指针借用与释放不一致,兼容分支在 API 20–24 响应销毁前清空 cookie 指针;API 26 保留系统正常销毁流程,这与上述 CAS 自旋是不同问题;

  9. 页面超时只能恢复显示,不能证明 Native 工作已结束。停止后通过轮次标识忽略迟到结果,并由 C++ 忙碌保护阻止新工作排队;

  10. N-API 必须先复制返回 JSON,再调用 FuelSampleFree;生成头文件和动态库必须来自同一套构建;

  11. 每个 ABI 的三件套和匹配 C++ 运行时缺一不可,编译通过后还要审计最终 HAP 的 ELF 依赖;

  12. 本机签名配置、证书和密码不能提交;修复原生库后要停止旧应用并重新构建安装,仅重新点击旧页面不会加载新库。

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 三条经验

  1. 平台适配要保留公共接口,同时接受底层能力边界。Fuel 的 HTTP API 可以继续复用,但请求体上传必须明确说明限制,预期拒绝不能写成上传成功;
  2. 生命周期问题要结合日志、源码和产物定位。HTTP 200、UI 退出等待、请求真正释放是不同检查点,原子交换修复消除了仅靠看门狗无法解决的自旋;
  3. 验证结论应分层记录。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。

参考文档

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐