开源鸿蒙平台 CMP 三方库「ContactsComposeMultiplatform」适配全流程
本文记录
ContactsComposeMultiplatform接入 OpenHarmony 的完整过程,覆盖 KMP 公共应用层盘点、ohosArm64/ohosX64目标、Kotlin/Native 动态库、C ABI/N-API 桥接、ArkUI 真机页面、HAP 构建、签名和真机验收。
ContactsComposeMultiplatform是 Philipp Lackner 的 Compose Multiplatform 通讯录教程应用(Android + iOS),shared模块commonMain中包含完整的 domain / data / presentation 三层。本次适配让这三层业务模型进入 OpenHarmony ARM64 运行时,ArkUI 页面作为真机验收宿主,Compose Multiplatform(CMP)应用的同类三层代码可以通过同一条 Kotlin/Native 链路复用。
项目地址: AtomGit/oh-tpc/ContactsComposeMultiplatform
上游仓库: philipplackner/ContactsComposeMultiplatform
开发工具: 华为云码道
环境安装: 环境安装指南
一、背景
1.1 为什么做 OpenHarmony 平台 KMP/CMP 适配
ContactsComposeMultiplatform 为 CMP 提供一个"三层齐全"的真实应用样例:Contact 实体与 ContactValidator 校验器(domain 层)、ContactDataSource 数据源契约与 contact.sq 建表语句(data 层)、ContactListViewModel 状态机与事件面(presentation 层),再由 Compose UI 渲染。KMP 项目通常把这些能力放在 commonMain,由 Android、iOS 宿主共享。OpenHarmony 应用同样需要这套能力,但默认构建并不会生成 OpenHarmony 的 Kotlin/Native 变体。
如果只把示例页面改成 ArkTS,页面可以显示联系人,却无法证明公共业务模型已经在 OpenHarmony ARM64 设备上完成编译、链接和执行。适配需要同时验证 KMP 模型、Native 动态库、C ABI、N-API、ArkUI 页面和 HAP 交付链路。
| 障碍 | 具体问题 |
|---|---|
| 目标缺失 | 必须增加 ohosArm64() / ohosX64() 才能生成 OpenHarmony 动态库。 |
| 工具链不一致 | 上游是 Kotlin 1.8.20 / Compose 1.4.0;社区鸿蒙 CMP 是 1.9.2、HarmonyOS Kotlin 是 2.2.21-1.0.0,整体迁移等于重写并拖坏 Android / iOS。 |
| 依赖缺位 | SQLDelight 1.5.5 无 ohos driver,moko-mvvm 0.16.1 无 ohos 目标,kotlinx-datetime 0.4.0 同样缺失。 |
| 语言边界不同 | ArkTS 不能直接持有 Kotlin 对象,需要 C ABI、C++ N-API 和 JSON。 |
| 异步语义差异 | 上游用 Flow + combine 聚合状态、delay(300L) 等浮层动画;同步 C ABI 桥没有协程泵。 |
| 交付链路复杂 | .so、CMake、N-API、HAP、签名和 hdc 都要单独验收。 |
因此,本项目把边界放在 KMP 目标配置、C ABI/N-API 桥接和 ArkUI 验收层。业务语义仍由公共 Kotlin 实现负责,ArkTS 只负责调用 Native、解析 JSON 和渲染结果。
1.2 应用提供的能力
真机示例提供五个可观察场景:
- 联系人列表:
getContacts按firstName ASC排序渲染; - 最近添加:
getRecentContacts(20)按createdAt DESC取前 20 条横向滑动展示; - 新增 / 编辑: 四字段表单(First name / Last name / Email / Phone number),保存前经
ContactValidator校验,错误文案与上游逐字一致; - 删除: 详情浮层删除后,列表与最近添加两处同步消失;
- 头像:
PhotoViewPicker选图,字节以 Base64 过桥,渲染为PixelMap。
页面渲染的是 ContactListState 完整状态快照:
| 字段 | 作用 |
|---|---|
contacts | 全量联系人,firstName ASC。 |
recentlyAddedContacts | createdAt DESC 前 20 条。 |
selectedContact | 详情浮层当前联系人。 |
newContact | 新增 / 编辑表单正在编辑的草稿。 |
isAddContactSheetOpen / isSelectedContactSheetOpen | 两个浮层的开关。 |
firstNameError 等 ×4 | 四条校验错误文案。 |
totalContacts | 列表总数,渲染 My contacts (n)。 |
共享层还提供八项对账检查:首字母排序、最近添加限 20 且按时间倒序、四条错误文案逐字一致、空表单保存不落库、INSERT OR REPLACE 保号不重复、编辑刷新 createdAt、动画时序(cleanup 语义)和错误信封(未知动作返回 ok:false + error)。
1.3 实现适配
| 维度 | 要求 |
|---|---|
| 代码复用 | domain / data / presentation 三层逻辑全部位于 Kotlin,逐字对齐上游(校验器含正则与错误文案)。 |
| 平台目标 | 独立 Gradle 工程编译 ohosArm64 + ohosX64,生成双 ABI 动态库。 |
| 桥接稳定 | 使用少量 C ABI 和 UTF-8 JSON 完整状态快照,不传递 Kotlin 对象地址。 |
| CMP 兼容 | 根工程原样保留,Android / iOS 继续用 Kotlin 1.8.20 构建;ArkUI 只负责真机验收。 |
| UI 完整 | 列表、最近添加、新增 / 编辑浮层、详情浮层、校验错误展示和头像选择。 |
| 仓库规范 | 项目说明、文章、效果图和源码链接统一使用 AtomGit。 |
本项目的 ArkUI 页面不是 Compose Multiplatform UI 实现。CMP 工程可以直接复用同一套 Kotlin 三层模型;等社区 CMP 工具链与上游版本对齐后,Compose UI 层可整体上岗,桥与模型层不动。
二、实现路线图
第 1 阶段:项目初始化 ── 盘点上游三层结构、工程边界和 OpenHarmony 交付物
第 2 阶段:目标与依赖打通 ── 独立 example Gradle 工程、双 ABI sharedLib 和产物拷贝
第 3 阶段:业务模型建模 ── Contact / ContactValidator / contact.sq 语义 / 状态机
第 4 阶段:原生桥接 ── @CName C ABI、C++ N-API、参数校验和内存释放
第 5 阶段:ArkUI 宿主 ── 完整状态快照渲染、浮层时序、头像链路和错误边界
第 6 阶段:示例与验证 ── JVM 对照测试、双 ABI 链接、HAP、签名、真机运行和效果图
三、逐步实现过程
第 1 阶段:项目初始化
1.1 工程边界
androidContactsMP/ 上游 Android App(未改动)
iosContactsMP/ 上游 iOS App(未改动)
shared/ 上游 shared 模块:Compose UI + 三层逻辑(未改动)
example/ 鸿蒙适配独立 Gradle 工程(Gradle 与 Hvigor 分离)
nativeApp/ Kotlin 移植层 → ohosArm64/ohosX64 → libohoscontacts.so
harmonyApp/ DevEco Stage 工程:CMake NAPI + ArkTS UI → HAP
example 是独立 Gradle 工程,不把 DevEco 工程作为根项目的 Kotlin 子模块,Gradle 和 Hvigor 可以分开执行;根工程的构建文件一个字节都不用改。
1.2 工具链矩阵
| 项目 | 配置 |
|---|---|
| Kotlin Multiplatform | HarmonyOS Kotlin 2.2.21-1.0.0(eazytec Maven) |
| Gradle | 8.14.1(example 自带 wrapper) |
| JDK | 21(org.gradle.java.home 已钉进 example/gradle.properties) |
| OpenHarmony 目标 | ohosArm64 + ohosX64 |
| DevEco product | 5.1.0(18),runtimeOS HarmonyOS |
| DevEco Studio | 5.1.0 Release |
| ABI | arm64-v8a + x86_64 |
| Bundle | org.terminator.ohos.contacts |
cd example
./gradlew :nativeApp:linkDebugSharedOhosArm64 :nativeApp:linkDebugSharedOhosX64
example/settings.gradle.kts 中两个 eazytec 仓库必须写在 gradlePluginPortal() / google() 之前,否则 HarmonyOS Kotlin 插件解析不到。
1.3 页面边界
最近添加区 横滑头像卡(createdAt DESC 前 20)
标题区 My contacts (n)
列表区 联系人行(firstName ASC)
操作区 FAB 新增 / Save / Edit / Delete
浮层区 新增编辑表单(四字段 + 校验错误)/ 联系人详情
页面启动时发送一次 seed 动作播种演示数据,之后每次交互都是"一个动作进、一份完整状态快照出"。
第 2 阶段:目标与依赖打通
2.1 双目标 sharedLib
kotlin {
applyDefaultHierarchyTemplate()
compilerOptions {
optIn.add("kotlin.experimental.ExperimentalNativeApi")
}
ohosArm64 {
binaries.sharedLib { baseName = "ohoscontacts" }
}
ohosX64 {
binaries.sharedLib { baseName = "ohoscontacts" }
}
}
只有 nativeApp 一个模块开启 binaries.sharedLib,避免每个模块各吐一份带运行时的 so。链接任务 finalizedBy 两个 Copy 任务,把 libohoscontacts.so(实测 arm64-v8a 约 8.9 MB、x86_64 约 7.6 MB,含 Kotlin/Native 运行时)和匹配 triple 的 libc++_shared.so 拷进 harmonyApp/entry/libs/{arm64-v8a,x86_64}/。
2.2 产物链
example/nativeApp
└─ build/bin/ohosArm64/debugShared/libohoscontacts.so
example/harmonyApp/entry/libs/arm64-v8a/
├─ libohoscontacts.so
└─ libc++_shared.so
entry/libs/ 整体在 .gitignore 里——产物不进版本库,clean 之后重跑链接命令即可再生。
2.3 独立工程的依赖策略
移植层不依赖任何第三方库:Bridge.kt 手写 MiniJson,时钟用 POSIX gettimeofday,状态机是纯 Kotlin reducer。少一个需要和 HarmonyOS Kotlin 版本对齐的依赖,桥的 C 签名就多一分稳定。
第 3 阶段:业务模型建模
3.1 JSON 契约
ContactListStateMachine.onEvent(event)
↓ ContactListState
refreshContacts()(重读两个列表,等价上游 combine)
↓ 完整状态快照 JSON
C ABI ContactsDispatch(const char*)
↓
N-API contactsDispatch(request)
↓
ArkUI Index.ets(纯渲染)
页面不复制任何业务判断:排序、校验、浮层状态机的每一次迁移都由公共 Kotlin 代码生成。
3.2 结果数据类
data class Contact(
val id: Long?,
val firstName: String,
val lastName: String,
val email: String,
val phoneNumber: String,
val photoBase64: String?,
)
data class ContactListState(
val contacts: List<Contact> = emptyList(),
val recentlyAddedContacts: List<Contact> = emptyList(),
val selectedContact: Contact? = null,
val isAddContactSheetOpen: Boolean = false,
val isSelectedContactSheetOpen: Boolean = false,
val firstNameError: String? = null,
val lastNameError: String? = null,
val emailError: String? = null,
val phoneNumberError: String? = null,
)
photoBytes: ByteArray? 改为 photoBase64: String?,是唯一一处字段级改动:C ABI 走 UTF-8 JSON,字节数组过不去。
3.3 校验与数据源语义
校验器逐字移植,错误文案一个字符不差:
val emailRegex = Regex("^[\\w-\\.]+@([\\w-]+\\.)+[\\w-]{2,4}\$")
if (!emailRegex.matches(contact.email)) {
result = result.copy(emailError = "This is not a valid email.")
}
SQLDelight 没有 ohos driver,内存实现严格复刻 contact.sq 四条语义:
| SQL | 内存实现 |
|---|---|
SELECT * ORDER BY firstName ASC | sortedWith(compareBy({ firstName }, { id })) |
ORDER BY createdAt DESC LIMIT :amount | 时间倒序取前 N,另补一条 SQL 没写的约束:连续插入共享同一毫秒时 createdAt 强制严格递增 |
INSERT OR REPLACE | 编辑复用原 id 整行替换并刷新 createdAt(被编辑的联系人爬回"最近添加"顶部) |
| 自增主键 | id=NULL 走自增,带 id 替换保号 |
第 4 阶段:原生桥接(技术难点)
4.1 三层桥接
ArkTS
│ JSON string(一次动作)
▼
C++ N-API entry
│ const char*
▼
Kotlin/Native C ABI
│ ContactListStateMachine
▼
完整状态快照 JSON + DisposeString
直接导出 Kotlin 对象会暴露 ABI 和生命周期问题;HarmonyOS CAdapter 也不会把 @CName 业务函数挂到 ArkTS 模块上,所以 ArkTS 只 import CMake 包装库 libcontacts_napi.so。
4.2 Kotlin/Native 导出函数
@CName("ContactsDispatch")
fun contactsDispatch(request: String): String {
return try {
val json = MiniJson.parse(request)
val error = dispatchAction(json)
stateJson(ok = error == null, error = error)
} catch (t: Throwable) {
stateJson(ok = false, error = t.message ?: t.toString())
}
}
返回值是 Kotlin 字符串;C++ 创建 ArkTS 字符串后立即调用 libohoscontacts_symbols()->DisposeString 释放。Throwable 在桥内降级为错误 JSON——Kotlin/Native 异常跨语言边界会变成无信息崩溃。
4.3 C++ N-API 方法分发
napi_property_descriptor desc[] = {
{"contactsDispatch", nullptr, NapiContactsDispatch, nullptr, nullptr, nullptr,
static_cast<napi_property_attributes>(napi_default | napi_enumerable), nullptr}};
contactsDispatch 检查参数必须是字符串。N-API 只负责参数类型、字符串创建和释放,不实现业务语义。
4.4 CMake 和类型声明
add_library(ohoscontacts SHARED IMPORTED)
set_target_properties(ohoscontacts PROPERTIES
IMPORTED_LOCATION ${KN_SO}
IMPORTED_NO_SONAME TRUE)
add_library(contacts_napi SHARED napi_init.cpp)
target_link_libraries(contacts_napi PUBLIC
libace_napi.z.so
libhilog_ndk.z.so
ohoscontacts)
export const contactsDispatch: (request: string) => string;
模块名 contacts_napi、so 名和 index.d.ts 三处一致;POST_BUILD 把 KN so 和 libc++_shared.so 拷到 NAPI 旁边,$ORIGIN rpath 保证 dlopen 可解析。
第 5 阶段:ArkUI 宿主
5.1 快照加载与动作分发
import { contactsDispatch } from 'libcontacts_napi.so';
this.state = this.bridge.seed(); // aboutToAppear 播种
this.state = this.bridge.addNew(); // FAB
this.state = this.bridge.setField('email', v); // 输入框 onChange
this.state = this.bridge.save(); // 保存(错误文案随快照回来)
解码层是手写的 typed 辅助函数(asString / asNumber / asBool / decodeContact),全链路禁 any——解码层错了编译期就该炸,不是运行期。
5.2 浮层时序
两个浮层用 bindSheet 实现。上游 ContactListViewModel 在 Delete / Dismiss / Save 后 delay(300L) 等浮层收起动画播完再清 selectedContact / newContact;同步桥不能睡眠,因此协议里增加 ohos 专属 cleanup 动作,ArkTS 在 onDisappear 后延时发送:
private onDetailSheetClosed(): void {
if (this.state.isAddContactSheetOpen) {
return; // 详情正在切换为编辑表单,不能误关
}
this.dispatch(() => this.bridge.dismiss());
setTimeout(() => {
this.dispatch(() => this.bridge.cleanup());
}, 350);
}
5.3 头像链路和错误边界
选图走 photoAccessHelper.PhotoViewPicker,fileIo 读字节、Base64 过桥;渲染方向 Base64Helper().decodeSync() → image.createImageSource() → createPixelMap(),缓存于 Map 并整体重新赋值触发刷新。所有照片 API 用 try/catch 包裹,失败只打 hilog 不打断 UI;校验失败时输入框描边与占位符变错误色,下方小字原样显示 Kotlin 返回的错误文案。
第 6 阶段:示例与验证
6.1 工程结构
example/
├── nativeApp/src/ohosMain/kotlin/
│ ├── contacts/domain/ Contact / ContactDataSource / ContactValidator
│ ├── contacts/data/ InMemoryContactDataSource
│ ├── contacts/presentation/ ContactListState / Event / StateMachine
│ └── example/nativeapp/ Bridge.kt(@CName + MiniJson)
└── harmonyApp/entry/src/main/
├── cpp/ CMakeLists.txt / napi_init.cpp / types/
├── ets/ entryability / model / pages/Index.ets
└── resources/
6.2 构建、签名和安装
cd example
./gradlew :nativeApp:linkDebugSharedOhosArm64 :nativeApp:linkDebugSharedOhosX64
DevEco Studio 打开 example/harmonyApp,File > Project Structure > Signing Configs 勾选 Automatically generate signature 生成调试证书后 Run;命令行等价物:
hdc install -r entry/build/default/outputs/default/entry-default-signed.hap
hdc shell aa start -a EntryAbility -b org.terminator.ohos.contacts
build-profile.json5 仓库内只保留 "signingConfig": "default" 占位,签名材料(证书与密码)由 DevEco 写在本机 ~/.ohos/config/,不进入 AtomGit。
四、完整代码对照
4.1 整体架构
contacts/{domain,data,presentation}
│ Contact / Validator / contact.sq 语义 / 状态机
▼
Bridge.kt
│ @CName("ContactsDispatch") + MiniJson
▼
libcontacts_napi.so
│ contactsDispatch(request): string
▼
Index.ets
│ typed 解码 + 状态快照渲染
▼
ArkUI 真机页面
4.2 文件清单
| 文件 | 职责 |
|---|---|
example/settings.gradle.kts | 独立工程配置,eazytec 仓库在前 |
example/gradle.properties | JDK 21 路径 + KN 开关 |
nativeApp/build.gradle.kts | 双 ABI sharedLib + Copy 任务 |
.../contacts/domain/ContactValidator.kt | 逐字移植的校验器 |
.../contacts/data/InMemoryContactDataSource.kt | contact.sq 四条语义的内存实现 |
.../contacts/presentation/ContactListStateMachine.kt | moko-mvvm ViewModel 的同步 reducer |
.../example/nativeapp/Bridge.kt | C ABI、JSON 编解码 |
harmonyApp/.../cpp/napi_init.cpp | N-API 导出和参数检查 |
harmonyApp/.../ets/model/ContactsBridge.ets | typed 解码层 |
harmonyApp/.../ets/pages/Index.ets | ArkUI 页面 |
4.3 关键 API 对照
| 层次 | API | 作用 |
|---|---|---|
| KMP | ContactValidator.validateContact | 四字段校验,产出逐字错误文案 |
| KMP | InMemoryContactDataSource | 列表 / 最近添加 / 插入 / 删除 |
| KMP | ContactListStateMachine.onEvent | 事件驱动的状态迁移 |
| Native | ContactsDispatch | 请求进、完整状态快照出 |
| Native | libohoscontacts_symbols()->DisposeString | 释放 Kotlin 字符串 |
| N-API | contactsDispatch | 向 ArkTS 暴露唯一入口 |
| ArkTS | applyState | 快照整体赋值 + 浮层镜像同步 |
4.4 ArkTS 与 Kotlin 的边界
fun contactsDispatch(request: String): String // Kotlin:动作 → 快照
this.state = this.bridge.select(id); // ArkTS:快照 → 渲染
两者之间只传输字符串和 JSON,不传输 Kotlin 对象或未校验的动态结构。
五、关键决策说明
决策 1:根工程不动,独立 example/ 工程
上游 Compose 1.4.0 与社区鸿蒙 CMP 1.9.2 两个时代的 API 面对不齐;整体迁移会拖坏 Android / iOS。Gradle 与 Hvigor 分离的布局让鸿蒙构建完全不碰根工程。
决策 2:只有 nativeApp 出 sharedLib,双 ABI 都要
库模块出 klib、示例模块出 so,避免运行时被打包多份。真机是 arm64、DevEco 模拟器是 x86_64,HAP 只带单 ABI 会安装失败(错误码 9568347)。
决策 3:内存数据源复刻 SQL 语义
SQLDelight 无 ohos driver,但排序、限流、INSERT OR REPLACE、自增这四条语义是可对账的行为契约,逐条在内存实现里复刻,接口面 ContactDataSource 为将来接关系型存储预留。
决策 4:JSON 完整状态快照作为跨语言契约
一次动作进、一份快照出,ArkTS 是状态机的纯渲染器,不存在两端状态漂移的生存空间;以后字段变化只动序列化器,C 签名不动。
决策 5:ArkUI 作为验收宿主,cleanup 替代 delay(300L)
页面验证 OpenHarmony Native 链路;上游两处动画等待收敛为一个 ohos 专属事件,由 ArkTS 在浮层动画结束后发送,时序语义与上游一致。
六、测试与验证
6.1 测试环境
本次验证使用 macOS、JDK 21(Android Studio JBR)、DevEco Studio 5.1.0(18)、OpenHarmony ARM64 Native SDK 和华为真机(arm64-v8a,DevEco 自动签名)。
6.2 共享层全流程对照测试
链接 ohos 之前,同一份三层代码 + Bridge 先在 JVM 上跑完整用户旅程:seed → 空表单保存出四条逐字错误 → 填字段保存 → 详情 → 编辑保存(联系人爬回最近添加顶部)→ 删除 → cleanup,共 40 项断言全部通过,每个响应都经过严格 JSON 语法校验。这次对照测试抓出过两个真实缺陷(序列化键名被写成无效字符串语句导致 JSON 缺键;种子数据插入顺序与时间倒序方向相反),正是它存在的意义。
== 8. firstName ASC ordering ==
PASS contacts sorted by firstName ASC: [Alan, Dennis, Grace, Katherine, Linus, Margaret]
ALL PASSED
6.3 HAP 验证
Finished :entry:default@CompileArkTS
Finished :entry:default@PackageHap
BUILD SUCCESSFUL
entry-default-signed.hap(双 ABI,六个 so 三件套齐全)
nm: ContactsDispatch / libohoscontacts_symbols 双 ABI 可见
6.4 功能用例
- 首屏播种 6 位演示联系人,最近添加第一位是 Grace Hopper;
- 列表按
firstName ASC排序,标题显示My contacts (6); - 空表单点保存,四条错误文案逐字出现,联系人数不变;
- 填合法数据保存,浮层收起,列表与最近添加顶部同时出现新联系人;
- 详情浮层 Edit 进入编辑,改完保存后该联系人爬回最近添加顶部;
- 详情浮层 Delete,联系人从两处消失;
- 新增时选头像,照片以 Base64 过桥,行内与浮层都能显示。
6.5 验证结论
Kotlin/Native 双 ABI 动态库、C ABI、N-API、ArkUI 页面和签名 HAP 已完成链路验证。应用在华为真机上成功安装并进入前台,页面展示了真实的三层模型行为:排序、限流、逐字校验文案、浮层状态机和头像链路。
七、运行效果
7.1 真机截图

