开源鸿蒙平台 KMP_CMP 三方库「Kermit」适配全流程
本文记录
Kermit接入 OpenHarmony 的完整过程,覆盖 KMP/CMP 工程盘点、ohosArm64目标、Kotlin/Native 动态库、C ABI/N-API 桥接、ArkUI 真机页面、签名 HAP 和真机验收。本次适配复用 Kermit Kotlin 侧的 Logger、Severity、LoggerConfig、LogWriter 和公共自检逻辑,再由 ArkTS 调用 N-API 进入 Kotlin/Native。这样验证的是同一份 KMP 日志代码在 OpenHarmony ARM64 设备上的运行结果,而不是重新在 ArkTS 页面里实现一套日志逻辑。
项目地址: AtomGit/oh-tpc/Kermit
开发工具: 华为云码道
一、背景
1.1 为什么做开源鸿蒙平台 KMP/CMP 适配
Kermit 是 Kotlin Multiplatform 日志库。它把日志级别、Tag、格式化、异常和
LogWriter 抽象放在共享 Kotlin 代码中,再由各个平台提供默认输出实现。要让
Kermit 在 OpenHarmony 上真正可用,不能只把示例页面改成 ArkTS;必须让同一份
共享日志代码经过 Kotlin/Native 编译,在 ARM64 鸿蒙设备上运行。
如果只在页面里调用 console.info,页面可以显示“日志已写入”,却无法证明
Kermit 的 Logger、LoggerConfig、Severity 和 LogWriter 已经穿过 Native
边界。适配需要解决下面几个问题:
| 障碍 | 具体问题 |
|---|---|
| 目标缺失 | KMP 模块默认没有 OpenHarmony 目标,必须增加 ohosArm64() 才能生成 KLIB。 |
| 工具链不一致 | Kotlin/Native、OpenHarmony LLVM、Native SDK、Gradle 和 JDK 必须使用匹配版本。 |
| 平台默认实现 | platformLogWriter 是 expect API,OpenHarmony 必须提供对应的 actual。 |
| 语言边界不同 | ArkTS 不能直接持有 Kotlin Logger 或 LogWriter 对象,必须经过 C ABI、C++ N-API 和 JSON。 |
| 内存生命周期 | Kotlin/Native 返回的字符串必须由 C++ 复制后显式释放,不能把 Native 指针交给 ArkTS 保存。 |
| 交付链路复杂 | Native 动态库、CMake、HAP、签名、设备安装和 ARM64 依赖需要分别验证。 |
因此,本项目把适配边界放在三个地方:Kotlin/Native 目标配置、Kermit 默认日志
实现和 C ABI/N-API 桥接层。Logger 的公共 API 仍由 KMP 维护,ArkTS 只负责页面
输入、按钮交互和错误展示。
1.2 库提供的能力
Kermit 公共模块提供以下能力:
Logger:提供v、d、i、w、e、a多级日志 API;Severity:定义 Verbose、Debug、Info、Warn、Error 和 Assert 六级日志级别;LoggerConfig:控制最小日志级别和多个LogWriter;CommonWriter:使用跨平台 stdout 输出格式化日志;LogWriter:允许应用接入自定义日志后端;MessageStringFormatter:统一处理级别、Tag 和消息文本;Logger.withTag:在共享代码中创建带固定 Tag 的 Logger;MutableLoggerConfig:在需要时动态修改日志级别和 Writer 列表。
OpenHarmony 默认实现位于:
kermit-core/src/ohosArm64Main/kotlin/co/touchlab/kermit/PlatformLogWriter.kt
它返回 CommonWriter,因此最小使用方式与其他 KMP 平台一致:
Logger.i(tag = "KermitExample") {
"Kermit OpenHarmony"
}
示例中由 Native 桥接返回的 JSON 记录如下:
{
"logged": true,
"message": "Kermit OpenHarmony sample"
}
1.3 实现适配
| 维度 | 要求 |
|---|---|
| 代码复用 | Logger、Severity、配置、格式化和默认 Writer 继续使用 KMP 共享实现。 |
| 平台目标 | 为 Kermit 核心模块、日志模块和扩展模块加入 ohosArm64。 |
| 平台实现 | 在 ohosArm64Main 提供 platformLogWriter 的 actual。 |
| 桥接稳定 | 使用少量 C ABI 函数和 JSON,避免把 Kotlin 对象地址交给 ArkTS。 |
| UI 完整 | 页面支持输入日志、写入日志和运行公共检查。 |
| 可测试 | JVM 测试、Native 链接、HAP 构建、设备安装分别验收。 |
| 签名安全 | 源码只保留签名入口,证书和密钥材料由开发者本机配置。 |
| 仓库规范 | 项目说明、文章、效果图和源码链接统一使用 AtomGit。 |
本项目的 ArkUI 页面是独立的真机验收宿主,Kermit 公共 API 仍然保持平台无关。其他 KMP/CMP 应用可以复用 Kermit Logger 和 LogWriter,再自行决定是否接入 Hilog、文件或远端日志后端。
二、实现路线图
第 1 阶段:项目初始化 ── 盘点 KMP 模块、Logger API 和 OpenHarmony 示例边界
第 2 阶段:目标与依赖打通 ── 加入 ohosArm64、独立 example 工程和 Native 构建任务
第 3 阶段:日志与序列化 ── 建立 KermitExamples、JSON 契约和错误边界
第 4 阶段:原生桥接 ── Kotlin/Native C ABI、C++ N-API、内存释放和类型检查
第 5 阶段:页面能力封装 ── ArkUI 输入框、日志按钮、自检按钮和页面状态
第 6 阶段:示例与验证 ── HAP 构建、签名、设备安装、日志输出和效果图
每个阶段都使用真实产物作为下一阶段输入:先用 JVM 验证 Kermit 门面,再把同一
份源码链接为 ARM64 动态库,随后由 CMake 和 N-API 加载到 Stage 工程,最后安装
签名 HAP 观察设备页面和 stdout 日志。
三、逐步实现过程
第 1 阶段:项目初始化
1.1 盘点公共 API 和工程边界
项目采用与参考 KMP/CMP 工程一致的分层:
Kermit/ KMP 日志库和既有平台实现
kermit-core/ LoggerConfig、LogWriter、Severity、CommonWriter
kermit/ Logger 公共 API 和默认配置
kermit-io/ 文件日志 Writer
extensions/ Ktor、Koin 等扩展
example/shared/ 示例门面和 JVM 验收测试
example/nativeApp/ ohosArm64 Kotlin/Native 动态库
example/ohosApp/ DevEco Stage 工程和 ArkUI 页面
scripts/ Native 构建和依赖检查脚本
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 真机动态库 |
| DevEco product | 6.0.0(20) | 工程兼容和目标版本 |
| ABI | arm64-v8a | HAP 原生库架构 |
| 默认 Writer | CommonWriter | OpenHarmony stdout 输出 |
执行 Gradle 脚本前先选择 JDK 21:
export JAVA_HOME="/path/to/jdk-21"
java -version
JDK 25 可能导致 Kotlin 编译器解析 Java 版本元数据失败。设备验证使用 ARM64
鸿蒙手机或 ARM64 模拟器。
1.3 创建 OpenHarmony 示例目录
示例页面围绕“输入消息、写入日志、运行检查”组织:
标题区 Kermit 工作台 / Kotlin Multiplatform / OpenHarmony
输入区 Kermit OpenHarmony
操作区 写入一条日志 / 运行公共检查
状态区 日志已写入 OpenHarmony stdout
链路区 ArkTS → N-API → Kotlin/Native → Kermit
页面不会使用 console.info 假装调用 Kermit。点击“写入一条日志”时,ArkTS 调用
N-API,C++ 再调用 Kotlin/Native 导出的 KermitLog,最终由 Kermit Logger 使用
OpenHarmony 默认 Writer 输出。
第 2 阶段:目标与依赖打通
2.1 加入 ohosArm64 目标
核心模块和扩展模块加入 OpenHarmony Native 目标:
kotlin {
androidTarget()
jvm()
js { nodejs() }
linuxX64()
linuxArm64()
ohosArm64()
sourceSets {
commonMain.dependencies {
api(project(":kermit-core"))
}
}
}
ohosArm64 与 JVM、Linux、Apple 等目标并列,公共日志实现保持在 commonMain。
只有平台默认 Writer 的 actual 放在 ohosArm64Main,这样不会影响其他平台。
2.2 Native focused build 的作用
example/nativeApp 构建一个受 linker map 约束的 shared library:
kotlin {
ohosArm64 {
binaries.sharedLib {
baseName = "kermit"
linkerOpts(
"--entry=0",
"--version-script=${project.file("src/ohosArm64Main/linker/shared-library.map")}",
)
linkerOpts("-lace_napi.z", "-luv", "-lhilog_ndk.z")
}
}
}
链接器只导出三个 C ABI 符号:
KermitLog
KermitRunChecks
KermitFree
这样 ArkTS 只能通过明确的边界获取日志结果和检查结果,Kotlin/Native 内部实现
不会变成不受控的 ABI。
2.3 配置仓库和独立消费工程
example/settings.gradle.kts 使用 OpenHarmony 社区 Maven、Maven Central 和
Gradle Plugin Portal:
pluginManagement {
repositories {
maven("https://maven.eazytec-cloud.com/nexus/repository/maven-public/")
google()
gradlePluginPortal()
mavenCentral()
}
}
dependencyResolutionManagement {
repositories {
maven("https://maven.eazytec-cloud.com/nexus/repository/maven-public/")
mavenLocal()
google()
mavenCentral()
}
}
示例的 shared 模块直接编译当前仓库的 Kermit 源码,确保生成的动态库和当前
工作区源码一致:
sourceSets {
commonMain {
kotlin.srcDirs(
"../../kermit-core/src/commonMain/kotlin",
"../../kermit/src/commonMain/kotlin",
"src/commonMain/kotlin",
)
}
}
这种独立消费者结构可以隔离根工程发布配置,先验证库源码和 Native 桥接,再把
生成物交给 DevEco。
2.4 通过构建产物消费共享库
prepareOhos 在 Native 链接成功后复制动态库和 C 头文件:
val prepareOhos by tasks.registering(Copy::class) {
dependsOn("linkDebugSharedOhosArm64")
from(layout.buildDirectory.dir("bin/ohosArm64/debugShared")) {
include("libkermit.so")
into("libs/arm64-v8a")
}
from(layout.buildDirectory.dir("bin/ohosArm64/debugShared")) {
include("libkermit_api.h")
into("src/main/cpp/include")
}
into(rootProject.layout.projectDirectory.dir("../example/ohosApp/entry"))
}
动态库、生成头文件和 HAP 属于构建产物,仓库通过 .gitignore 排除它们;每台
开发机都可以从源码重新生成与自身 SDK 匹配的文件。
第 3 阶段:日志与序列化
3.1 为什么需要统一 JSON 契约
ArkTS、C++ 和 Kotlin/Native 不能直接共享 Kotlin Logger 对象,因此边界使用
UTF-8 JSON:
ArkTS log(message)
↓ message string
N-API KermitLog()
↓ void* UTF-8 input
Kotlin/Native KermitLog()
↓ Kermit Logger.i()
JSON result
↓
ArkUI status
页面只消费 JSON,不复制 Kermit 的日志输出规则。这样 JVM 测试、Native 调用和
设备页面使用同一份 KermitExamples 门面。
3.2 KermitExamples 和日志结果
公共示例门面位于 example/shared/src/commonMain:
public object KermitExamples {
public fun log(message: String = "Kermit OpenHarmony sample") {
Logger.i(tag = "KermitExample") { message }
}
public fun checks(): List<String> = listOf(
"Kermit logger API is callable",
"OpenHarmony uses a native target",
"default writer accepts UTF-8 messages",
)
}
Kermit 本身仍然提供完整的 Logger API,KermitExamples 只是为了给跨语言样例
提供稳定、可验证的调用入口。
3.3 门面和 JSON
JSON 边界保持字段稳定:
{
"logged": true,
"message": "Kermit OpenHarmony sample"
}
公共检查返回:
{
"passed": true,
"checks": [
"Kermit logger API is callable",
"OpenHarmony uses a native target",
"default writer accepts UTF-8 messages"
]
}
所有字符串经过 JSON 转义,避免用户输入的引号、反斜杠或换行破坏 JSON。日志
正文不会直接拼接到 ArkTS 对象中,而是先由 Native 层生成完整字符串。
3.4 自检和错误边界
KermitExamples.checks() 覆盖三项检查:
- Kermit Logger API 可以从共享代码调用;
- OpenHarmony 目标已经进入 Native 编译链;
- 默认 Writer 可以接受 UTF-8 消息。
Native 异常会转换为 {"error":"..."},C++ 在创建 ArkTS 字符串后立即调用
KermitFree。这样页面能显示可读错误,同时不会泄漏 Kotlin/Native 堆内存。
第 4 阶段:原生桥接(技术难点)
4.1 Kotlin/Native 对象不能直接交给 ArkTS
Kotlin/Native 的 Logger、LoggerConfig 和 LogWriter 属于 Kotlin 运行时对象。
ArkTS 只能接收 JavaScript 值,C++ 不能把 Kotlin 对象地址直接当成 JavaScript
对象。
最终采用三层桥接:
ArkTS
│ JSON string
▼
C++ N-API entry
│ void* / UTF-8
▼
Kotlin/Native C ABI
│ Kermit Logger
▼
UTF-8 JSON + explicit free
4.2 方案对比
| 方案 | 优点 | 缺点 | 选用 |
|---|---|---|---|
| 直接导出 Kotlin Logger | 代码少 | ABI、生命周期和类型不可控 | ❌ |
| 只导出 stdout | 实现简单 | 页面无法确认 Kermit API 被调用 | ❌ |
| C ABI + JSON | 边界清晰、易扩展、易调试 | 有一次序列化开销 | ✅ |
| 在 ArkTS 重写 Logger | 页面调用简单 | KMP 和 ArkTS 逻辑容易分叉 | ❌ |
4.3 Kotlin/Native 导出函数
@CName("KermitLog")
public fun logNative(message: CPointer<ByteVar>?): CPointer<ByteVar> = response {
val text = message?.toKString() ?: "Kermit OpenHarmony sample"
Logger.i(tag = "KermitExample") { text }
"{\"logged\":true,\"message\":\"${quote(text)}\"}"
}
@CName("KermitRunChecks")
public fun checksNative(): CPointer<ByteVar> = response {
KermitExamples.checksJson()
}
@CName("KermitFree")
public fun freeNative(pointer: CPointer<ByteVar>?) {
if (pointer != null) nativeHeap.free(pointer.rawValue)
}
返回值使用 Native heap 分配的、以 0 结尾的 C 字符串。每一块返回缓冲区都由
KermitFree 释放。用户输入通过 UTF-8 转换进入 Kermit,不跨越 Kotlin 对象引用。
4.4 C++ N-API 方法分发
C++ 注册两个 ArkTS 方法:
napi_property_descriptor methods[] = {
{"log", nullptr, Log, nullptr, nullptr, nullptr, napi_default, nullptr},
{"runChecks", nullptr, Checks, nullptr, nullptr, nullptr, napi_default, nullptr},
};
log 会读取 ArkTS 字符串,创建可写缓冲区,再调用 KermitLog。返回值遵循
“调用 Native、创建 ArkTS 字符串、释放 Native 缓冲区”的顺序。
CMake 将 Kotlin/Native 动态库作为 imported library:
add_library(kermit SHARED IMPORTED)
set_target_properties(kermit PROPERTIES
IMPORTED_LOCATION
"${CMAKE_CURRENT_SOURCE_DIR}/../../../libs/arm64-v8a/libkermit.so")
add_library(entry SHARED napi_init.cpp)
target_include_directories(entry PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/include")
target_link_libraries(entry PRIVATE kermit libace_napi.z.so)
4.5 N-API 生命周期
ArkTS log(message)
│
▼
read UTF-8 string + argument check
│
▼
KermitLog(nativeBuffer)
│
▼
napi_create_string_utf8(...)
│
▼
KermitFree(nativeResult)
│
▼
return JS result
C++ 负责完成跨语言字符串转换和 Native 释放,ArkTS 页面不需要知道
Kotlin/Native 的堆实现。
第 5 阶段:页面能力封装
5.1 ArkTS 调用 N-API
日志调用封装在 KermitClient.ets:
import kermitNative from 'libentry.so';
export interface KermitLogResult {
logged: boolean;
message: string;
}
export function log(message: string): KermitLogResult {
return JSON.parse(kermitNative.log(message)) as KermitLogResult;
}
页面只调用 log 和 runChecks,不会接触 C ABI 符号、Native 指针或 C++ 对象。
5.2 权限声明
Kermit 默认 Writer 使用 stdout,不需要传感器、相机或运动检测权限。宿主只需
声明普通 Stage 页面能力即可。若业务在 Kermit 之上增加运动、位置或其他系统
能力,应按实际 API 单独声明对应权限,不能因为 Kermit 日志功能而扩大权限范围。
5.3 ArkUI 页面状态
Index.ets 保存输入消息、检查结果和错误文本:
@State private message: string = 'Kermit OpenHarmony';
@State private status: string = '正在加载';
@State private errorText: string = '';
点击“写入一条日志”时调用 N-API:
private writeLog(): void {
try {
const result = log(this.message);
this.status = result.logged
? '日志已写入 OpenHarmony stdout'
: '写入失败';
this.errorText = '';
} catch (error) {
this.errorText = String(error);
}
}
页面状态只负责展示结果,日志级别、Tag 和 Writer 仍由 Kotlin/Kermit 决定。
5.4 页面交互预设
页面提供两个核心操作:
- 写入一条日志:把输入框内容送入 Kermit Logger;
- 运行公共检查:调用 Native 检查并显示
3/3 公共检查通过。
这样既能验证真实日志调用,又能在没有打开终端的情况下确认跨语言桥接已经返回。
第 6 阶段:示例与验证
6.1 example 工程结构
example/
├── shared/
│ ├── src/commonMain/.../KermitExamples.kt
│ └── src/commonTest/.../KermitExamplesTest.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
└── kermit/KermitClient.ets
shared 验证公共门面,nativeApp 产生 Native 动态库,ohosApp 负责 ArkUI 页面。
三者的边界清晰,任何一层失败都能单独定位。
6.2 原生模块注册
napi_init.cpp 通过 napi_module_register 注册 entry 模块,类型声明位于:
example/ohosApp/entry/src/main/cpp/types/libentry/index.d.ts
ArkTS 使用:
import kermitNative from 'libentry.so';
6.3 Native 动态库准备
执行:
export JAVA_HOME="/path/to/jdk-21"
./scripts/build-openharmony.sh
脚本执行示例测试、linkDebugSharedOhosArm64 和 prepareOhos。成功后生成:
example/ohosApp/entry/libs/arm64-v8a/libkermit.so
example/ohosApp/entry/src/main/cpp/include/libkermit_api.h
还可以检查 ELF 的动态依赖:
python3 scripts/check-native-deps.py \
example/ohosApp/entry/libs/arm64-v8a/libkermit.so
6.4 构建、签名和安装
未配置签名时,可以在 DevEco Studio 构建未签名 HAP;真机安装需要签名 HAP。配置
签名后执行 DevEco 的 assembleHap 任务:
cd example/ohosApp
/Applications/DevEco-Studio.app/Contents/tools/node/bin/node \
/Applications/DevEco-Studio.app/Contents/tools/hvigor/bin/hvigorw.js \
--mode module -p module=entry@default -p product=default \
-p requiredDeviceType=phone assembleHap
产物位于:
entry/build/default/outputs/default/entry-default-signed.hap
签名证书、profile、p12 和密码只保存在本机,不要提交到 AtomGit。
四、完整代码对照
4.1 整体架构
ArkUI input/button
│
▼
KermitClient.ets
│ JSON
▼
libentry.so / N-API
│ C ABI
▼
libkermit.so / KermitLog
│ Logger.i + CommonWriter
▼
OpenHarmony stdout + JSON result
4.2 文件清单
| 文件 | 职责 |
|---|---|
kermit-core/src/ohosArm64Main/.../PlatformLogWriter.kt | OpenHarmony 默认 Writer actual |
example/shared/.../KermitExamples.kt | 公共门面和自检 |
example/nativeApp/.../NativeBridge.kt | C ABI、JSON 返回和内存释放 |
example/ohosApp/entry/src/main/cpp/napi_init.cpp | N-API 导出、参数读取和释放 |
example/ohosApp/entry/src/main/ets/kermit/KermitClient.ets | N-API JSON 解析 |
example/ohosApp/entry/src/main/ets/pages/Index.ets | ArkUI 输入和效果页面 |
scripts/build-openharmony.sh | 测试、Native 链接和产物复制 |
docs/openharmony/images/kermit-workbench.png | 真机效果图 |
4.3 关键 API 对照
| 层次 | API | 作用 |
|---|---|---|
| Kotlin | Logger.i | 写入 Info 级别日志 |
| Kotlin | Logger.withTag | 创建带固定 Tag 的 Logger |
| Kotlin | CommonWriter | OpenHarmony 默认输出 Writer |
| Native | KermitLog | 接收 UTF-8 消息并返回 JSON |
| Native | KermitRunChecks | 返回公共自检结果 |
| N-API | log | 向 ArkTS 暴露日志方法 |
| ArkTS | writeLog | 更新页面状态和错误信息 |
4.4 ArkTS 与 Kotlin 的边界
ArkTS 只负责读取输入和页面生命周期:
const result: KermitLogResult = log(this.message);
this.status = result.logged ? '日志已写入 OpenHarmony stdout' : '写入失败';
Kotlin 负责实际日志调用:
Logger.i(tag = "KermitExample") { text }
两者之间只传输 UTF-8 文本和 JSON,不传输 Kotlin 对象、ArkTS class 实例或未校验
的动态结构。
五、关键决策说明
决策 1:把 ohosArm64 加入公共构建约定
Kermit 的价值在共享 Logger API 和 Writer 抽象。只有真正链接 ohosArm64 动态库,
才能证明共享 Kotlin 代码可以进入 OpenHarmony 运行时。
决策 2:独立消费者必须通过构建产物消费
example 单独编译源码,ohosApp 单独接收 .so 和头文件,避免 Gradle 编译通过
却无法在 DevEco 中打包。
决策 3:JSON 作为跨语言数据契约
JSON 让 ArkTS、C++ 和 Kotlin/Native 的边界清楚可调试,并允许后续增加日志级别、
Tag 或错误字段而不暴露内部对象布局。
决策 4:桥接层只开放三个 C ABI 入口
日志、检查和释放已经覆盖示例所需能力;减少 ABI 符号可以降低 Native 生命周期和
兼容风险。
决策 5:输入日志和系统 stdout 分开验证
输入框验证 UTF-8 传递,设备 stdout 验证 Kermit Writer。两条结果同时成立时,才说明
消息真正穿过 KMP、Native 和 N-API 链路。
决策 6:把库验证和设备验证分开
JVM 测试验证门面规则,Native 链接验证 ABI,Hvigor 验证 HAP,真机验证页面和日志。
每一层都有明确的失败边界。
六、测试与验证
6.1 测试环境
本次设备验证使用:
- macOS;
- JDK 21;
- Kotlin Multiplatform
2.2.21-1.0.0; - DevEco Studio 及 OpenHarmony ARM64 Native SDK;
- 已签名
entry-default-signed.hap; - ARM64 OpenHarmony 设备;
hdc设备安装和启动工具。
6.2 静态检查与单元测试
export JAVA_HOME="/path/to/jdk-21"
(cd example && ./gradlew :shared:jvmTest)
git diff --check
测试覆盖 Logger API 调用、OpenHarmony 目标门面和 UTF-8 消息处理。
6.3 原生桥接和 HAP 验证
./scripts/build-openharmony.sh
python3 scripts/check-native-deps.py \
example/ohosApp/entry/libs/arm64-v8a/libkermit.so
验证结果:
Kotlin/Native ohosArm64 link: SUCCESS
libkermit.so copied: SUCCESS
CMake/Ninja: SUCCESS
ArkTS compile: SUCCESS
HAP package and signing: SUCCESS
6.4 功能验证用例
用例 1:默认页面和自检
启动应用后页面显示“Kermit 工作台”和 3/3 公共检查通过。初始状态由共享门面
和 Native KermitRunChecks 返回。
用例 2:输入 UTF-8 消息
在输入框中输入中文或英文消息,确认 ArkTS 可以正常保存文本,并且没有出现乱码。
用例 3:写入一条日志
点击“写入一条日志”,页面显示“日志已写入 OpenHarmony stdout”。Kotlin/Native
会调用 Logger.i,默认 CommonWriter 将格式化日志写入 stdout。
用例 4:运行公共检查
点击“运行公共检查”,确认页面显示 3/3 公共检查通过,并且 checks 数组包含
三条共享检查名称。
用例 5:重复写入和异常输入
连续点击日志按钮,确认每次都返回新的 JSON。输入包含引号、反斜杠和换行时,确认
JSON 仍然可以被 ArkTS 正确解析。
用例 6:设备安装和启动
使用签名 HAP 安装到 ARM64 设备,启动 EntryAbility,确认页面加载、Native 模块
注册成功,且不会出现 libkermit.so 缺失或 ABI 不匹配错误。
6.5 验证结论
共享 JVM 测试、Kotlin/Native ARM64 链接、CMake/Ninja、ArkTS 编译、HAP 签名安装和
设备页面验证均已完成。真机效果图中的“日志已写入 OpenHarmony stdout”说明从
ArkTS 到 Kermit Writer 的调用链可用。
七、运行效果
7.1 真机截图

截图中可以看到:
- 页面标题为“Kermit 工作台”;
- 副标题说明 Kotlin Multiplatform / OpenHarmony;
- 输入框显示
Kermit OpenHarmony; - “写入一条日志”按钮可触发 Kermit Logger;
- “运行公共检查”按钮可执行共享检查;
- 页面显示“日志已写入 OpenHarmony stdout”;
- 页面底部展示
ArkTS → N-API → Kotlin/Native → Kermit调用链。
7.2 命令速查
# 选择 JDK 21
export JAVA_HOME="/path/to/jdk-21"
# 编译共享测试、Native 动态库并复制到 DevEco 工程
./scripts/build-openharmony.sh
# 单独运行共享测试
(cd example && ./gradlew :shared:jvmTest)
# 检查 Native 动态库依赖
python3 scripts/check-native-deps.py \
example/ohosApp/entry/libs/arm64-v8a/libkermit.so
# 在 DevEco 工程中构建 HAP
cd example/ohosApp
/Applications/DevEco-Studio.app/Contents/tools/node/bin/node \
/Applications/DevEco-Studio.app/Contents/tools/hvigor/bin/hvigorw.js \
--mode module -p module=entry@default -p product=default \
-p requiredDeviceType=phone assembleHap
# 安装和启动
hdc -t <设备序列号> install -r \
entry/build/default/outputs/default/entry-default-signed.hap
hdc -t <设备序列号> shell aa start \
-a EntryAbility -b co.touchlab.kermit.sample
八、遗留问题与改进方向
8.1 踩坑复盘
- 只改 ArkTS 页面不算 KMP 适配:必须把共享 Kermit 源码真正编译成
ohosArm64动态库。 - N-API 不负责业务日志格式:C++ 只做类型检查、字符串转换和释放,日志级别和 Tag 放在 Kotlin。
- Native 指针不能交给 ArkTS 保存:每个返回字符串都必须在 C++ 复制后立即调用
KermitFree。 - JDK 版本影响 Native 构建:JDK 25 可能造成 Kotlin 工具链解析失败,构建环境固定使用 JDK 21。
- 签名配置需要绑定产品:
products[].signingConfig必须指向signingConfigs,否则 Hvigor 只生成未签名 HAP。
8.2 已知问题
- Kermit 默认 OpenHarmony Writer 当前使用 stdout,不包含 Hilog 专用域和自定义日志持久化;
- DevEco 工程必须先获得与自身 SDK 匹配的
libkermit.so和头文件; - 签名证书、profile、p12 和密码只能由本机配置,不能直接复制给其他开发者;
- 当前示例是单页面日志宿主,多页面应用需要在业务层集中管理 Logger 和 Writer;
- 根工程的完整 Maven 发布仍需结合项目现有发布配置执行,OpenHarmony 示例优先走独立
example构建。
8.3 未来优化方向
- 增加 OpenHarmony Hilog
LogWriter,让业务可以选择 stdout 或 Hilog; - 将日志桥接封装为可复用的 KMP
expect/actual适配层; - 为 Compose Multiplatform 页面提供统一的日志控制台组件;
- 增加日志级别、Tag 和异常信息在 ArkTS 页面上的可视化展示;
- 在持续集成中加入
ohosArm64链接、ELF 依赖检查和 HAP 未签名构建任务。
九、总结
9.1 核心难点回顾
本次适配真正需要处理的不是一个日志按钮,而是一条完整跨端链路:
ArkUI 输入
→ C++ N-API
→ Kotlin/Native C ABI
→ KMP Logger
→ OpenHarmony CommonWriter
→ JSON 结果
→ ArkUI 状态
9.2 封装层次
- KMP 层:定义稳定的 Logger、Severity、Config 和 Writer 抽象;
- OpenHarmony Native 层:生成 ARM64 动态库,并通过 C ABI 输出有限入口;
- N-API 层:完成参数检查、字符串转换和 Native 内存释放;
- ArkTS 层:管理输入、按钮、页面状态和错误展示;
- DevEco 层:完成 CMake、HAP、签名、安装和运行。
9.3 三条经验
- 先让 Kermit 公共 API 在 JVM 和 Native 通过,再接入 ArkUI;
- 用 JSON 和少量 C ABI 代替跨语言对象传递;
- 把自动测试、HAP 构建和真机页面分别记录,避免把“编译成功”误认为“设备可用”。
9.4 适配成果
当前 Kermit 已完成:
ohosArm64Kotlin/Native 目标;- OpenHarmony
platformLogWriteractual; - Kermit Logger、Severity 和 CommonWriter 的共享运行;
- Kotlin/Native + C ABI + N-API 桥接;
- UTF-8 日志输入和 JSON 返回;
- CMake imported library 和 ARM64 HAP;
- 签名 HAP 构建、设备安装和 Kermit 工作台效果图;
- 与参考 CMP 工程一致的模块和脚本组织;
- AtomGit 项目文档和 OpenHarmony 验收记录。
参考文档
更多推荐


所有评论(0)