开源鸿蒙平台 KMP/CMP 三方库「原子化服务」适配全流程
本文记录 KMP 原子化服务库 kmp-atomic-service 接入 OpenHarmony 的完整过程,覆盖工程盘点、
ohosArm64原生桥、C ABI/N-API 桥接、纯 ArkTS 原子化服务宿主、Form Kit 桌面卡片、HAP 签名和真机验收。本次适配把交付链拆成两部分:Kotlin/Native 生成 ARM64 动态库,并保留供普通 OpenHarmony 宿主使用的 C ABI/N-API 桥接;受
bundleType=atomicService限制的原子化服务 HAP 则使用纯 ArkTS host adapter,按同一份 JSON 字段和归约语义完成免安装入口、Want 路由、服务卡片和 Form Kit 展示。两条链路职责不同,不能把契约一致性误写成原子化服务 HAP 直接加载 Kotlin/Native。
项目地址: AtomGit/oh-tpc/kmp-atomic-service
开发工具: 华为云码道
一、背景
1.1 为什么做开源鸿蒙平台 KMP/CMP 适配
kmp-atomic-service 是一个面向 OpenHarmony 原子化服务(元服务)的 Kotlin Multiplatform/Compose Multiplatform 三方库。它的核心价值不在某一个服务页面,而在于服务描述、入口路由、免安装交付模式、动作归约和 JSON 契约可以在共享代码中组织,然后交给不同平台的宿主层使用。
OpenHarmony 应用不能直接把 JVM 或 Android Compose 产物安装到 ARM64 设备。如果只把页面重新写成 ArkTS,虽然可以画出几个卡片,却无法验证 Kotlin 共享模型是否真的在鸿蒙设备上运行,也容易在路由分发和状态归约上出现第二套实现。适配需要解决下面几个问题:
| 障碍 | 具体问题 |
|---|---|
| 目标缺失 | 示例工程默认没有 ohosArm64(),无法生成 OpenHarmony KLIB 和 ARM64 动态库。 |
| 工具链不一致 | Kotlin/Native、OpenHarmony LLVM、Native SDK 和 Gradle 插件必须使用匹配版本。 |
| 原子服务硬约束 | DevEco 禁止 bundleType=atomicService 的 HAP 使用 externalNativeOptions,原子服务包内不能直接链接 Kotlin/Native 动态库。 |
| 语言边界不同 | ArkTS 不能直接持有 Kotlin 对象,必须经过 C ABI、C++ N-API 和 JSON。 |
| 入口模型不同 | Compose 的页面导航需要映射为免安装 Want 参数(serviceRoute、serviceId)和 ArkUI 页面状态。 |
| 消费验证不足 | 根工程编译通过,不代表独立的 example 工程能链接动态库和打包原子化服务 HAP。 |
| 交付链路复杂 | 原生库、CMake、HAP、签名、设备安装和 ARM64 ELF 依赖都需要单独检查。 |
其中"原子服务硬约束"是本项目与普通 KMP 适配最大的差异:真正的原子化服务 HAP 只能使用纯 ArkTS 宿主消费共享契约;若要做 Native 运行时闭环,需要把保留的桥接层放进独立的普通宿主验证。
因此,本次实现把适配边界放在三个地方:Kotlin/Native 目标配置、独立原生桥接验证层和原子化服务 ArkUI 交互层。Kotlin 共享模块定义服务模型、路由规则和归约逻辑;原子化服务 HAP 中的 ArkTS adapter 按相同契约镜像这些规则,负责页面状态、模板渲染和动作响应。
1.2 库提供的能力
kmp-atomic-service 的公共能力包括服务目录、路由解析、请求分发、不可变状态归约和 JSON 序列化。由于原子化服务 HAP 不能链接 Native 动态库,示例在 ArkTS host adapter 中维护一份与 KMP JSON 契约一致的映射,并选择最容易在设备上验证、又能体现免安装交付差异的三个场景:
- 每日专注(
/focus): Medium 尺寸服务卡片,展示完成度进度和"完成"动作,体现动作归约; - 配送进度(
/delivery): Large 尺寸服务卡片,展示订单状态流转和"稍后提醒"动作; - 设备概览(
/device): Small 尺寸服务卡片,展示模拟设备读数和刷新语义。
页面另外提供刷新次数(revision)、完成/暂缓/刷新三类动作、Want 路由入口和七项共享自检。所有数据本地确定性生成,不需要网络连接。
1.3 实现适配
| 维度 | 要求 |
|---|---|
| 代码复用 | 普通宿主链路复用 Kotlin 服务模型;原子服务 HAP 按相同 JSON 契约镜像路由、归约和自检语义。 |
| 平台目标 | 为示例模块增加 ohosArm64(),生成 libatomic_service.so。 |
| 桥接稳定 | 使用少量 C ABI 函数和 JSON,避免把 Kotlin 对象直接暴露给 ArkTS。 |
| 原子服务合规 | 原子化服务 HAP 使用纯 ArkTS host adapter,Native 桥接保留给独立普通宿主。 |
| UI 完整 | 页面要能切换三个模板、执行动作、刷新状态、查看自检结果并支持 Form Kit。 |
| 可测试 | JVM 测试、Native 链接、ArkUI/HAP 构建和真机交互分别验收。 |
| 签名安全 | 证书、profile 和密钥文件不随文章交付,发布前应在独立工程中配置并妥善保管。 |
| 仓库规范 | README、文章、效果图和项目地址统一使用 AtomGit。 |
说明: 本次交付提供源码适配和独立 OpenHarmony 示例,不新增一个替代 kmp-atomic-service 公共 API 的 ArkTS 服务库。这样可以保持上游 API 和平台实现边界清晰。
二、实现路线图
第 1 阶段:项目初始化 ── 盘点 atomic-service 模块、公共能力和示例边界
第 2 阶段:目标与依赖打通 ── 加入 ohosArm64、仓库和独立消费工程
第 3 阶段:状态与序列化 ── 建立 AtomicServiceModel、路由规则和 JSON 契约
第 4 阶段:原生桥接 ── Kotlin/Native C ABI、C++ N-API 和内存释放
第 5 阶段:能力封装 ── 纯 ArkTS 原子服务宿主、Want 路由、Form Kit 和自检页
第 6 阶段:示例与验证 ── 原子化服务 HAP 构建、签名、设备安装和真机验收
每个阶段都使用真实产物作为下一阶段输入:atomic-service 先验证服务模型,example/shared 再提供示例门面,example/nativeApp 把模型链接为 ARM64 动态库,example/ohosApp 通过纯 ArkTS host adapter 消费同一份 JSON 契约,最后由 DevEco 负责原子化服务 HAP 打包和签名。
三、逐步实现过程
第 1 阶段:项目初始化
1.1 盘点原库源码和公共 API
根工程保留 kmp-atomic-service 的公共模块和 Compose 边界。OpenHarmony 示例放在 example/ 下,避免把 DevEco 工具链、ArkTS 文件和证书配置混入库模块:
atomic-service/ 公共 KMP 原子化服务模型、路由和 JSON 边界
vico/ 参考工程兼容聚合层和 Compose 状态边界
sample/ android/desktop/shared/web/ios 消费入口
example/shared/ OpenHarmony 示例共享门面和 JVM 测试
example/nativeApp/ ohosArm64 Kotlin/Native 动态库
example/ohosApp/ DevEco Stage 原子化服务应用和 ArkUI 页面
scripts/ 构建、签名工程和 HAP 辅助脚本
docs/openharmony/ 验收记录和效果图
example 是一个独立的 Gradle 工程。它不会把 DevEco 工程当成 Kotlin 子模块,因此可以分别执行 Gradle 和 Hvigor,也可以将 ohosApp 复制到另一个目录完成手动签名。
1.2 固定工具链和版本矩阵
本次示例使用 Kotlin Multiplatform 2.2.21-1.0.0、Gradle Wrapper、JDK 21、OpenHarmony API 20 ARM64 Native SDK 和 arm64-v8a HAP ABI。根 kmp-atomic-service 模块继续保留原有平台矩阵,OpenHarmony 的 Kotlin/Native 依赖由 example/settings.gradle.kts 中的社区 Maven 仓库解析。
example/ohosApp
↓ 纯 ArkTS host adapter(原子化服务 HAP)
普通 OpenHarmony 宿主(可选)
↓ CMake + N-API
example/nativeApp/libatomic_service.so
↓ project dependency
example/shared
↓ shared Kotlin atomic-service model
atomic-service move/reduce semantics
这里的 shared 不是 ArkUI 页面数据的临时缓存,而是明确的跨平台模型层。它复用 atomic-service 的 AtomicServiceModel、AtomicServiceEngine 和 runChecks();nativeApp 负责把模型转换成 C ABI 可返回的 JSON,ohosApp 则按同一契约生成页面数据、展示状态并响应动作。
1.3 创建适配示例目录
参考工程的主要交互是编辑两段文本并比较差异。kmp-atomic-service 的核心能力是原子化服务模型,如果沿用文本编辑器布局,会掩盖免安装入口、路由分发和动作归约能力。因此页面选择了一个独立的原子化服务工作台:顶部是模板切换,中间是服务卡片和动作按钮,底部是自检入口和可选的 Form Kit 管理。
页面不会复用上一个适配项目的界面。三个服务模板使用相同的数据契约,但每种模板的卡片尺寸、指标和动作单独渲染,确保用户可以直观看到库能力在不同交付形态中的表现。
第 2 阶段:目标与依赖打通
2.1 加入 ohosArm64 目标
共享模块保留 JVM 测试,同时增加 ohosArm64():
plugins {
kotlin("multiplatform")
}
kotlin {
jvm()
ohosArm64()
sourceSets {
commonTest.dependencies {
implementation(kotlin("test"))
}
}
}
JVM 目标让服务模型和路由规则可以在不连接设备的情况下运行测试,ohosArm64 则让同一份 commonMain 代码进入 Kotlin/Native 动态库。
2.2 focused build 的作用
Native 模块构建一个 shared library,并用 linker map 限制导出的符号:
kotlin {
ohosArm64 {
binaries.sharedLib {
baseName = "atomic_service"
linkerOpts(
"--entry=0",
"--version-script=${project.file("src/ohosArm64Main/linker/shared-library.map")}",
)
linkerOpts("-lace_napi.z", "-luv", "-lhilog_ndk.z")
}
}
}
OpenHarmony 动态库不是直接由 ArkUI 加载的 JavaScript 包;普通宿主需要通过 CMake 链接它。符号表只导出桥接入口,避免 Kotlin 运行时符号和宿主加载的其他库冲突。当前原子化服务 HAP 不启用这份 CMake 配置。
2.3 配置仓库和独立消费工程
example/settings.gradle.kts 同时声明 OpenHarmony 社区 Maven、Maven Central 和 Gradle Plugin Portal,并把根目录的 atomic-service 映射为独立示例工程中的项目依赖:
pluginManagement {
repositories {
maven("https://maven.eazytec-cloud.com/nexus/repository/maven-public/")
mavenCentral()
gradlePluginPortal()
}
}
include("atomic-service")
project(":atomic-service").projectDir = file("../atomic-service")
这样既能验证根模块本身,也能验证一个独立消费者能否解析插件、引用公共模型并生成 OpenHarmony 产物。
2.4 通过构建产物消费共享库
example/nativeApp 依赖 :shared,并通过 prepareOhos 将 Kotlin/Native 产物复制到 DevEco 工程:
val prepareOhos by tasks.registering(Copy::class) {
dependsOn("linkDebugSharedOhosArm64")
from(layout.buildDirectory.dir("bin/ohosArm64/debugShared")) {
include("libatomic_service.so")
into("libs/arm64-v8a")
}
from(layout.buildDirectory.dir("bin/ohosArm64/debugShared")) {
include("libatomic_service_api.h")
into("src/main/cpp/include")
}
into(rootProject.layout.projectDirectory.dir("ohosApp/entry"))
}
动态库和生成头文件属于构建产物,.gitignore 会排除它们。仓库保留复制任务,这样每台开发机都可以从源码重新生成与本机工具链匹配的文件。
第 3 阶段:状态与序列化
3.1 为什么需要统一 JSON 契约
ArkTS、C++ 和 Kotlin/Native 之间不能直接共享 Kotlin 对象。为了让边界稳定,示例使用一个 JSON 模型描述当前服务和动作状态:
AtomicServiceEngine
↓ AtomicServiceModel
Kotlin/Native JSON encoder
↓ UTF-8 buffer
C++ N-API string / ArkTS host adapter
↓
ArkUI service card and interaction state
普通宿主路径中的服务字段、动作列表、进度和刷新次数由 Kotlin 模型生成,ArkTS 不需要了解 Kotlin data class 的内部结构。原子化服务 HAP 不能走这条 Native 路径,因此由 host adapter 生成相同 JSON 形状;字段可以扩展,而 C ABI 入口仍保持稳定。
3.2 AtomicServiceModel 设计
页面需要的不只是服务标题,还需要副标题、进度、指标、能力列表和动作:
data class AtomicServiceModel(
val id: String,
val title: String,
val subtitle: String,
val body: String,
val updatedAt: String,
val progress: Int,
val accent: String,
val dimension: AtomicServiceDimension,
val entryRoute: String,
val deliveryMode: AtomicServiceDeliveryMode,
val status: AtomicServiceStatus,
val capabilities: List<String>,
val metrics: List<AtomicServiceMetric>,
val actions: List<AtomicServiceAction>,
)
deliveryMode 在 Kotlin 中统一判断为 INSTALLATION_FREE。ArkTS host adapter 保持相同字段,动作按钮由 actions 驱动。updatedAt 用来验证点击和刷新确实执行了契约约定的归约,而不是只改变静态文案。
3.3 引擎和三类动作语义
AtomicServiceEngine.reduce 通过动作 ID 选择归约规则:
public fun reduce(model: AtomicServiceModel, actionId: String, refresh: Int): AtomicServiceModel {
require(refresh >= 0) { "Revision must be non-negative" }
if (actionId.isEmpty()) return model
require(model.actions.any { it.id == actionId }) { "Action not supported by atomic service" }
return when (actionId) {
"refresh" -> service(indexOf(model.id), refresh + 1)
"complete" -> model.copy(progress = 100, body = "今日计划已完成", ...)
"snooze" -> model.copy(body = "已暂缓提醒(演示)", ...)
else -> model
}
}
刷新使用递增 revision 生成下一份确定性状态,完成动作把进度置为 100,暂缓动作只更新提示文本。三个模板分别持有独立的 revision,因此在配送模板上刷新不会修改专注模板的状态。
3.4 错误响应和内存限制
Native 层返回的服务对象包含以下字段:
{
"id": "daily-focus",
"title": "每日专注",
"subtitle": "免安装打开 · 把今天的小目标完成",
"body": "今日计划已完成 64%",
"updatedAt": "第 0 次刷新",
"progress": 64,
"accent": "#0A84FF",
"dimension": "MEDIUM",
"entryRoute": "/focus",
"deliveryMode": "INSTALLATION_FREE",
"status": "AVAILABLE",
"capabilities": ["deep-link", "desktop-form", "share"],
"metrics": [{ "label": "完成度", "value": "64%", "trend": "+7%" }],
"actions": [{ "id": "complete", "label": "完成" }, { "id": "refresh", "label": "刷新" }]
}
Kotlin/Native 计算异常会在桥接层转换为 ArkTS 可捕获的调用异常。C++ 层创建 ArkTS 字符串后立即调用 AtomicServiceFree,避免 Native 堆内存一直由 JavaScript 持有。索引、revision 和动作参数在 N-API 层检查整数类型,避免无效输入直接进入归约模型。
第 4 阶段:原生桥接(技术难点)
4.1 问题:Kotlin/Native 对象不能直接交给 ArkTS
Kotlin/Native 的 AtomicServiceModel、List 和异常对象属于 Kotlin 运行时对象。ArkTS 通过 N-API 接收到的是 JavaScript 值,C++ 不能把 Kotlin 对象地址直接当成 JavaScript 对象使用。
最终采用三层桥接:
ArkTS
│ JSON string
▼
C++ N-API entry
│ const char*
▼
Kotlin/Native C ABI
│ shared atomic-service model
▼
UTF-8 JSON + explicit free
4.2 方案对比
| 方案 | 优点 | 缺点 | 选用 |
|---|---|---|---|
| 直接导出 Kotlin 对象 | 代码少 | ABI、生命周期和类型不可控 | ❌ |
| 导出基础字段数组 | 不需要 JSON | 字段扩展和错误处理困难 | ❌ |
| C ABI + JSON | 边界清晰、易扩展、易调试 | 有一次序列化开销 | ✅ |
| 在 ArkTS 重写服务模型 | 页面调用简单 | 逻辑重复,结果可能与 Kotlin 不一致 | ❌ |
4.3 Kotlin/Native 导出函数
NativeBridge.kt 导出四个函数:
@CName("AtomicServiceCatalog")
fun catalogNative(): CPointer<ByteVar> = ...
@CName("AtomicServiceGet")
fun serviceNative(index: Int, revision: Int, action: Int): CPointer<ByteVar> = ...
@CName("AtomicServiceRunChecks")
fun checksNative(): CPointer<ByteVar> = ...
@CName("AtomicServiceFree")
fun freeNative(pointer: CPointer<ByteVar>?) { ... }
返回值使用 nativeHeap.allocArray<ByteVar> 分配,并以 0 结尾,满足 C 字符串约定。所有返回字符串都必须由 AtomicServiceFree 释放。
4.4 C++ N-API 方法分发
C++ 模块注册的 ArkTS 方法:
napi_property_descriptor methods[] = {
{"getCatalog", nullptr, Catalog, nullptr, nullptr, nullptr,
napi_default, nullptr},
{"getService", nullptr, Service, nullptr, nullptr, nullptr,
napi_default, nullptr},
{"getCard", nullptr, Service, nullptr, nullptr, nullptr,
napi_default, nullptr},
{"runChecks", nullptr, RunChecks, nullptr, nullptr, nullptr,
napi_default, nullptr},
};
getService 检查参数数量和整数类型;目录和自检方法都遵循"调用 Native → 生成 ArkTS 字符串 → 释放 Native 缓冲区"的顺序。CMake 将 Kotlin/Native 动态库作为 imported library:
add_library(atomic_service SHARED IMPORTED)
set_target_properties(atomic_service PROPERTIES
IMPORTED_LOCATION
"${CMAKE_CURRENT_SOURCE_DIR}/../../../libs/arm64-v8a/libatomic_service.so")
add_library(entry SHARED napi_init.cpp)
target_include_directories(entry PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/include")
target_link_libraries(entry PRIVATE atomic_service libace_napi.z.so)
4.5 N-API 生命周期
ArkTS getService(index, revision, action)
│
▼
ReadInt32 + argument check
│
▼
AtomicServiceGet(index, revision, action)
│
▼
napi_create_string_utf8(...)
│
▼
AtomicServiceFree(nativeBuffer)
│
▼
return JS string
C++ 负责把 Native 缓冲区转换成 ArkTS 字符串,并在返回前释放缓冲区。页面调用者不需要知道 Kotlin/Native 的堆实现,也不会因为重复刷新或自检而积累 Native 内存。
第 5 阶段:能力封装
5.1 三个服务模板封装
共享层先把原子化服务概念封装成三个可验证模型:
| 模型 | 页面标题 | 表现的能力 |
|---|---|---|
daily-focus | 每日专注 | Medium 卡片、完成度指标、完成/刷新动作 |
package-delivery | 配送进度 | Large 卡片、订单状态流转、稍后提醒动作 |
device-status | 设备概览 | Small 卡片、模拟设备读数、刷新语义 |
模型还提供 progress 和 updatedAt,让页面可以在不理解 Kotlin 业务代码的情况下展示当前状态和验收结果。
5.2 双宿主架构:原子服务约束下的桥接方案
DevEco 对 bundleType=atomicService 有一个硬约束:原子化服务 HAP 不支持 externalNativeOptions,不能在包内链接 Kotlin/Native .so。工程因此分成两条验证链:
公共 KMP 模块 → JVM 测试
→ example/nativeApp → ohosArm64 → C ABI/N-API 桥接源码
公共 JSON 契约 → example/ohosApp → 纯 ArkTS host adapter → atomicService HAP
AtomicServiceClient.ets 是纯 ArkTS 的 host adapter,它实现的 readService(index, revision, action) 与 Native 桥接的 AtomicServiceGet 消费同一份 JSON 形状和归约语义:
export function readService(index: number, revision: number, action: number = 0): CardModel {
if (!Number.isInteger(index) || index < 0 || index >= CATALOG.length) {
throw new Error('Unknown atomic service index');
}
// ... 与 Kotlin AtomicServiceEngine 相同的 revision/action 归约
}
这不是把普通应用伪装成原子服务,而是遵守系统交付边界:原子服务 HAP 使用 ArkTS 运行时;Native bridge 验证同一份 KMP 模型可编译、可导出,并为普通宿主保留 N-API 消费入口。未来 SDK 如果允许原子服务使用 Native,可以替换 host adapter,而不需要改公共 API。
5.3 ArkTS 页面分区
Index.ets 只有一个 Stage 页面,按垂直方向分成:
标题区 ATOMIC SERVICE / 原子化服务 / OHOS · KMP
模板选择区 每日专注 / 配送进度 / 设备概览
状态区 服务卡片、进度、指标和刷新次数
操作区 完成 / 稍后提醒 / 刷新
自检入口 查看七项共享自检结果
Form Kit 入口 添加卡片、尺寸变化、动作回调
这个布局与参考 diff 工程的双文本编辑器完全不同,更适合展示原子化服务库。三个模板共用状态区和操作区,但每种卡片单独渲染指标和动作。
5.4 Want 入口和 Form Kit
EntryAbility.ets 接收免安装 Want 参数并映射到共享路由:
- Want 携带
serviceRoute(如/focus)和serviceId,入口按resolveRoute()的语义选择模板; - 页面状态和 Form 卡片使用同一份
CardBindingData扁平化字段; EntryFormAbility.ets作为 Form Kit 扩展,桌面卡片的添加、尺寸变化、刷新和动作回调都走共享契约。
需要说明的是,第三方应用不能静默把内容固定到桌面。Form Kit 入口由系统管理页和用户确认完成,它与免安装元服务入口是两个不同的系统流程。
第 6 阶段:示例与验证
6.1 example 工程结构
example/
├── build.gradle.kts
├── settings.gradle.kts
├── shared/
│ ├── build.gradle.kts
│ └── src/commonMain + src/commonTest/
├── nativeApp/
│ ├── build.gradle.kts
│ └── src/ohosArm64Main/
│ ├── kotlin/.../NativeBridge.kt
│ └── linker/shared-library.map
└── ohosApp/
└── entry/
├── src/main/ets/pages/Index.ets
├── src/main/ets/atomicservice/AtomicServiceClient.ets
└── src/main/module.json5
三个模块分别承担共享服务门面、Kotlin/Native 动态库和 DevEco Stage 原子化服务页面职责。
6.2 原子化服务工程配置
AppScope/app.json5 声明原子化服务包类型:
"app": {
"bundleName": "com.atomicservice.6917617026646679084",
"bundleType": "atomicService",
"versionName": "1.0.0",
}
entry/src/main/module.json5 声明免安装交付:
"deliveryWithInstall": false,
"installationFree": true
构建时必须看到 bundleType=atomicService 且不再报 Atomic service development does not support Native development,即入口模块不配置 externalNativeOptions。
6.3 Native 动态库准备
先在仓库根目录执行:
export JAVA_HOME="/path/to/jdk-21"
./scripts/build-openharmony.sh
或者只执行示例任务:
(cd example && ./gradlew :shared:jvmTest :nativeApp:prepareOhos)
成功后应存在:
example/ohosApp/entry/libs/arm64-v8a/libatomic_service.so
example/ohosApp/entry/src/main/cpp/include/libatomic_service_api.h
6.4 构建与安装
构建可安装 HAP 前,建议复制一份不含构建缓存、证书和密钥文件的 DevEco 工程:
python3 scripts/prepare-signing-project.py "$HOME/atomic_service_ohos_signing"
在 DevEco Studio 中手动配置 API 20 ARM64 签名后构建 HAP:
./scripts/build-hap.sh "$HOME/atomic_service_ohos_signing"
安装并启动:
hdc list targets
hdc -t <target-id> install -r \
entry/build/default/outputs/default/entry-default-signed.hap
hdc -t <target-id> shell aa start \
-a EntryAbility -b com.atomicservice.6917617026646679084
最后按三个模板、动作归约、刷新次数、Want 路由和七项自检验收页面。
四、完整代码对照
4.1 整体架构
普通宿主桥接链(当前原子服务 HAP 不启用):
AtomicServiceEngine
↓ AtomicServiceModel / reduce / runChecks / JSON
NativeBridge.kt
↓ AtomicServiceCatalog / Get / RunChecks / Free
C ABI → napi_init.cpp → ArkTS JSON
原子化服务 HAP 链:
公共 JSON 契约
↓ 字段和归约语义保持一致
AtomicServiceClient.ets
↓ readService / CardModel / checkNative
Index.ets
↓ Want / ArkUI / Form Kit
installation-free atomicService HAP
4.2 文件清单
| 文件 | 作用 |
|---|---|
atomic-service/src/commonMain/.../AtomicService.kt | 服务模型、维度、交付模式、状态、指标和动作。 |
atomic-service/src/commonMain/.../AtomicServiceEngine.kt | 目录、路由解析、分发和不可变归约。 |
atomic-service/src/commonMain/.../AtomicServiceJson.kt | 跨 Native、C++、ArkTS 的 JSON 契约。 |
atomic-service/src/commonTest/.../AtomicServiceEngineTest.kt | JVM 侧路由、归约和自检测试。 |
example/shared/src/commonMain/.../AtomicServiceExamples.kt | 示例门面和七项验收检查。 |
example/nativeApp/.../NativeBridge.kt | Kotlin/Native C ABI、JSON 和内存释放。 |
example/nativeApp/.../shared-library.map | 限制导出的 Native 符号。 |
example/ohosApp/entry/src/main/cpp/napi_init.cpp | C++ N-API 方法和模块注册。 |
example/ohosApp/entry/src/main/cpp/CMakeLists.txt | imported library 和 N-API 链接。 |
example/ohosApp/entry/src/main/ets/pages/Index.ets | ArkUI 状态、三个模板、动作和自检页。 |
example/ohosApp/entry/src/main/ets/atomicservice/AtomicServiceClient.ets | 纯 ArkTS host adapter 和 CardBindingData。 |
example/ohosApp/entry/src/main/ets/formability/EntryFormAbility.ets | Form Kit 卡片刷新和动作回调。 |
scripts/build-openharmony.sh | 根测试、示例测试和 Native 准备。 |
scripts/prepare-signing-project.py | 复制无签名材料的 DevEco 工程。 |
scripts/build-hap.sh | 调用 Hvigor 构建 HAP。 |
docs/openharmony/images/cmp-atomic-service-openharmony.jpg | 运行效果图。 |
4.3 关键 API 对照
AtomicServiceCatalog → 返回三个确定性服务模板
AtomicServiceGet → 按索引、revision 和动作归约出下一份服务状态
AtomicServiceRunChecks → 返回七项共享验收检查
AtomicServiceFree → 释放 Native 字符串
ArkTS 侧只依赖少量方法,避免把 Native 指针、Kotlin 对象和内存管理细节带入页面逻辑。
4.4 ArkTS 与 TypeScript / Kotlin 的语法差异(本次实际踩到的)
| 能力 | Kotlin/Native | ArkUI |
|---|---|---|
| 服务模板 | 生成 AtomicServiceModel | 使用 Column、Row 和卡片容器渲染 |
| 动作归约 | reduce(model, actionId, revision) | 原子服务 HAP 由 host adapter 执行镜像归约 |
| 刷新语义 | refresh 递增 revision | 完成和刷新按钮调用相同 API |
| 交付模式 | INSTALLATION_FREE 字段 | 免安装入口由 Want 和 module.json5 承载 |
| 入口路由 | resolveRoute(route) | Want 参数映射 serviceRoute/serviceId |
| 状态刷新 | toJson() | 普通宿主解析 Native JSON;原子服务 HAP 读取 host adapter 返回值 |
| 桌面卡片 | 不持有 FormKit 对象 | CardBindingData 扁平化 + @LocalStorageProp |
五、关键决策说明
决策 1:把 ohosArm64 加入示例的公共构建约定
示例模块加入 ohosArm64(),让同一份共享服务模型进入 Kotlin/Native 动态库。kmp-atomic-service 的 Compose 状态边界和 ArkUI Stage 的页面生命周期不同,因此 Kotlin 负责跨平台服务模型和归约规则,ArkUI 负责 OpenHarmony 原生卡片和动作输入;原子化服务 HAP 再通过 host adapter 保持契约一致。
决策 2:独立消费者必须通过构建产物消费
example/shared、example/nativeApp 和 example/ohosApp 按独立工程组织,先验证共享模型,再准备动态库,最后由 DevEco 打包。这样可以分别检查 Gradle 变体、KLIB、C ABI/N-API 源码边界和 HAP 链路,避免根工程通过但下游无法消费。
决策 3:JSON 作为跨语言数据契约
JSON 同时约束 Kotlin/Native 普通宿主链和纯 ArkTS 原子服务链。普通宿主由 AtomicServiceEngine 生成 JSON,原子化服务 HAP 的 host adapter 生成相同字段形状;这让两条受平台约束而分离的运行链仍可逐字段对照。
决策 4:桥接层只开放四个 C ABI 入口
示例页面字段会随交互能力增加而变化,JSON 可以向后兼容新增字段。C ABI 只保留服务索引、revision、动作索引和字符串返回,不需要为每一个 UI 属性扩展 N-API 方法签名。
决策 5:页面按照库能力重新设计
参考 diff 工程的文本编辑交互不适合原子化服务库,工作台把三个模板放在同一个选择区,用户可以在一次运行中对比三种卡片尺寸、完成/暂缓/刷新动作和 Want 路由入口。新的页面主题、配色和卡片布局也与上一个示例区分开。
决策 6:把库验证、设备验证和交付边界分开
JVM 测试验证共享规则,Kotlin/Native 编译验证 OpenHarmony 目标和 C ABI 导出,预留的 N-API 层供普通宿主集成;Hvigor 和真机验证纯 ArkTS 原子化服务 HAP。DevEco 禁止该 HAP 使用 externalNativeOptions,因此两条链路只共享契约和语义,互不替代。证书、profile 和密钥属于开发机材料,不应提交到 AtomGit。
六、测试与验证
6.1 测试环境
本次真机验收使用:
| 项目 | 值 |
|---|---|
| 设备 | HarmonyOS 真机(ARM64),同时使用系统卡片管理验证 Form Kit |
| ABI | ARM64 |
| DevEco | DevEco Studio 6 系列 |
| HAP 包名 | com.atomicservice.6917617026646679084 |
| 入口 | EntryAbility |
| 示例版本 | 1.0.0 |
6.2 静态检查与单元测试
共享测试覆盖:
./gradlew :atomic-service:jvmTest :sample:shared:jvmTest
(cd example && ./gradlew :shared:jvmTest)
Native 动态库准备任务为:
(cd example && ./gradlew :nativeApp:prepareOhos)
检查点包括三个模板都能归约状态、所有模板免安装交付、每个服务都有动作、入口路由唯一、进度有边界、路由分发正确以及动作归约不可变。
6.3 原生桥接和 HAP 验证
DevEco 构建阶段需要确认:
- 原子化服务 HAP 不启用
externalNativeOptions; - ArkTS host adapter 能生成与 KMP 相同的服务 JSON 形状;
bundleType=atomicService构建不再报 Native 开发不支持错误;- 普通宿主集成时,CMake imported library 路径指向
libatomic_service.so,并由独立宿主生成libentry.so; - 签名由当前开发机的 DevEco 配置完成。
构建命令为:
DEVECO=/Applications/DevEco-Studio.app/Contents
export PATH="$DEVECO/tools/node/bin:$DEVECO/tools/ohpm/bin:$PATH"
export DEVECO_SDK_HOME="$DEVECO/sdk"
cd example/ohosApp
"$DEVECO/tools/ohpm/bin/ohpm" install --all
"$DEVECO/tools/hvigor/bin/hvigorw" \
--mode module \
-p module=entry@default \
-p product=default \
-p buildMode=debug \
assembleHap --no-daemon
Native 动态库还可以使用 SDK 符号表做强依赖检查:
python3 scripts/check-native-deps.py \
/Applications/DevEco-Studio.app/Contents/sdk/default/openharmony/native \
example/ohosApp/entry/libs/arm64-v8a
验证结果:
atomic-service JVM tests: BUILD SUCCESSFUL
example shared/native prepare: BUILD SUCCESSFUL
libatomic_service.so: 0 unresolved strong imports
Hvigor: BUILD SUCCESSFUL
entry-default-signed.hap generated
6.4 功能验证用例
用例 1:默认页面和自检
启动应用后,页面显示"原子化服务"、三个模板按钮和默认的"每日专注"卡片,进度为 64%。普通宿主执行 Kotlin 侧七项共享检查;原子化服务 HAP 显示与它们对应的七项 host 契约自检。
用例 2:三个模板切换
按顺序点击每日专注、配送进度、设备概览:
| 按钮 | 页面内容 | 主要验证点 |
|---|---|---|
| 每日专注 | Medium 卡片 | 完成度指标、完成/刷新动作 |
| 配送进度 | Large 卡片 | 订单状态流转、稍后提醒动作 |
| 设备概览 | Small 卡片 | 模拟设备读数、状态降级语义 |
每次切换都会更新说明文本、卡片尺寸和当前模板的服务状态。
用例 3:动作归约
点击"完成",ArkTS 调用 host adapter 的归约语义,进度置为 100,正文变为"今日计划已完成";点击"稍后提醒"(配送模板),正文变为"已暂缓提醒(演示)"。刷新次数随 revision 递增,验证页面状态按公共 JSON 契约变化,而不是只替换静态文案。
用例 4:Want 路由入口
通过 Want 携带 serviceRoute/serviceId 启动免安装入口,/focus、/delivery、/device 分别映射到对应的模板卡片,未匹配路由返回统一的失败提示。
用例 5:Form Kit 桌面卡片
从预览页进入系统元服务管理面板,确认可以看到“添加至桌面”和“添加卡片”等系统入口。进一步添加桌面卡片后,可继续检查标题、进度、指标和 EntryFormAbility 回调是否与页内契约一致。
用例 6:刷新语义
连续点击"刷新",updatedAt 显示"第 N 次刷新",进度和指标按 revision 确定性变化:专注模板每刷新一次加 7%,配送模板在备货/运输/送达之间流转,设备模板电量逐步下降到 LIMITED 状态。
6.5 验证结论
本次验收结果:
atomic-service和example/sharedJVM 验收通过;nativeAppARM64 动态库生成成功;- ArkUI/Hvigor 原子化服务 HAP 构建成功,且未触发 Native 开发不支持错误;
- 签名 HAP 安装成功并启动
EntryAbility; - 三个模板切换成功;
- 完成、暂缓和刷新动作归约成功;
- Want 路由能正确映射三个入口;
- 系统元服务管理面板能够识别该服务,并提供添加至桌面、添加卡片等入口;
- Kotlin 共享检查和 ArkTS host 契约检查均定义为七项。
七、运行效果
7.1 真机截图
下面是原子化服务工作台在真机上的运行效果图。背景页面展示“原子化服务”标题、“在桌面,随时看见重要的事”说明、三个模板按钮(每日专注/配送进度/设备概览)和默认“每日专注”内容;前景是系统为应用“qwer”打开的元服务管理面板。

截图中可以看到:
- 背景页面标题为“原子化服务”,副标题为“在桌面,随时看见重要的事”;
- “每日专注”“配送进度”“设备概览”三个模板入口均已呈现;
- 默认模板显示“今日计划已完成 64%”;
- 系统面板提供“添加至我的服务”“添加至桌面”“通知与消息”“添加卡片”和“分享元服务”等入口;
- 系统面板还提供“添加安全锁”“设置”“反馈与投诉”,并展示最近使用的元服务。
7.2 命令速查
# 根工程测试和 OpenHarmony 原生库准备
./scripts/build-openharmony.sh
# 单独运行共享测试和 Native 复制任务
(cd example && ./gradlew :shared:jvmTest :nativeApp:prepareOhos)
# 检查 ARM64 动态库的强依赖
python3 scripts/check-native-deps.py \
/Applications/DevEco-Studio.app/Contents/sdk/default/openharmony/native \
example/ohosApp/entry/libs/arm64-v8a
# 准备不带个人证书的 DevEco 工程
python3 scripts/prepare-signing-project.py "$HOME/atomic_service_ohos_signing"
# 在已配置签名的工程中构建 HAP
./scripts/build-hap.sh "$HOME/atomic_service_ohos_signing"
# 安装和启动
hdc -t <target-id> install -r entry-default-signed.hap
hdc -t <target-id> shell aa start -a EntryAbility -b com.atomicservice.6917617026646679084
八、遗留问题与改进方向
8.1 踩坑复盘
- 原子服务不能链接 Native 库:
bundleType=atomicService时 DevEco 拒绝externalNativeOptions,必须在纯 ArkTS host adapter 中消费同一份 JSON 契约,Native bridge 放到独立宿主验证。 - JDK 版本必须匹配:OpenHarmony 示例脚本使用 JDK 21,不能用不兼容的 JDK 版本绕过 Kotlin DSL 或 Native 编译器检查。
- Native 文件必须先复制:如果没有执行
prepareOhos,CMake 的 imported library 路径存在但文件不存在,Ninja 会直接失败。 - 签名配置不能进源码:本机 DevEco 生成的 profile 可能带有绝对路径和密码,必须使用独立签名工程。
- 多设备 hdc 需要指定目标:同时连接真机和电脑设备时,安装命令要加
-t,否则 hdc 会提示需要确认设备。 - Want 参数必须校验:免安装入口的
serviceRoute/serviceId来自外部,必须经resolveRoute()与serviceId匹配,不能直接用路由字符串索引页面状态。 - Form Kit 添加流程不可省略:第三方应用不能静默固定桌面卡片,必须走系统卡片管理和用户确认,页内不能提供绕过入口。
8.2 已知问题
- 原子化服务 HAP 内的 host adapter 是同一份归约语义的 ArkTS 实现,修改共享模型时需要同步
AtomicServiceClient.ets,两条链路的字段一致性靠 JSON 契约和自检保证。 - Native 模型以 JSON 返回,数据量很小时足够直观;大数据集需要进一步评估编码开销和增量更新策略。
- Form Kit 目前覆盖添加、尺寸变化、刷新和动作回调,尚未覆盖更复杂的卡片交互(如多卡片实例的独立 revision)。
- HAP 需要开发者在 DevEco Studio 中完成签名,仓库不会提供可直接发布的证书。
8.3 未来优化方向
- 为 host adapter 与 KMP 模型建立契约一致性测试,在 CI 中自动对比两条链路的 JSON 形状。
- 在保持 JSON 契约稳定的前提下增加窗口化数据和增量刷新。
- 为 OpenHarmony 目标建立独立 CI,自动执行 JVM、Native、HAP 和依赖审计。
- 扩展 Form Kit 的更多尺寸与交互语义,覆盖
EXTRA_LARGE卡片。 - 如果后续发布 OpenHarmony 变体,再为消费者提供明确的 AtomGit 仓库坐标和版本策略。
九、总结
9.1 核心难点回顾
kmp-atomic-service OpenHarmony 适配的难点不是把卡片画出来,而是在平台限制下让 Kotlin/Native 验证链与原子化服务 HAP 保持同一份数据契约和归约语义:
共享模型 → Kotlin/Native → C ABI → C++ N-API → 普通宿主(预留链路)
公共 JSON 契约 → 纯 ArkTS host adapter → 原子化服务 HAP → ArkUI 卡片
每一层都有清晰的输入和输出,出现问题时可以分别检查模型、动态库、符号、HAP 或页面状态。
9.2 封装层次
atomic-service root module
└── example
├── shared 示例门面、路由语义和 JVM 检查
├── nativeApp ohosArm64 动态库、C ABI、内存释放
└── ohosApp Stage、Want 入口、ArkUI 卡片、Form Kit 和 HAP
9.3 三条经验
- 先固定归约语义,再写 UI。 服务状态、动作和刷新先由
AtomicServiceEngine定义,ArkTS host adapter 再按同一契约实现并接受逐字段校验。 - 把工具链问题和交互问题分开。 JVM、Native、CMake、Hvigor 和真机分层验证,能迅速定位是依赖、符号、签名还是页面状态问题。
- 签名工程必须隔离。 开源仓库提交源码和脚本,开发者在 DevEco 中手动完成签名,既满足真机运行,也避免泄露本机密钥。
9.4 适配成果
- OpenHarmony 示例具备
ohosArm64Kotlin/Native 构建链路; - Kotlin 共享层提供三个服务模板、免安装交付、路由分发和七项自检;
- Native 层通过四个 C ABI 函数向 ArkTS 提供 JSON 和状态操作;
- 原子化服务 HAP 使用纯 ArkTS host adapter,遵守
bundleType=atomicService的交付边界; - ArkUI 页面拥有独立的原子化服务工作台 UI;
- 真机可以切换模板、执行动作、刷新状态并通过 Want 进入指定路由;
- 真机系统面板能够识别该元服务,并显示添加至桌面、添加卡片等系统入口;
- 中文、英文 README、本文、效果图和仓库地址统一使用 AtomGit。
参考文档
更多推荐




所有评论(0)