开源鸿蒙平台 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 解析结果。

二、实现路线图

text 复制代码
第 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 工程一致的分层:

text 复制代码
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:

bash 复制代码
export JAVA_HOME="/path/to/jdk-21"
java -version
1.3 创建 OpenHarmony 示例页面

示例页面围绕"输入、操作、结果和历史"组织,而不是把 runChecks() 放在页面中心:

text 复制代码
标题区          URI KMP 三方库 / OpenHarmony 交互示例
输入区          URI 或普通文本输入框
操作区          解析 URI / 编码 / 解码
示例区          Web 链接 / Content URI / Mailto
结果区          URI 字段卡片或编码解码结果
历史区          最近操作和再次使用

第 2 阶段:目标与依赖打通

2.1 加入 ohosArm64 目标
kotlin 复制代码
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 复制代码
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")
    }
  }
}

链接器只导出示例需要的符号:

text 复制代码
UriCatalog
UriGet
UriRunChecks
UriParse
UriEncode
UriDecode
UriFree
2.3 配置独立消费工程

example/shared 通过项目依赖消费 URI 库:

kotlin 复制代码
kotlin {
  sourceSets {
    commonMain.dependencies {
      api("com.eygraber:uri-kmp:${property("uriVersion")}")
    }
  }
}

example/nativeApp 再依赖 shared,从而保证 Native 示例和 JVM 示例使用同一份 URI 门面。

2.4 通过构建产物消费共享库

prepareOhos 在 Native 链接成功后复制动态库和 C 头文件:

kotlin 复制代码
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"))
}

生成文件为:

text 复制代码
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:

text 复制代码
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 解析结果模型
json 复制代码
{
  "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 边界
kotlin 复制代码
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 对象。

最终采用三层桥接:

text 复制代码
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:

kotlin 复制代码
@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 方法,同时保留目录和公共检查方法:

cpp 复制代码
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 字符串后立即释放:

text 复制代码
ArkTS parseUri(input)
        │
        ▼
napi_get_value_string_utf8
        │
        ▼
UriParse / UriEncode / UriDecode
        │
        ▼
napi_create_string_utf8
        │
        ▼
UriFree(nativeBuffer)
4.5 CMake 引入 Kotlin/Native 动态库
cmake 复制代码
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 做轻量类型封装:

typescript 复制代码
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 输入和模式切换

页面提供输入框和三个模式按钮:

typescript 复制代码
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;编码和解码模式显示转换结果。

每次成功操作都会追加一条最近记录:

typescript 复制代码
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 工程结构
text 复制代码
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 动态库准备
bash 复制代码
export JAVA_HOME="/path/to/jdk-21"
cd example
./gradlew :shared:jvmTest :nativeApp:prepareOhos

成功后生成:

text 复制代码
example/ohosApp/entry/libs/arm64-v8a/liburi.so
example/ohosApp/entry/src/main/cpp/include/liburi_api.h
6.3 构建无签名 HAP
bash 复制代码
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

输出位于:

text 复制代码
example/ohosApp/entry/build/default/outputs/default/entry-default-unsigned.hap

无签名 HAP 只用于构建和打包检查,真机安装需要签名 HAP。

6.4 Native 依赖检查
bash 复制代码
python3 scripts/check-native-deps.py \
  /Applications/DevEco-Studio.app/Contents/sdk/default/openharmony/native \
  example/ohosApp/entry/libs/arm64-v8a

检查结果:

text 复制代码
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 只负责输入和页面生命周期:

typescript 复制代码
const details: UriDetails = parseUri(this.input.trim());
this.details = details;

Kotlin 负责 URI 语义:

kotlin 复制代码
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 静态检查与单元测试

bash 复制代码
./gradlew :uri:jvmTest
(cd example && ./gradlew :shared:jvmTest)

测试覆盖 URI 往返解析、编码和解码、Builder 输出、hierarchical/opaque 分类、固定示例和非法索引错误。

6.3 Kotlin/Native 和 C ABI 验证

bash 复制代码
(cd example && ./gradlew :nativeApp:prepareOhos)

该任务成功完成 compileKotlinOhosArm64、linkDebugSharedOhosArm64,并生成 liburi.so 和 liburi_api.h。导出的 UriParse、UriEncode、UriDecode 和 UriFree 符号已加入 linker map。

6.4 ELF 强依赖验证

bash 复制代码
python3 scripts/check-native-deps.py \
  /Applications/DevEco-Studio.app/Contents/sdk/default/openharmony/native \
  example/ohosApp/entry/libs/arm64-v8a

结果:

text 复制代码
liburi.so: 0 unresolved strong imports

6.5 C++、ArkTS 和 HAP 验证

Hvigor 无签名构建已完成以下步骤:

text 复制代码
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 命令速查

bash 复制代码
# 根工程测试
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 踩坑复盘

  1. 只改 ArkTS 页面不算 KMP 适配 :必须把公共 URI 实现真正编译成 ohosArm64 动态库。
  2. N-API 不负责 URI 业务判断:C++ 只做类型检查、字符串转换和释放,URI 语义放在 Kotlin。
  3. Native 返回值必须成对管理 :Native 分配 JSON 后,N-API 创建 JS 字符串后要立即调用 UriFree。
  4. 编码和解码输入要区分:解码模式应输入百分号编码文本,编码模式应输入普通文本。
  5. 无签名 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,而是一条完整跨端链路:

text 复制代码
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 三条经验

  1. 先让公共 URI 模型在 JVM 和 Native 通过,再接入 ArkUI 页面;
  2. 用 JSON 和少量 C ABI 代替跨语言对象传递;
  3. 把自动测试、Native 链接、HAP 构建和签名安装分别记录,避免把"编译成功"误认为"设备交付完成"。

9.4 适配成果

当前 uri-kmp 已完成:

  • ohosArm64 Kotlin/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。

参考文档

相关推荐
小杨互联网1 小时前
开源安全审计 Skills / 轻量安全工具离线镜像合集
开源·工具·离线镜像
king王一帅1 小时前
AI 时代少有人类阅读,我开始建自己的独立站,但真的是 49 年入国军吗
开源·ai编程·markdown
Agent世界1 小时前
摩尔线程开源万卡训练底座:大模型 MoE 训练的四个开关
人工智能·开源·moe
聆思科技AI芯片1 小时前
【AI硬件开发上手教程】怎么使用AI编程实现小聆AI语音控制舵机
人工智能·嵌入式硬件·开源·ai编程·智能硬件
hhhjhl2 小时前
React Native 三方库「react-native-sha256」的鸿蒙化适配指南
react native·华为·harmonyos
2601_949970692 小时前
React Native 三方库「react-native-version-check」的鸿蒙化适配指南
react native·华为·harmonyos
行者-全栈开发2 小时前
【鸿蒙心迹】鸿蒙 PC 应用上架实战:分布式 Markdown 笔记从开发到过审全流程
harmonyos·markdown·arkui·应用上架·鸿蒙 pc·分布式数据同步
hhhjhl2 小时前
React Native 三方库「react-native-proximity」的鸿蒙化适配指南
react native·华为·harmonyos
2601_949970692 小时前
React Native 三方库「react-native-media-notification」的鸿蒙化适配指南
react native·华为·harmonyos