开源鸿蒙平台 KMP_CMP 三方库「WhatIf」适配全流程
本文记录
WhatIf接入 OpenHarmony 的完整过程,覆盖 KMP/CMP 工程盘点、
ohosArm64目标、Kotlin/Native 动态库、C ABI/N-API 桥接、ArkUI 示例页面、
HAP 构建、签名准备和验证记录。WhatIf 是一个用于简化 Kotlin 条件判断、空值处理、字符串/数组/集合判断和
Boolean 逻辑的 Kotlin Multiplatform 库。本次适配复用原有commonMain扩展函数,
由 Kotlin/Native 生成 ARM64 动态库,再通过 C ABI、C++ N-API 和 ArkTS 页面展示
同一份共享 Kotlin 代码的执行结果。
项目地址: AtomGit/oh-tpc/WhatIf
开发工具: 华为云码道
一、背景
1.1 为什么做开源鸿蒙平台 KMP/CMP 适配
WhatIf 原本是一个 Kotlin Multiplatform 扩展库,公共 API 位于
whatif/src/commonMain。它不依赖 Android UI 或 JVM 运行时,适合把同一套条件
表达式复用于 Android、iOS、桌面和 Native 工程。
如果只把页面重新写成 ArkTS,再在页面中手写几个 if 判断,页面可以显示结果,
但无法证明 WhatIf 的 Kotlin 公共 API 已经进入 OpenHarmony Native 运行时。本次
适配需要同时解决以下问题:
| 障碍 | 具体问题 |
|---|---|
| 目标缺失 | 原 KMP 模块没有 ohosArm64(),无法生成 OpenHarmony KLIB。 |
| 工具链不一致 | Kotlin/Native、Gradle、OpenHarmony LLVM 和 Native SDK 必须匹配。 |
| 公共 API 边界 | 扩展函数是 inline/common API,需要在 Native 编译器上保持语义一致。 |
| 语言边界不同 | ArkTS 不能直接持有 Kotlin data class,需要经过 C ABI、C++ N-API 和 JSON。 |
| 内存生命周期 | Kotlin/Native 返回的字符串必须由明确的 Free 函数释放。 |
| 示例可验证性 | 需要在 JVM、Native、CMake、ArkTS 和 HAP 层分别留下可重复的检查。 |
| 交付链路复杂 | ARM64 动态库、HAP、签名、设备安装和依赖检查需要分阶段验证。 |
因此,本项目把适配边界放在三个地方:KMP 目标配置、C ABI/N-API 桥接层和 ArkUI
展示层。WhatIf 的判断语义仍由 Kotlin 共享代码维护,ArkTS 只负责调用 Native
方法、解析 JSON 和展示检查状态。
1.2 库提供的能力
WhatIf 公共模块提供以下能力:
whatIf:条件为true时执行代码,并返回原始对象;whatIfMap:根据条件或对象是否为空返回不同类型的结果;whatIfNotNull:接收对象非空时执行分支;whatIfNotNullAs:非空且可以安全转换为目标类型时执行分支;whatIfNotNullOrEmpty:处理字符串、数组、List、Set 和 Map;addWhatIfNotNull、addAllWhatIfNotNull:按条件向可变集合添加元素;removeWhatIfNotNull、removeAllWhatIfNotNull:按条件删除集合元素;whatIfElse:可空 Boolean 为false时执行分支;whatIfAnd、whatIfOr:组合 Boolean 或 Boolean 集合的判断结果。
示例 facade 将这些 API 组织为四个可展示场景:
| 场景 | 实际调用 | 页面含义 |
|---|---|---|
| 条件执行 | whatIf(true) | true 分支被执行 |
| 空值映射 | whatIfMap(default = ...) | null 安全返回默认值 |
| 集合扩展 | List、Array、String 的 whatIfNotNullOrEmpty | 非空数据进入回调 |
| 布尔扩展 | whatIfElse、whatIfAnd、whatIfOr | Boolean 条件组合 |
公共 facade 另外执行 8 项检查,JVM 测试和 OpenHarmony Native 桥接共用同一组
检查函数,避免测试代码与页面代码各自实现一套判断。
1.3 实现适配
| 维度 | 要求 |
|---|---|
| 代码复用 | 条件、空值、集合和 Boolean 语义全部来自原始 commonMain。 |
| 平台目标 | 为 whatif 和示例模块加入 ohosArm64,生成 libwhatif.so。 |
| 桥接稳定 | 使用少量 C ABI 函数和 UTF-8 JSON,不暴露 Kotlin 对象地址。 |
| UI 完整 | 页面展示当前场景、返回值、能力列表和公共检查状态。 |
| 可测试 | JVM 测试、Native 链接、ELF 依赖、HAP 构建分别验收。 |
| 签名安全 | 仓库只保留签名工程准备脚本,证书和密钥由开发者本机配置。 |
| 仓库规范 | 项目说明、文章、效果图和源码链接统一使用 AtomGit。 |
本项目的 ArkUI 页面是独立的验证宿主,公共 API 仍然保持平台无关。其他
KMP/CMP 应用可以直接复用 WhatIf 的 Kotlin 扩展,不需要依赖 ArkTS 页面。
二、实现路线图
第 1 阶段:项目初始化 ── 盘点 KMP API、测试和 OpenHarmony 示例边界
第 2 阶段:目标与依赖打通 ── 加入 ohosArm64、独立 example 工程和 Native 任务
第 3 阶段:行为与序列化 ── 建立 WhatIfExamples、JSON 记录和八项公共检查
第 4 阶段:原生桥接 ── Kotlin/Native C ABI、C++ N-API、内存释放和类型检查
第 5 阶段:ArkUI 宿主 ── ArkTS Native 客户端、页面状态和刷新交互
第 6 阶段:示例与验证 ── HAP 构建、签名准备、依赖检查和效果图
每个阶段都使用真实产物作为下一阶段输入:先用 JVM 验证共享 WhatIf 行为,再把
相同代码链接为 ARM64 动态库,随后由 CMake 和 N-API 加载到 Stage 工程,最后
构建未签名 HAP 并检查包内的 Native 库。
三、逐步实现过程
第 1 阶段:项目初始化
1.1 盘点公共 API 和工程边界
项目采用与参考 KMP/CMP 工程一致的分层:
whatif/ KMP 条件扩展、空值扩展、集合扩展和 Boolean 扩展
example/shared/ 公共 facade 和 JVM 验收测试
example/nativeApp/ ohosArm64 Kotlin/Native 动态库
example/ohosApp/ DevEco Stage 工程和 ArkUI 页面
scripts/ Native、HAP、签名工程和依赖检查脚本
docs/openharmony/ 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 真机动态库 |
| DevEco product | 6.0.0(20) | Stage 工程兼容和目标版本 |
| ABI | arm64-v8a | HAP 原生库架构 |
| WhatIf 版本 | 1.2.2 | 公共 API 和 Maven 坐标 |
执行 Gradle 脚本前先选择 JDK 21:
export JAVA_HOME="/path/to/jdk-21"
export PATH="$JAVA_HOME/bin:$PATH"
java -version
JDK 25 会让当前 Kotlin 编译器在解析 Java 版本时失败,因此构建脚本明确要求
JDK 21。设备是否为 ARM64 也要单独确认,宿主构建成功不能替代设备检查。
1.3 创建 OpenHarmony 示例目录
示例页面围绕“当前场景、执行结果、能力列表和公共检查”组织:
标题区 WhatIf 工作台 / Kotlin Multiplatform + OpenHarmony
状态区 NATIVE READY
当前场景 条件执行、空值映射、集合扩展或布尔扩展
结果区 detail 文本和重新运行检查按钮
能力区 四个真实 WhatIf 场景,可点击切换
自检区 8/8 公共检查通过
页面不把扩展函数重新实现成 ArkTS 条件判断。用户点击场景时,ArkTS 只改变当前
记录;重新运行检查时,调用 Native 的 runChecks(),由 Kotlin facade 返回结果。
第 2 阶段:目标与依赖打通
2.1 加入 ohosArm64 目标
根模块在原有 JVM、Android 和 Apple 目标基础上增加 OpenHarmony Native:
kotlin {
listOf(
iosX64(),
iosArm64(),
iosSimulatorArm64(),
macosArm64(),
macosX64(),
).forEach {
it.binaries.framework { baseName = "common" }
}
androidTarget {
publishLibraryVariants("release")
}
ohosArm64()
jvm { compilerOptions.jvmTarget.set(JvmTarget.JVM_17) }
}
这样 whatif/src/commonMain 的扩展函数可以直接参与 compileKotlinOhosArm64,
不需要复制一份平台专用实现。
2.2 Native focused build 的作用
example/nativeApp 构建一个受 linker map 约束的 shared library:
kotlin {
ohosArm64 {
binaries.sharedLib {
baseName = "whatif"
linkerOpts(
"--entry=0",
"--version-script=${project.file("src/ohosArm64Main/linker/shared-library.map")}",
)
linkerOpts("-lace_napi.z", "-luv", "-lhilog_ndk.z")
}
}
}
链接器只导出三个 C ABI 符号:
WhatIfCatalog
WhatIfRunChecks
WhatIfFree
目录、检查和释放已经覆盖示例所需能力;减少 ABI 符号可以降低 Native 生命周期
和兼容风险。
2.3 配置独立 example 工程
example/settings.gradle.kts 使用 OpenHarmony 社区 Maven、Maven Central 和
Gradle Plugin Portal:
pluginManagement {
repositories {
maven("https://maven.eazytec-cloud.com/nexus/repository/maven-public/")
mavenCentral()
gradlePluginPortal()
}
}
dependencyResolutionManagement {
repositories {
mavenLocal()
maven("https://maven.eazytec-cloud.com/nexus/repository/maven-public/")
mavenCentral()
}
}
示例通过 composite substitution 消费当前仓库的 whatif 项目:
includeBuild("..") {
dependencySubstitution {
substitute(module("com.github.skydoves:whatif"))
.using(project(":whatif"))
}
}
共享模块仍然以正常坐标表达依赖:
sourceSets {
commonMain.dependencies {
api("com.github.skydoves:whatif:1.2.2")
}
}
2.4 通过构建产物消费共享库
prepareOhos 在 Native 链接成功后复制动态库和 C 头文件:
val prepareOhos by tasks.registering(Copy::class) {
dependsOn("linkDebugSharedOhosArm64")
from(layout.buildDirectory.dir("bin/ohosArm64/debugShared")) {
include("libwhatif.so")
into("libs/arm64-v8a")
}
from(layout.buildDirectory.dir("bin/ohosArm64/debugShared")) {
include("libwhatif_api.h")
into("src/main/cpp/include")
}
into(rootProject.layout.projectDirectory.dir("ohosApp/entry"))
}
动态库、生成头文件和 HAP 属于构建产物,仓库通过 .gitignore 排除它们;每台
开发机都可以从源码重新生成与自身 SDK 匹配的文件。
第 3 阶段:行为与序列化
3.1 为什么需要统一 JSON 契约
Kotlin/Native 返回的是 Kotlin 侧的字符串指针,C++ N-API 再把它转换为 ArkTS
字符串。跨语言边界不传递 WhatIfExample 对象本身,而是使用稳定的 UTF-8 JSON:
WhatIfExamples.catalog()
↓ Kotlin data class -> JSON
WhatIfCatalog()
↓ const char* + WhatIfFree()
C++ N-API getCatalog()
↓ JavaScript string
WhatIfClient.ets -> JSON.parse()
↓
Index.ets 页面状态
这样页面只负责解析展示,条件判断、空值处理、集合处理和 Boolean 逻辑仍然只在
Kotlin commonMain 中维护。
3.2 WhatIfExamples facade
示例公共 facade 位于 example/shared/src/commonMain,将原库扩展函数组织为四个
可观察场景:
public object WhatIfExamples {
public fun catalog(): List<WhatIfExample> = listOf(
WhatIfExample("what-if", "条件执行", evaluateWhatIf()),
WhatIfExample("what-if-map", "空值映射", evaluateMap()),
WhatIfExample("collections", "集合扩展", evaluateCollections()),
WhatIfExample("boolean", "布尔扩展", evaluateBoolean()),
)
}
四个场景分别验证:whatIf(true) 执行 true 分支,whatIfMap 对空值返回默认值,
whatIfNotNullOrEmpty 覆盖 List、Array 和 String,以及 whatIfElse、
whatIfAnd、whatIfOr 的 Boolean 组合。
3.3 八项公共检查
runChecks() 返回固定顺序的检查名称,并在任意检查失败时抛出异常。当前八项检查
覆盖:
whatIf只执行 true 分支;whatIfMap对 null 返回默认值;- 可空集合、数组和字符串的非空判断;
- 可空 Boolean 的
whatIfElse; - 可变集合按条件添加非空值;
whatIfNotNull执行非空分支;whatIfAnd和whatIfOr保持谓词语义;- 公共 facade 不包含平台类型。
返回给 ArkTS 的 JSON 形状如下:
{
"passed": true,
"checks": [
"whatIf executes only for true",
"whatIfMap returns the default for null"
]
}
checks.length 在页面上显示为 8/8 公共检查通过。检查名称使用英文是为了让
JVM、Native 和 ArkTS 日志都保持稳定,页面状态文字仍然使用中文。
3.4 JSON 字符串和错误边界
NativeBridge.kt 中的 quote 会转义反斜杠、双引号和控制字符;response 捕获
Kotlin 异常并返回 {"error":"..."}。这样 C++ 和 ArkTS 不需要解析 Kotlin
异常对象,也不会因为错误信息包含特殊字符而得到无效 JSON。
第 4 阶段:原生桥接(技术难点)
4.1 Kotlin/Native 对象不能直接交给 ArkTS
WhatIfExample 是 Kotlin data class,属于 Kotlin/Native 运行时对象。ArkTS 只能
接收 JavaScript 值,C++ 也不能把 Kotlin 对象地址当作 JavaScript 对象使用。本项目
采用三层桥接:
ArkTS
│ JSON string
▼
C++ N-API entry
│ const char*
▼
Kotlin/Native C ABI
│ WhatIfExamples
▼
UTF-8 JSON + explicit free
4.2 方案对比
| 方案 | 优点 | 缺点 | 选用 |
|---|---|---|---|
| 直接导出 Kotlin 对象 | 代码少 | ABI、生命周期和类型不可控 | ❌ |
| 只导出场景编号 | 实现简单 | 页面无法拿到共享扩展的真实结果 | ❌ |
| C ABI + JSON | 边界清晰、易扩展、易调试 | 有一次序列化开销 | ✅ |
| 在 ArkTS 重写 WhatIf 逻辑 | 页面调用简单 | Kotlin 和 ArkTS 逻辑容易分叉 | ❌ |
4.3 Kotlin/Native 导出函数
@CName("WhatIfCatalog")
public fun catalogNative(): CPointer<ByteVar> = response {
WhatIfExamples.catalog().joinToString(prefix = "[", postfix = "]") { it.toJson() }
}
@CName("WhatIfRunChecks")
public fun checksNative(): CPointer<ByteVar> = response {
val checks = WhatIfExamples.runChecks()
"{\"passed\":true,\"checks\":[${checks.joinToString { quote(it) }}]}"
}
@CName("WhatIfFree")
public fun freeNative(pointer: CPointer<ByteVar>?) {
if (pointer != null) nativeHeap.free(pointer.rawValue)
}
返回值由 nativeHeap 分配为以 0 结尾的 C 字符串。C++ 创建 ArkTS 字符串后立即
调用 WhatIfFree,每一块 Native 缓冲区都有明确的释放责任。
4.4 C++ N-API 方法分发
napi_init.cpp 注册两个供 ArkTS 使用的方法:
napi_property_descriptor methods[] = {
{"getCatalog", nullptr, Catalog, nullptr, nullptr, nullptr,
napi_default, nullptr},
{"runChecks", nullptr, RunChecks, nullptr, nullptr, nullptr,
napi_default, nullptr},
};
每个方法都遵循“调用 Kotlin/Native、创建 ArkTS 字符串、释放 Native 缓冲区”的
顺序。N-API 只做字符串转换和导出注册,不重新解释 WhatIf 的条件语义。
4.5 CMake 和 linker map
Kotlin/Native 动态库以 imported library 的方式交给 CMake:
add_library(whatif SHARED IMPORTED)
set_target_properties(whatif PROPERTIES
IMPORTED_LOCATION "${CMAKE_CURRENT_SOURCE_DIR}/../../../libs/arm64-v8a/libwhatif.so")
add_library(entry SHARED napi_init.cpp)
target_include_directories(entry PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/include")
target_link_libraries(entry PRIVATE whatif libace_napi.z.so)
shared-library.map 限制导出符号为 WhatIfCatalog、WhatIfRunChecks 和
WhatIfFree。这让动态库的公开 ABI 保持最小,避免把 Kotlin/Native 内部符号泄漏
到 HAP。
第 5 阶段:ArkUI 宿主
5.1 WhatIfClient.ets
ArkTS 客户端只负责把 Native 返回的 JSON 解析成页面类型:
import whatIfNative from 'libentry.so';
export function catalog(): WhatIfExample[] {
return JSON.parse(whatIfNative.getCatalog()) as WhatIfExample[];
}
export function runChecks(): WhatIfChecks {
return JSON.parse(whatIfNative.runChecks()) as WhatIfChecks;
}
5.2 Index.ets 页面状态
页面在 aboutToAppear 中读取目录并运行公共检查;点击能力列表只切换当前记录,
不会把 WhatIf 扩展函数复制成 ArkTS 逻辑:
@State private examples: WhatIfExample[] = [];
@State private selected: WhatIfExample = emptyExample();
@State private status: string = '正在加载';
private refresh(): void {
this.examples = catalog();
this.selected = this.examples[0] ?? emptyExample();
const checks = runChecks();
this.status = checks.passed
? `${checks.checks.length}/8 公共检查通过`
: '公共检查失败';
}
页面包括标题、Native READY 状态、当前场景、结果说明、四个能力入口、重新运行
检查按钮和底部数据来源说明。发生 Native 错误时,错误文本会出现在页面底部。
5.3 页面与真实能力的边界
当前示例展示的是 WhatIf 公共扩展执行结果,不声明设备传感器或系统动作能力。页面
选择场景和重新运行检查都经过 libentry.so,因此效果图能够说明 Kotlin/Native、
N-API 和 ArkTS 页面已经连通;它不替代签名安装后的真机行为验证。
第 6 阶段:示例与验证
6.1 example 工程结构
example/
├── shared/
│ ├── src/commonMain/.../WhatIfExamples.kt
│ └── src/commonTest/.../WhatIfExamplesTest.kt
├── nativeApp/
│ ├── src/ohosArm64Main/.../NativeBridge.kt
│ └── src/ohosArm64Main/linker/shared-library.map
└── ohosApp/
├── entry/src/main/cpp/
│ ├── CMakeLists.txt
│ └── napi_init.cpp
└── entry/src/main/ets/
├── pages/Index.ets
└── whatif/WhatIfClient.ets
shared 验证公共 facade,nativeApp 产生 ARM64 动态库,ohosApp 负责 CMake、
N-API 和 ArkUI 页面。三层可以分别构建,便于定位失败位置。
6.2 Native 模块注册
napi_init.cpp 通过 napi_module_register 注册 entry 模块,ArkTS 类型声明
位于:
example/ohosApp/entry/src/main/cpp/types/libentry/index.d.ts
ArkTS 通过下面的模块导入使用 Native 方法:
import whatIfNative from 'libentry.so';
6.3 Native 动态库准备
执行:
export JAVA_HOME="/path/to/jdk-21"
./scripts/build-openharmony.sh
脚本会运行根模块 JVM 测试、示例 JVM 测试、linkDebugSharedOhosArm64 和
prepareOhos。成功后会在本机构建出:
example/ohosApp/entry/libs/arm64-v8a/libwhatif.so
example/ohosApp/entry/src/main/cpp/include/libwhatif_api.h
6.4 构建、签名和安装
构建脚本当前明确生成未签名 HAP:
./scripts/build-hap.sh "$PWD/example/ohosApp"
脚本输出位置为:
example/ohosApp/entry/build/default/outputs/default/entry-default-unsigned.hap
签名配置需要在 DevEco Studio 或本机工程中完成后,才可以安装到设备。证书、profile、
P12 和密码不写入仓库;未完成签名时不执行安装命令。
四、完整代码对照
4.1 整体架构
WhatIf commonMain extensions
│ whatIf / whatIfMap / collection / Boolean APIs
▼
WhatIfExamples.kt
│ catalog() / runChecks()
▼
Kotlin/Native C ABI
│ WhatIfCatalog / WhatIfRunChecks / WhatIfFree
▼
libwhatif.so + C++ N-API libentry.so
│ UTF-8 JSON
▼
WhatIfClient.ets
│ JSON.parse()
▼
Index.ets ArkUI page
4.2 文件清单
| 文件 | 职责 |
|---|---|
whatif/src/commonMain | WhatIf 原始条件、空值、集合和 Boolean 扩展 |
example/shared/src/commonMain/.../WhatIfExamples.kt | 场景 facade、JSON 记录来源和八项检查 |
example/shared/src/commonTest | JVM 公共行为测试 |
example/nativeApp/src/ohosArm64Main/.../NativeBridge.kt | C ABI、JSON 返回和内存释放 |
example/nativeApp/src/ohosArm64Main/linker/shared-library.map | Native 导出符号白名单 |
example/ohosApp/entry/src/main/cpp/napi_init.cpp | N-API 注册、字符串转换和释放 |
example/ohosApp/entry/src/main/cpp/CMakeLists.txt | 链接 libwhatif.so 和 libace_napi.z.so |
example/ohosApp/entry/src/main/ets/whatif/WhatIfClient.ets | N-API JSON 解析和类型定义 |
example/ohosApp/entry/src/main/ets/pages/Index.ets | ArkUI 页面和场景选择 |
scripts/build-openharmony.sh | 测试、Native 链接和产物复制 |
scripts/build-hap.sh | Hvigor 未签名 HAP 构建 |
scripts/check-native-deps.py | ARM64 ELF 强依赖检查 |
docs/openharmony/VALIDATION.md | 自动检查和签名边界说明 |
4.3 关键 API 对照
| 层次 | API | 作用 |
|---|---|---|
| Kotlin | whatIf | 条件成立时执行 true 分支 |
| Kotlin | whatIfMap | 根据空值安全返回映射或默认值 |
| Kotlin | whatIfNotNullOrEmpty | 处理集合、数组和字符串的非空分支 |
| Kotlin | whatIfElse / whatIfAnd / whatIfOr | 组合 Boolean 条件 |
| Kotlin facade | WhatIfExamples.catalog | 返回四个示例场景 |
| Kotlin facade | WhatIfExamples.runChecks | 执行八项公共检查 |
| Native | WhatIfCatalog | 返回场景目录 JSON |
| Native | WhatIfRunChecks | 返回公共检查 JSON |
| Native | WhatIfFree | 释放 Native 字符串 |
| ArkTS | catalog / runChecks | 解析 N-API 返回值 |
4.4 ArkTS 与 Kotlin 的边界
ArkTS 只保存目录、当前选择和检查状态:
this.examples = catalog();
this.selected = this.examples[0] ?? emptyExample();
const checks = runChecks();
this.status = `${checks.checks.length}/8 公共检查通过`;
Kotlin 负责真正调用原库扩展:
private fun evaluateWhatIf(): String {
var result = "false branch"
"payload".whatIf(true) { result = "true branch" }
return result
}
两者之间只传输 JSON 字符串,不传输 Kotlin 对象地址或未经约束的动态对象。
五、关键决策说明
决策 1:把 ohosArm64 加入公共构建约定
只有把 whatif 和示例 Native 模块都编译为 ohosArm64,才能确认原始
commonMain 扩展在 OpenHarmony ARM64 工具链上可编译和可链接。
决策 2:独立消费者通过构建产物消费
example 作为独立 Gradle 工程解析根项目,ohosApp 只接收生成的 .so 和头文件。
这样 Gradle 测试、Native 链接和 Hvigor 打包的边界清楚,根工程构建成功也不会掩盖
DevEco 工程缺少 Native 产物的问题。
决策 3:JSON 作为跨语言数据契约
目录和检查结果字段少且稳定,JSON 便于 ArkTS 调试,也避免暴露 Kotlin/Native 对象
布局。后续增加展示字段时可以保持已有字段兼容。
决策 4:只开放三个 C ABI 入口
目录、检查和释放已经覆盖当前示例;不额外导出内部扩展函数,减少 Native ABI 和
生命周期风险。
决策 5:页面选择与检查执行分开
页面切换能力入口只改变当前展示记录;公共检查通过 Native 重新执行。这样可以同时
验证“读取共享结果”和“重复执行共享逻辑”两条路径。
决策 6:库验证与设备验证分开
JVM 测试验证公共语义,Native 链接验证 ABI,ELF 检查验证系统依赖,Hvigor 验证 HAP
工程。当前尚未配置签名和安装设备,因此文章不把未签名 HAP 当作真机验收结论。
六、测试与验证
6.1 测试环境
本次验证使用 macOS、JDK 21、Kotlin Multiplatform 2.2.21-1.0.0、DevEco Studio
6.0.0(20) 和 OpenHarmony ARM64 Native SDK。签名证书和设备安装不属于本次已完成的
验证范围。
6.2 静态检查与单元测试
已执行:
./gradlew :whatif:jvmTest
(cd example && ./gradlew :shared:jvmTest)
根模块和示例模块的 JVM 测试通过,示例检查覆盖条件执行、空值映射、集合/数组/字符串、
Boolean、可变集合、非空接收者和逻辑谓词。
6.3 原生桥接和 HAP 验证
已执行:
./scripts/build-openharmony.sh
./scripts/build-hap.sh "$PWD/example/ohosApp"
Native 依赖检查结果为:
libc++_shared.so: 0 unresolved strong imports
libentry.so: 0 unresolved strong imports
libwhatif.so: 0 unresolved strong imports
HAP 构建产物为:
example/ohosApp/entry/build/default/outputs/default/entry-default-unsigned.hap
Hvigor 构建成功,但当前工程没有可用的签名配置,日志提示 No signingConfig found for product default。因此没有执行签名、安装或设备功能验证,也没有把任何证书、密钥
或密码提交到 AtomGit。
6.4 已完成的验证用例
用例 1:共享模块行为
JVM 测试直接调用 WhatIfExamples.runChecks(),确认八项公共检查全部通过。
用例 2:Native 动态库
linkDebugSharedOhosArm64 成功生成 libwhatif.so 和 C 头文件,C++ N-API 模块成功
链接到该动态库。
用例 3:ArkUI 页面构建
Hvigor 成功编译并打包 ohosApp,页面所需的 libentry.so 和 libwhatif.so 已进入
ARM64 构建链路。
用例 4:效果图对应的页面内容
效果图展示了 WhatIf 工作台、四个能力入口、true branch、8/8 公共检查通过 和
Kotlin/Native → N-API → ArkTS 说明。这些内容来自页面数据和构建结果;它不证明签名
HAP 已经在设备上安装。
6.5 验证结论
当前可以确认:共享 Kotlin API 的 JVM 测试通过,OpenHarmony ARM64 Native 动态库
成功链接,C++ N-API 模块和 ArkUI HAP 成功构建,Native 强依赖检查通过。签名和真机
安装需要在本机配置证书后继续完成。
七、运行效果
7.1 WhatIf 工作台效果图

图中可以看到:
- 页面标题为“WhatIf 工作台”;
- 副标题为“Kotlin Multiplatform / OpenHarmony”;
- 当前条件执行结果显示
true branch; - 页面提供条件执行、空值映射、集合扩展和布尔扩展四个入口;
- 底部显示
8/8 公共检查通过; - 页面明确标注 Kotlin/Native → N-API → ArkTS 的数据路径。
7.2 命令速查
# 根工程和 example JVM 测试、Native 动态库准备
export JAVA_HOME="/path/to/jdk-21"
./scripts/build-openharmony.sh
# 单独运行示例共享测试
(cd example && ./gradlew :shared:jvmTest)
# 检查打包的 ARM64 Native 库强依赖
python3 scripts/check-native-deps.py \
/Applications/DevEco-Studio.app/Contents/sdk/default/openharmony/native \
example/ohosApp/entry/libs/arm64-v8a
# 构建未签名 HAP
./scripts/build-hap.sh example/ohosApp
配置签名后,再由开发者在 DevEco Studio 或本机 HAP 工程中完成签名和设备安装。
八、遗留问题与改进方向
8.1 踩坑复盘
- 只改 ArkTS 页面不算 KMP 适配:必须把原始
commonMain真正编译为
ohosArm64动态库并由页面调用。 - N-API 不负责业务判断:C++ 只完成模块注册、字符串转换和释放,WhatIf 语义
放在 Kotlin 公共代码。 - Native 字符串必须释放:每次
WhatIfCatalog或WhatIfRunChecks返回的
缓冲区都要调用WhatIfFree。 - JDK 版本需要固定:当前 Kotlin/Gradle 组合按 JDK 21 验证,其他版本要重新确认。
- 签名和构建分开:Hvigor 能成功生成未签名 HAP,不等于可以安装到设备。
8.2 已知问题
- 当前示例只接受 ARM64 OpenHarmony 目标,未提供其他 ABI 的 Native 产物;
- 当前仓库验证了 Native 和 HAP 构建,尚未发布独立 HAR/npm 交付包;
- HAP 当前为未签名产物,未执行设备安装和真实设备交互验收;
- 页面使用
JSON.parse读取固定契约,正式产品可增加字段版本和错误类型; - WhatIf 扩展本身是无状态函数,后续示例可加入 Compose Multiplatform 页面以展示
同一公共 facade 的更多 UI 宿主。
8.3 未来优化方向
- 发布可直接消费的 OpenHarmony Native/HAR 产物,并补充版本化交付说明;
- 为 Compose Multiplatform 增加与 ArkUI 页面一致的场景卡片;
- 在 CI 中固定 JDK、Native SDK 和 HAP 未签名构建检查;
- 增加 JSON 契约测试、错误码和 API 版本字段;
- 在具备签名和设备条件后,补做安装、启动、页面交互和 Native 依赖回归。
九、总结
9.1 核心难点回顾
WhatIf 的鸿蒙适配不是把 Kotlin 条件判断翻译成 ArkTS,而是让同一份共享代码
经过完整链路进入 OpenHarmony:
WhatIf commonMain
→ ohosArm64 Kotlin/Native
→ C ABI JSON
→ C++ N-API
→ ArkTS JSON.parse
→ ArkUI 工作台
9.2 封装层次
- KMP 层:保留条件、空值、集合和 Boolean 扩展的公共语义;
- 示例 facade:把扩展函数组织成四个场景并提供八项检查;
- Native 层:生成 ARM64 动态库,只导出三个 C ABI 入口;
- N-API 层:完成模块注册、字符串转换和 Native 缓冲区释放;
- ArkTS 层:解析 JSON、管理页面状态并展示验证结果;
- DevEco 层:完成 CMake、Hvigor、HAP 构建和后续签名入口。
9.3 三条经验
- 先让公共 API 在 JVM 和 Native 目标通过,再接入 ArkUI 页面;
- 用 JSON 和少量 C ABI 代替跨语言对象传递,并让释放责任回到导出方;
- 分开记录测试、Native 链接、依赖检查、HAP 构建和设备验证,避免把构建成功写成
真机验收完成。
9.4 适配成果
当前 WhatIf OpenHarmony 适配已完成:
whatif公共模块加入ohosArm64目标;WhatIfExamples覆盖四个典型 WhatIf 使用场景;- JVM 和 Native 共用八项公共检查;
- Kotlin/Native + C ABI + C++ N-API + ArkTS JSON 链路打通;
- ARM64 Native 依赖检查通过;
- ArkUI WhatIf 工作台和效果图已加入仓库;
- HAP 未签名构建成功,签名材料保留给开发者本机配置;
- 项目说明、文章和源码链接统一使用 AtomGit。
参考文档
更多推荐



所有评论(0)