本文记录 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 参数(serviceRouteserviceId)和 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-serviceAtomicServiceModelAtomicServiceEnginerunChecks()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 的 AtomicServiceModelList 和异常对象属于 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 卡片、模拟设备读数、刷新语义

模型还提供 progressupdatedAt,让页面可以在不理解 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.ktJVM 侧路由、归约和自检测试。
example/shared/src/commonMain/.../AtomicServiceExamples.kt示例门面和七项验收检查。
example/nativeApp/.../NativeBridge.ktKotlin/Native C ABI、JSON 和内存释放。
example/nativeApp/.../shared-library.map限制导出的 Native 符号。
example/ohosApp/entry/src/main/cpp/napi_init.cppC++ N-API 方法和模块注册。
example/ohosApp/entry/src/main/cpp/CMakeLists.txtimported library 和 N-API 链接。
example/ohosApp/entry/src/main/ets/pages/Index.etsArkUI 状态、三个模板、动作和自检页。
example/ohosApp/entry/src/main/ets/atomicservice/AtomicServiceClient.ets纯 ArkTS host adapter 和 CardBindingData。
example/ohosApp/entry/src/main/ets/formability/EntryFormAbility.etsForm 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/NativeArkUI
服务模板生成 AtomicServiceModel使用 ColumnRow 和卡片容器渲染
动作归约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/sharedexample/nativeAppexample/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
ABIARM64
DevEcoDevEco 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 构建阶段需要确认:

  1. 原子化服务 HAP 不启用 externalNativeOptions
  2. ArkTS host adapter 能生成与 KMP 相同的服务 JSON 形状;
  3. bundleType=atomicService 构建不再报 Native 开发不支持错误;
  4. 普通宿主集成时,CMake imported library 路径指向 libatomic_service.so,并由独立宿主生成 libentry.so
  5. 签名由当前开发机的 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-serviceexample/shared JVM 验收通过;
  • nativeApp ARM64 动态库生成成功;
  • 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 踩坑复盘

  1. 原子服务不能链接 Native 库bundleType=atomicService 时 DevEco 拒绝 externalNativeOptions,必须在纯 ArkTS host adapter 中消费同一份 JSON 契约,Native bridge 放到独立宿主验证。
  2. JDK 版本必须匹配:OpenHarmony 示例脚本使用 JDK 21,不能用不兼容的 JDK 版本绕过 Kotlin DSL 或 Native 编译器检查。
  3. Native 文件必须先复制:如果没有执行 prepareOhos,CMake 的 imported library 路径存在但文件不存在,Ninja 会直接失败。
  4. 签名配置不能进源码:本机 DevEco 生成的 profile 可能带有绝对路径和密码,必须使用独立签名工程。
  5. 多设备 hdc 需要指定目标:同时连接真机和电脑设备时,安装命令要加 -t,否则 hdc 会提示需要确认设备。
  6. Want 参数必须校验:免安装入口的 serviceRoute/serviceId 来自外部,必须经 resolveRoute()serviceId 匹配,不能直接用路由字符串索引页面状态。
  7. Form Kit 添加流程不可省略:第三方应用不能静默固定桌面卡片,必须走系统卡片管理和用户确认,页内不能提供绕过入口。

8.2 已知问题

  • 原子化服务 HAP 内的 host adapter 是同一份归约语义的 ArkTS 实现,修改共享模型时需要同步 AtomicServiceClient.ets,两条链路的字段一致性靠 JSON 契约和自检保证。
  • Native 模型以 JSON 返回,数据量很小时足够直观;大数据集需要进一步评估编码开销和增量更新策略。
  • Form Kit 目前覆盖添加、尺寸变化、刷新和动作回调,尚未覆盖更复杂的卡片交互(如多卡片实例的独立 revision)。
  • HAP 需要开发者在 DevEco Studio 中完成签名,仓库不会提供可直接发布的证书。

8.3 未来优化方向

  1. 为 host adapter 与 KMP 模型建立契约一致性测试,在 CI 中自动对比两条链路的 JSON 形状。
  2. 在保持 JSON 契约稳定的前提下增加窗口化数据和增量刷新。
  3. 为 OpenHarmony 目标建立独立 CI,自动执行 JVM、Native、HAP 和依赖审计。
  4. 扩展 Form Kit 的更多尺寸与交互语义,覆盖 EXTRA_LARGE 卡片。
  5. 如果后续发布 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 三条经验

  1. 先固定归约语义,再写 UI。 服务状态、动作和刷新先由 AtomicServiceEngine 定义,ArkTS host adapter 再按同一契约实现并接受逐字段校验。
  2. 把工具链问题和交互问题分开。 JVM、Native、CMake、Hvigor 和真机分层验证,能迅速定位是依赖、符号、签名还是页面状态问题。
  3. 签名工程必须隔离。 开源仓库提交源码和脚本,开发者在 DevEco 中手动完成签名,既满足真机运行,也避免泄露本机密钥。

9.4 适配成果

  • OpenHarmony 示例具备 ohosArm64 Kotlin/Native 构建链路;
  • Kotlin 共享层提供三个服务模板、免安装交付、路由分发和七项自检;
  • Native 层通过四个 C ABI 函数向 ArkTS 提供 JSON 和状态操作;
  • 原子化服务 HAP 使用纯 ArkTS host adapter,遵守 bundleType=atomicService 的交付边界;
  • ArkUI 页面拥有独立的原子化服务工作台 UI;
  • 真机可以切换模板、执行动作、刷新状态并通过 Want 进入指定路由;
  • 真机系统面板能够识别该元服务,并显示添加至桌面、添加卡片等系统入口;
  • 中文、英文 README、本文、效果图和仓库地址统一使用 AtomGit。

参考文档

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