7.2 命令速查
git clone https://atomgit.com/oh-tpc/compose-multiplatform-AnimatedImage.git
cd compose-multiplatform-AnimatedImage/example
./gradlew :nativeApp:linkDebugSharedOhosArm64 :nativeApp:linkDebugSharedOhosX64
# DevEco Studio 打开 example/harmonyApp,配置自动签名后 Run,或:
hdc install -r entry/build/default/outputs/default/entry-default-signed.hap
hdc shell aa start -a EntryAbility -b org.terminator.ohos.contacts
八、遗留问题与改进方向
8.1 踩坑复盘
- 只改 ArkTS 页面不算 KMP 适配,必须真正编译
ohosArm64动态库; - N-API 不负责业务判断,C++ 只做类型检查、字符串转换和释放;
- Native 返回串交给 N-API 后必须调用
DisposeString,不能free; - Kit 命名随 SDK 版本变化:SDK 5.1.0(18) 中
photoAccessHelper由@kit.MediaLibraryKit导出,文件 API 是fileIo而不是fs,以$DEVECO_SDK_HOME下@kit.*.d.ts导出面为准; - Kotlin 里字符串字面量写成独立语句会被编译器当死代码消除,序列化键名必须走
append("...")调用——JVM 对照测试在链接前抓住了这个 JSON 缺键缺陷; - 签名材料只存在本机
~/.ohos/config/,仓库内保留占位配置; - 浮层内容收起中途闪没,是
cleanup发早了,必须等动画结束。
8.2 已知问题
- 内存数据源不跨进程重启持久化,杀进程重开回到种子数据;
- 照片以 Base64 存内存不落文件系统,大图会随快照反复过桥;
firstName排序用 Kotlin 字符串比较,ASCII 场景与 SQLite BINARY collation 一致,多语种名字会有差异;- 无搜索——上游也没有,补了反而制造功能面对不齐的对账噪音;
- CAdapter 链接期有一条
api.cppclang 警告,是鸿蒙 Kotlin 生成的胶水代码,非业务代码。
8.3 未来优化方向
- 用关系型存储或 Preferences 实现
ContactDataSource的持久化变体,接口面已预留; - 照片先落盘再存引用,Base64 只走选择器到磁盘一段;
- 等社区 CMP 工具链与上游版本对齐后,退役 ArkTS 渲染层,
shared/commonMain的 Compose UI 直接上岗; - 在 CI 中加入 Native 链接、ELF 符号检查和 HAP 构建;
- 补充不同设备 ABI、API 版本的验收矩阵。
九、总结
9.1 核心难点回顾
KMP commonMain 三层业务模型
→ ohosArm64/ohosX64 Kotlin/Native
→ C ABI(ContactsDispatch)
→ C++ N-API(libcontacts_napi.so)
→ ArkTS typed 解码
→ ArkUI 真机页面
9.2 封装层次
- KMP 层:提供 Contact、ContactValidator、数据源语义和 ContactListState 状态机;
- Native 层:生成双 ABI 动态库并通过唯一 C ABI 输出完整状态快照;
- N-API 层:完成参数检查、字符串转换和 Native 内存释放;
- ArkTS 层:管理快照解码、浮层时序、头像缓存和错误展示;
- DevEco 层:完成 CMake、HAP、签名、安装和真机运行。
9.3 三条经验
- 先让三层模型在 JVM 全流程对照测试通过,再链接 ohos 动态库——序列化缺陷在链接前就该被抓出来;
- 用 JSON 完整状态快照和少量 C ABI 代替跨语言对象传递,并明确字符串释放责任;
- 分别记录共享层测试、双 ABI 链接、HAP 构建和真机运行,避免把编译成功误认为设备链路可用。
9.4 适配成果
当前适配已完成:
- domain / data / presentation 三层的
ohosArm64+ohosX64双 ABI 动态库; - 校验器逐字对齐、
contact.sq四条语义逐条复刻、事件面只多一个可撤掉的cleanup; - JVM 对照测试 40 项断言全部通过的验收基线;
- DevEco Stage ArkUI 验收页面(列表、最近添加、双浮层、校验、头像);
- HAP 构建、自动签名、设备安装和华为真机验证;
- AtomGit 项目文档、OpenHarmony README、中文适配说明和本地效果图。
适配过程 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)