开源鸿蒙平台 KMP_CMP 三方库「URI KMP」适配全流程
本文记录
uri-kmp接入 OpenHarmony 的完整过程,覆盖 KMP 工程盘点、ohosArm64目标、Kotlin/Native 动态库、C ABI/N-API 桥接、ArkUI 交互页面、无签名 HAP 构建、依赖检查和签名交接。本次适配复用 URI KMP 的解析、编码、解码、Builder 和 URI 分类能力,再由 ArkTS 调用 Kotlin/Native 生成的
liburi.so。示例页面不是固定数据验证页,而是一个可以输入 URI、选择操作、查看结构化结果和回放最近操作的 URI 交互工作台。
项目地址: AtomGit/oh-tpc/uri-kmp
上游仓库: eygraber/uri-kmp
开发工具: 华为云码道
环境安装: 环境安装指南
一、背景
1.1 为什么做开源鸿蒙平台 KMP/CMP 适配
uri-kmp 是一个 Kotlin Multiplatform URI 和 URL 库,公共实现位于 commonMain,提供 URI 解析、编码、解码、Builder、查询参数和 URI 类型判断。原工程可以在 JVM、JS、Wasm 和其他 Kotlin 平台使用,但 OpenHarmony 需要一条独立的 ARM64 Native 构建和 ArkTS 消费链路。
如果只把示例页面重新写成 ArkTS,页面可以显示几个固定 URI,却无法证明公共 Kotlin 代码已经运行在 OpenHarmony ARM64 目标上。适配需要解决下面几个问题:
| 障碍 | 具体问题 |
|---|---|
| 目标缺失 | KMP 模块默认没有 OpenHarmony 目标,必须增加 ohosArm64() 才能生成 KLIB。 |
| 工具链不一致 | Kotlin/Native、OpenHarmony LLVM、Native SDK、Gradle 和 JDK 需要保持兼容。 |
| 语言边界不同 | ArkTS 不能直接持有 Kotlin Uri 对象,必须经过 C ABI、C++ N-API 和 JSON。 |
| 内存所有权 | Kotlin/Native 返回的字符串需要由明确的 UriFree 接口释放。 |
| API 体验 | 示例不能只调用 runChecks(),需要让用户实际输入 URI 并查看库的能力。 |
| 交付链路复杂 | Native 动态库、CMake、HAP、签名和 ARM64 依赖需要分别验证。 |
因此,本项目把适配边界放在三个地方:Kotlin/Native 目标配置、C ABI/N-API 桥接层和 ArkUI 交互层。URI 语义仍由 KMP 维护,ArkTS 只负责输入、操作选择、页面状态和错误展示。
1.2 库提供的能力
uri-kmp 公共模块提供以下能力:
Uri.parse:解析绝对 URI、相对 URI、hierarchical URI 和 opaque URI;Uri.scheme、authority、host、port:读取 URI 的 authority 结构;Uri.path、query、fragment:读取解码后的路径、查询和片段;Uri.isHierarchical、isOpaque、isRelative:判断 URI 类型;Uri.encode、Uri.decode:完成百分号编码和解码;Uri.Builder:构建和追加路径、查询参数;getQueryParameter、getQueryParameterNames:读取查询参数;Uri.parseOrNull:将无法解析的输入转换为null;UriExamples.runChecks:在 JVM、Kotlin/Native 和 ArkTS 示例中复用同一组检查。
示例页面用三个真实场景展示这些能力:
| 场景 | 示例 | 主要能力 |
|---|---|---|
| Web 链接 | https://example.com/path?q=hello world#section | scheme、host、path、query、fragment |
| Content URI | content://media/external/audio/media/1 | 层级 URI 和路径解析 |
| Mailto URI | mailto:nobody@example.com | opaque URI 和 scheme-specific part |
1.3 实现适配
| 维度 | 要求 |
|---|---|
| 代码复用 | URI 解析、编码、解码、Builder 和公共检查继续由 Kotlin 共享。 |
| 平台目标 | 为 uri 和示例 Native 模块加入 ohosArm64,生成 liburi.so。 |
| 桥接稳定 | 使用少量 C ABI 函数和 JSON,避免把 Kotlin 对象地址交给 ArkTS。 |
| UI 完整 | 页面支持输入、模式切换、示例填充、结果查看、错误提示和最近操作。 |
| 可测试 | JVM 测试、Native 链接、ELF 依赖、C++ 编译和 HAP 打包分别验收。 |
| 签名安全 | 源码只提供签名工程准备入口,证书、profile 和密码留在开发者本机。 |
| 仓库规范 | 项目地址、文章链接和源码协作地址统一使用 AtomGit。 |
本项目的 ArkUI 页面是独立的 URI 功能验收宿主,公共 API 仍然保持平台无关。其他 KMP/CMP 应用可以复用
Uri,再自行决定如何呈现 URI 解析结果。
二、实现路线图
第 1 阶段:项目初始化 ── 盘点 URI API、KMP 模块和 OpenHarmony 示例边界
第 2 阶段:目标与依赖打通 ── 加入 ohosArm64、独立 example 工程和 Native 构建任务
第 3 阶段:URI 与序列化 ── 建立示例模型、JSON 字段和统一错误响应
第 4 阶段:原生桥接 ── Kotlin/Native C ABI、C++ N-API、内存释放和类型检查
第 5 阶段:交互页面 ── ArkUI 输入、解析、编码解码、结果卡片和操作历史
第 6 阶段:构建与验证 ── Native 依赖、Hvigor HAP、签名交接和效果图
每个阶段都使用真实产物作为下一阶段输入:先用 JVM 验证 URI 公共模型,再把相同代码链接为 ARM64 动态库,随后由 CMake 和 N-API 加载到 Stage 工程,最后由 ArkUI 页面调用解析、编码和解码接口。
三、逐步实现过程
第 1 阶段:项目初始化
1.1 盘点公共 API 和工程边界
项目采用与参考 KMP/CMP 工程一致的分层:
uri/ URI KMP 核心库
example/shared/ 示例门面、固定样例和 JVM 验收测试
example/nativeApp/ ohosArm64 Kotlin/Native 动态库
example/ohosApp/ DevEco Stage 工程和 ArkUI 页面
scripts/ Native、HAP 和签名工程辅助脚本
docs/openharmony/ 构建、验收和文章素材
example 是独立 Gradle 工程,不把 DevEco 工程当作 Kotlin 子模块。这样可以分别执行 Gradle 和 Hvigor,也可以把 ohosApp 复制到其他目录后在 DevEco Studio 中配置签名。
1.2 固定工具链和版本矩阵
| 项目 | 配置 | 用途 |
|---|---|---|
| Kotlin Multiplatform | 2.2.21-1.0.0 | JVM、Kotlin/Native 和 KLIB |
| JDK | 21 | Gradle、Kotlin 编译和 Native 任务 |
| OpenHarmony 目标 | ohosArm64 | ARM64 Native 动态库 |
| DevEco product | 6.0.0(20) | Stage 工程兼容和目标版本 |
| DevEco Studio | 6.0.0 Release | IDE 版本,负责工程同步、HAP 构建与签名 |
| ABI | arm64-v8a | HAP 原生库架构 |
执行 Gradle 脚本前先选择 JDK 21:
export JAVA_HOME="/path/to/jdk-21"
java -version
1.3 创建 OpenHarmony 示例页面
示例页面围绕“输入、操作、结果和历史”组织,而不是把 runChecks() 放在页面中心:
标题区 URI KMP 三方库 / OpenHarmony 交互示例
输入区 URI 或普通文本输入框
操作区 解析 URI / 编码 / 解码
示例区 Web 链接 / Content URI / Mailto
结果区 URI 字段卡片或编码解码结果
历史区 最近操作和再次使用
第 2 阶段:目标与依赖打通
2.1 加入 ohosArm64 目标
plugins {
kotlin("multiplatform")
}
kotlin {
explicitApi()
jvm()
ohosArm64()
}
OpenHarmony 的 Native 目标会直接编译 commonMain,因此 URI 解析器、编码器和 Builder 不需要再写一份 ArkTS 或 C++ 实现。
2.2 Native focused build 的作用
example/nativeApp 构建一个受 linker map 约束的 shared library:
kotlin {
ohosArm64 {
binaries.sharedLib {
baseName = "uri"
linkerOpts(
"--entry=0",
"--version-script=${project.file("src/ohosArm64Main/linker/shared-library.map")}",
)
linkerOpts("-lace_napi.z", "-luv", "-lhilog_ndk.z")
}
}
}
链接器只导出示例需要的符号:
UriCatalog
UriGet
UriRunChecks
UriParse
UriEncode
UriDecode
UriFree
2.3 配置独立消费工程
example/shared 通过项目依赖消费 URI 库:
kotlin {
sourceSets {
commonMain.dependencies {
api("com.eygraber:uri-kmp:${property("uriVersion")}")
}
}
}
example/nativeApp 再依赖 shared,从而保证 Native 示例和 JVM 示例使用同一份 URI 门面。
2.4 通过构建产物消费共享库
prepareOhos 在 Native 链接成功后复制动态库和 C 头文件:
val prepareOhos by tasks.registering(Copy::class) {
dependsOn("linkDebugSharedOhosArm64")
from(layout.buildDirectory.dir("bin/ohosArm64/debugShared")) {
include("liburi.so")
into("libs/arm64-v8a")
}
from(layout.buildDirectory.dir("bin/ohosArm64/debugShared")) {
include("liburi_api.h")
into("src/main/cpp/include")
}
into(rootProject.layout.projectDirectory.dir("../example/ohosApp/entry"))
}
生成文件为:
example/ohosApp/entry/libs/arm64-v8a/liburi.so
example/ohosApp/entry/src/main/cpp/include/liburi_api.h
第 3 阶段:URI 与序列化
3.1 为什么需要统一 JSON 契约
ArkTS、C++ 和 Kotlin/Native 不能直接共享 Kotlin Uri 对象,因此边界使用 UTF-8 JSON:
ArkTS input / operation
↓ string
N-API parseUri / encodeUri / decodeUri
↓ C ABI pointer
Kotlin Uri.parse / Uri.encode / Uri.decode
↓ JSON string
ArkUI result card
页面只消费 JSON,不复制 URI 的解析规则。这样 JVM 测试和 OpenHarmony 页面使用同一份 Uri 实现。
3.2 解析结果模型
{
"value": "https://example.com/path?q=hello%20world#section",
"scheme": "https",
"authority": "example.com",
"host": "example.com",
"port": -1,
"path": "/path",
"query": "q=hello world",
"fragment": "section",
"schemeSpecificPart": "//example.com/path?q=hello world",
"hierarchical": true,
"opaque": false,
"relative": false
}
对于 mailto:nobody@example.com,opaque 为 true,path 和 host 没有层级 URI 的含义;这正是页面区分 HIERARCHICAL 和 OPAQUE 的依据。
3.3 Kotlin/Native JSON 边界
private fun Uri.toJson(): String = buildString {
append("{\"value\":${quote(toString())}")
append(",\"scheme\":${scheme.toJsonValue()}")
append(",\"authority\":${authority.toJsonValue()}")
append(",\"host\":${host.toJsonValue()}")
append(",\"port\":$port")
append(",\"path\":${path.toJsonValue()}")
append(",\"query\":${query.toJsonValue()}")
append(",\"fragment\":${fragment.toJsonValue()}")
append(",\"hierarchical\":$isHierarchical")
append(",\"opaque\":$isOpaque")
append(",\"relative\":$isRelative}")
}
Native 异常统一转换成 {"error":"URI input is required"},ArkTS 客户端在解析 JSON 后检查 error 字段,再将它显示在页面状态区。
第 4 阶段:原生桥接(技术难点)
4.1 Kotlin/Native 对象不能直接交给 ArkTS
Kotlin/Native 的 Uri 属于 Kotlin 运行时对象。ArkTS 只能接收 JavaScript 值,C++ 也不能把 Kotlin 对象地址直接当成 JavaScript 对象。
最终采用三层桥接:
ArkTS
│ JSON string
▼
C++ N-API entry
│ void* / UTF-8 input
▼
Kotlin/Native C ABI
│ Uri.parse / UriCodec
▼
UTF-8 JSON + explicit free
4.2 方案对比
| 方案 | 优点 | 缺点 | 选用 |
|---|---|---|---|
| 直接导出 Kotlin 对象 | 代码少 | ABI、生命周期和类型不可控 | ❌ |
| 只导出字符串结果 | 实现简单 | 页面无法展示 URI 结构 | ❌ |
| C ABI + JSON | 边界清晰、易调试、易扩展 | 有一次序列化开销 | ✅ |
| 在 ArkTS 重写 URI 解析 | 页面调用简单 | KMP 和 ArkTS 逻辑容易分叉 | ❌ |
4.3 Kotlin/Native 导出函数
解析、编码和解码分别复用公共 Uri API:
@CName("UriParse")
public fun parseNative(input: CPointer<ByteVar>?): CPointer<ByteVar> = response {
requireNotNull(input) { "URI input is required" }
Uri.parse(input.toKString()).toJson()
}
@CName("UriEncode")
public fun encodeNative(input: CPointer<ByteVar>?): CPointer<ByteVar> = response {
requireNotNull(input) { "Text input is required" }
val value = input.toKString()
"{\"input\":${quote(value)},\"result\":${quote(Uri.encode(value))}}"
}
@CName("UriDecode")
public fun decodeNative(input: CPointer<ByteVar>?): CPointer<ByteVar> = response {
requireNotNull(input) { "Encoded input is required" }
val value = input.toKString()
"{\"input\":${quote(value)},\"result\":${quote(Uri.decode(value, convertPlus = true, throwOnFailure = true))}}"
}
@CName("UriFree")
public fun freeNative(pointer: CPointer<ByteVar>?) {
if (pointer != null) nativeHeap.free(pointer.rawValue)
}
4.4 C++ N-API 方法分发
C++ 注册三个面向用户的 ArkTS 方法,同时保留目录和公共检查方法:
napi_property_descriptor methods[] = {
{"getCatalog", nullptr, Catalog, nullptr, nullptr, nullptr,
napi_default, nullptr},
{"getUri", nullptr, GetUri, nullptr, nullptr, nullptr,
napi_default, nullptr},
{"runChecks", nullptr, RunChecks, nullptr, nullptr, nullptr,
napi_default, nullptr},
{"parseUri", nullptr, ParseUri, nullptr, nullptr, nullptr,
napi_default, nullptr},
{"encodeUri", nullptr, EncodeUri, nullptr, nullptr, nullptr,
napi_default, nullptr},
{"decodeUri", nullptr, DecodeUri, nullptr, nullptr, nullptr,
napi_default, nullptr},
};
输入参数经过 napi_get_value_string_utf8 读取,Native 返回文本被复制成 ArkTS 字符串后立即释放:
ArkTS parseUri(input)
│
▼
napi_get_value_string_utf8
│
▼
UriParse / UriEncode / UriDecode
│
▼
napi_create_string_utf8
│
▼
UriFree(nativeBuffer)
4.5 CMake 引入 Kotlin/Native 动态库
add_library(uri SHARED IMPORTED)
set_target_properties(uri PROPERTIES
IMPORTED_LOCATION
"${CMAKE_CURRENT_SOURCE_DIR}/../../../libs/arm64-v8a/liburi.so")
add_library(entry SHARED napi_init.cpp)
target_include_directories(entry PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/include")
target_link_libraries(entry PRIVATE uri libace_napi.z.so)
第 5 阶段:交互页面
5.1 ArkTS 客户端接口
UriClient.ets 对 Native JSON 做轻量类型封装:
export function parseUri(input: string): UriDetails {
return parse<UriDetails>(uriNative.parseUri(input));
}
export function encodeUri(input: string): UriTransform {
return parse<UriTransform>(uriNative.encodeUri(input));
}
export function decodeUri(input: string): UriTransform {
return parse<UriTransform>(uriNative.decodeUri(input));
}
页面不在 ArkTS 重新实现 URI 解析规则,只负责把返回字段映射到结果卡片。
5.2 输入和模式切换
页面提供输入框和三个模式按钮:
TextInput({ text: this.input, placeholder: '输入 URI 或普通文本' })
.onChange((value: string) => this.input = value)
Button('解析 URI').onClick(() => this.mode = 'parse')
Button('编码').onClick(() => this.mode = 'encode')
Button('解码').onClick(() => this.mode = 'decode')
主按钮根据当前模式执行 parseUri、encodeUri 或 decodeUri。输入为空时,页面直接显示提示,不调用 Native。
5.3 结果卡片和历史记录
解析模式显示 scheme、authority、host/port、path、query、fragment 和 scheme-specific part;编码和解码模式显示转换结果。
每次成功操作都会追加一条最近记录:
const record: OperationRecord = {
id: this.nextRecordId,
mode: this.modeTitle(),
input: input,
output: output,
};
this.history = [record, ...this.history].slice(0, 5);
点击历史项会把输入和模式重新填入页面,再次走完整 Native 链路。
5.4 页面效果图
页面标题、输入区、三种操作模式、快速示例、状态提示和解析结果都集中在一条可滚动工作流中:

第 6 阶段:构建与验证
6.1 example 工程结构
example/
├── shared/
│ ├── src/commonMain/.../UriExamples.kt
│ └── src/commonTest/.../UriExamplesTest.kt
├── nativeApp/
│ ├── src/ohosArm64Main/.../NativeBridge.kt
│ └── src/ohosArm64Main/linker/shared-library.map
└── ohosApp/
├── AppScope/
├── entry/src/main/cpp/
│ ├── CMakeLists.txt
│ └── napi_init.cpp
└── entry/src/main/ets/
├── pages/Index.ets
└── uri/UriClient.ets
6.2 Native 动态库准备
export JAVA_HOME="/path/to/jdk-21"
cd example
./gradlew :shared:jvmTest :nativeApp:prepareOhos
成功后生成:
example/ohosApp/entry/libs/arm64-v8a/liburi.so
example/ohosApp/entry/src/main/cpp/include/liburi_api.h
6.3 构建无签名 HAP
cd example/ohosApp
/Applications/DevEco-Studio.app/Contents/tools/hvigor/bin/hvigorw \
--mode module \
-p module=entry@default \
-p product=default \
-p buildMode=debug \
clean assembleHap --no-daemon
输出位于:
example/ohosApp/entry/build/default/outputs/default/entry-default-unsigned.hap
无签名 HAP 只用于构建和打包检查,真机安装需要签名 HAP。
6.4 Native 依赖检查
python3 scripts/check-native-deps.py \
/Applications/DevEco-Studio.app/Contents/sdk/default/openharmony/native \
example/ohosApp/entry/libs/arm64-v8a
检查结果:
liburi.so: 0 unresolved strong imports
## 四、完整代码对照
### 4.1 整体架构
```text
ArkTS input
│ parseUri / encodeUri / decodeUri
▼
libentry.so
│ N-API
▼
UriParse / UriEncode / UriDecode
│ C ABI
▼
liburi.so
│ Kotlin/Native
▼
Uri.parse / Uri.encode / Uri.decode
│ JSON
▼
ArkUI result card
4.2 文件清单
| 文件 | 职责 |
|---|---|
uri/src/commonMain/.../Uri.kt | URI 公共接口和解析入口 |
uri/src/commonMain/.../UriCodec.kt | 编码和解码实现 |
example/shared/.../UriExamples.kt | 固定示例、转换和公共检查 |
example/nativeApp/.../NativeBridge.kt | C ABI、JSON 返回和内存释放 |
example/ohosApp/.../UriClient.ets | N-API JSON 解析和错误处理 |
example/ohosApp/.../Index.ets | URI 交互工作台页面 |
example/ohosApp/.../napi_init.cpp | N-API 导出和字符串参数读取 |
scripts/build-openharmony.sh | 测试、Native 链接和产物复制 |
scripts/build-hap.sh | 已配置签名工程的 HAP 构建 |
docs/openharmony/VALIDATION.md | 自动检查和验收记录 |
4.3 关键 API 对照
| 层次 | API | 作用 |
|---|---|---|
| Kotlin | Uri.parse | 解析 URI 并暴露结构字段 |
| Kotlin | Uri.encode | 编码普通文本 |
| Kotlin | Uri.decode | 解码百分号文本 |
| Native | UriParse | 返回解析字段 JSON |
| Native | UriEncode | 返回编码结果 JSON |
| Native | UriDecode | 返回解码结果 JSON |
| N-API | parseUri | 向 ArkTS 暴露解析方法 |
| N-API | encodeUri | 向 ArkTS 暴露编码方法 |
| N-API | decodeUri | 向 ArkTS 暴露解码方法 |
4.4 ArkTS 与 Kotlin 的边界
ArkTS 只负责输入和页面生命周期:
const details: UriDetails = parseUri(this.input.trim());
this.details = details;
Kotlin 负责 URI 语义:
val uri = Uri.parse(input)
val encoded = Uri.encode(input)
val decoded = Uri.decode(input, convertPlus = true, throwOnFailure = true)
两者之间只传输 UTF-8 字符串和 JSON,不传输 Kotlin 对象、ArkTS class 实例或未校验的 Native 指针。
五、关键决策说明
决策 1:把 ohosArm64 加入公共构建约定
只有真正链接 ohosArm64 动态库,才能证明 URI 公共实现可以进入 OpenHarmony 运行时。单独写一个 ArkTS 解析器只能验证页面,不能验证三方库适配。
决策 2:独立消费者必须通过构建产物消费
example 单独解析共享库,ohosApp 单独接收 .so 和头文件,避免根工程编译通过却无法在 DevEco 中打包。
决策 3:JSON 作为跨语言数据契约
JSON 让 ArkTS、C++ 和 Kotlin/Native 的边界清楚可调试,并允许后续增加解析字段而不暴露 Kotlin 对象的内部布局。
决策 4:桥接层只开放必要 C ABI 入口
目录、单个样例、公共检查、解析、编码、解码和释放已经覆盖示例所需能力;减少 ABI 符号可以降低 Native 生命周期和兼容风险。
决策 5:交互操作和公共检查分开
公共检查用于自动验收,交互页面用于体验实际 URI 功能。用户输入会走真实的 Kotlin/Native 和 N-API 链路,不把固定检查结果当作页面主体。
决策 6:把库验证、HAP 验证和签名交接分开
JVM 测试验证 URI 规则,Native 链接验证 ABI,Hvigor 验证 HAP,签名工程负责证书和 profile。每一层都有明确失败边界。
六、测试与验证
6.1 测试环境
本次构建验证使用:
- macOS ARM64;
- JDK 21;
- Kotlin Multiplatform
2.2.21-1.0.0; - DevEco Studio 及 OpenHarmony ARM64 Native SDK;
- OpenHarmony API 20 Native SDK;
arm64-v8a原生 ABI。
本次仓库验收以 JVM、Native、C++ 和无签名 HAP 为主。由于仓库不保存证书、profile 或设备密钥,未在仓库工程中执行签名和真机安装。
6.2 静态检查与单元测试
./gradlew :uri:jvmTest
(cd example && ./gradlew :shared:jvmTest)
测试覆盖 URI 往返解析、编码和解码、Builder 输出、hierarchical/opaque 分类、固定示例和非法索引错误。
6.3 Kotlin/Native 和 C ABI 验证
(cd example && ./gradlew :nativeApp:prepareOhos)
该任务成功完成 compileKotlinOhosArm64、linkDebugSharedOhosArm64,并生成 liburi.so 和 liburi_api.h。导出的 UriParse、UriEncode、UriDecode 和 UriFree 符号已加入 linker map。
6.4 ELF 强依赖验证
python3 scripts/check-native-deps.py \
/Applications/DevEco-Studio.app/Contents/sdk/default/openharmony/native \
example/ohosApp/entry/libs/arm64-v8a
结果:
liburi.so: 0 unresolved strong imports
6.5 C++、ArkTS 和 HAP 验证
Hvigor 无签名构建已完成以下步骤:
BuildNativeWithCmake SUCCESS
BuildNativeWithNinja SUCCESS
CompileArkTS SUCCESS
PackageHap SUCCESS
entry-default-unsigned.hap generated
6.6 功能验证用例
用例 1:输入并解析 Web URI
输入 https://example.com/path?q=hello world#section,点击“解析 URI”,页面显示 scheme 为 https、host 为 example.com、path 为 /path、query 和 fragment 等字段。
用例 2:解析 Content URI
点击“Content URI”快速示例,确认页面能够展示 content scheme 和层级路径。
用例 3:解析 opaque URI
点击“Mailto”快速示例,确认页面显示 OPAQUE,并把 scheme-specific part 作为主要结构字段。
用例 4:编码和解码
输入包含空格或中文的普通文本,点击“编码”;复制结果切换到“解码”,确认结果回到原始文本。
用例 5:历史记录回放
连续执行多个操作,点击“最近操作”中的条目,确认输入和操作模式恢复后重新走 Native 链路。
用例 6:错误处理
清空输入后执行,页面显示“请输入 URI 或文本”;输入非法百分号序列进行解码,页面显示 Native 返回的错误文本。
6.7 验证结论
自动测试、Kotlin/Native ARM64 链接、ELF 依赖检查、C++ 编译、ArkTS 编译和无签名 HAP 打包均已完成。由于签名材料不属于仓库交付物,签名 HAP 和真机安装需要在开发者自己的签名工程中继续完成。
七、运行效果
7.1 OpenHarmony 交互页面效果图

效果图中可以看到:
- 页面标题为“URI KMP 三方库”;
- 副标题为“OpenHarmony 交互示例”;
- 输入框预填充 Web URI;
- 当前模式为“解析 URI”;
- 页面提供“编码”和“解码”入口;
- 页面提供 Web 链接、Content URI 和 Mailto 快速示例;
- 解析结果卡片展示 URI 的层级类型和字段值。
7.2 命令速查
# 根工程测试
export JAVA_HOME="/path/to/jdk-21"
./gradlew :uri:jvmTest
# 示例 JVM 测试和 Native 准备
(cd example && ./gradlew :shared:jvmTest :nativeApp:prepareOhos)
# Native 依赖检查
python3 scripts/check-native-deps.py \
/Applications/DevEco-Studio.app/Contents/sdk/default/openharmony/native \
example/ohosApp/entry/libs/arm64-v8a
# 构建无签名 HAP
cd example/ohosApp
/Applications/DevEco-Studio.app/Contents/tools/hvigor/bin/hvigorw \
--mode module -p module=entry@default -p product=default \
-p buildMode=debug clean assembleHap --no-daemon
八、遗留问题与改进方向
8.1 踩坑复盘
- 只改 ArkTS 页面不算 KMP 适配:必须把公共 URI 实现真正编译成
ohosArm64动态库。 - N-API 不负责 URI 业务判断:C++ 只做类型检查、字符串转换和释放,URI 语义放在 Kotlin。
- Native 返回值必须成对管理:Native 分配 JSON 后,N-API 创建 JS 字符串后要立即调用
UriFree。 - 编码和解码输入要区分:解码模式应输入百分号编码文本,编码模式应输入普通文本。
- 无签名 HAP 不能代表设备验收完成:构建成功只说明工程和产物完整,真机安装还需要签名材料。
8.2 已知问题
- 当前示例只打包
arm64-v8a,不提供其他 ABI; libentry.so的 Native 模块校验提示取决于 DevEco SDK 版本;- JS/Wasm 的 npm lock 由 Kotlin Gradle 插件管理,升级依赖时需要按插件提示更新锁文件;
- 当前示例是单页面工作台,多页面应用需要在业务层集中管理 URI 操作历史;
- 未配置证书的 HAP 不能直接通过
hdc安装到真实设备。
8.3 未来优化方向
- 增加
expect/actual封装,让 Android、桌面端和 OpenHarmony 共用更高层的 URI 操作接口; - 将最近操作抽成共享 Kotlin 状态模型,减少页面状态代码;
- 为 Compose Multiplatform 页面提供相同的 URI 结果卡片;
- 增加查询参数的可视化编辑和 Builder 反向构建示例;
- 在持续集成中加入
ohosArm64链接和无签名 HAP 构建任务; - 在独立签名环境中补充真机安装和截图验收记录。
九、总结
9.1 核心难点回顾
本次适配真正需要处理的不是把一个页面放入 DevEco,而是一条完整跨端链路:
ArkUI 输入和交互
→ C++ N-API
→ Kotlin/Native C ABI
→ KMP URI 公共实现
→ JSON
→ ArkUI 结构化结果
9.2 封装层次
- KMP 层:定义稳定的 URI 解析、编码、解码和 Builder API;
- Native 层:生成 ARM64 动态库,并通过 C ABI 输出有限入口;
- N-API 层:完成参数检查、字符串转换和 Native 内存释放;
- ArkTS 层:管理输入、操作模式、错误状态、结果展示和历史记录;
- DevEco 层:完成 CMake、ArkTS、HAP、签名和设备交付。
9.3 三条经验
- 先让公共 URI 模型在 JVM 和 Native 通过,再接入 ArkUI 页面;
- 用 JSON 和少量 C ABI 代替跨语言对象传递;
- 把自动测试、Native 链接、HAP 构建和签名安装分别记录,避免把“编译成功”误认为“设备交付完成”。
9.4 适配成果
当前 uri-kmp 已完成:
ohosArm64Kotlin/Native 目标;- URI 解析、编码、解码和结构字段 JSON 桥接;
- Kotlin/Native + C ABI + C++ N-API 三层调用链;
- URI KMP 三方库 ArkUI 交互示例页面;
- Web、Content URI、Mailto 三类快速示例;
- Native ELF 依赖检查和无签名 HAP 构建;
- AtomGit 项目文档、中英文适配说明和本地效果图。
适配过程 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)