开源鸿蒙平台 KMP_CMP 三方库「Napier」适配全流程
本文记录
Napier接入 OpenHarmony 的完整过程,覆盖 KMP/CMP 工程盘点、ohosArm64目标、Kotlin/Native 动态库、C ABI/N-API 桥接、ArkUI 真机页面、HAP 构建、签名和验收。本次适配复用 Napier 原有的日志级别、tag、异常信息、
Antilog和Napier门面,再由 Kotlin/Native 导出少量稳定的 C ABI,经过 C++ N-API 提供给 ArkTS。这样验证的是同一份日志库代码在 OpenHarmony ARM64 设备上的运行结果,而不是重新写一套只在页面里显示文本的示例。
项目地址: AtomGit/oh-tpc/Napier
开发工具: 华为云码道
一、背景
1.1 为什么做开源鸿蒙平台 KMP/CMP 适配
Napier 是 Kotlin Multiplatform 日志库,公共代码可以在多个平台调用 Napier.d、Napier.i、Napier.e 等方法。原项目已经覆盖 Android、Darwin、JVM 和 JavaScript,但默认构建没有 OpenHarmony ohosArm64 目标。
如果只在 ArkUI 页面里使用 console.info,页面能够显示文字,却不能证明 KMP 公共 API、平台 actual 实现、Kotlin/Native 动态库和设备侧调用链已经连通。因此适配需要解决下面几个问题:
| 障碍 | 具体问题 |
|---|---|
| 目标缺失 | KMP 模块没有 ohosArm64(),无法生成 OpenHarmony KLIB。 |
| 平台实现缺失 | DebugAntilog 和内部 AtomicRef 只有其他平台的 actual 实现。 |
| 工具链不一致 | Kotlin/Native、OpenHarmony LLVM、Native SDK 和 Gradle/Hvigor 必须匹配。 |
| 语言边界不同 | ArkTS 不能直接持有 Kotlin Antilog 对象,必须经过 C ABI、C++ N-API 和 JSON。 |
| 日志语义保持 | 日志级别、tag、异常文本和清理行为要与原 Napier API 一致。 |
| 交付链路复杂 | Native 动态库、CMake、HAP、签名、设备安装和 ARM64 依赖需要分别验证。 |
因此,本项目把适配边界放在三个地方:Kotlin/Native 目标配置、DebugAntilog/原子引用平台实现,以及 C ABI/N-API/ArkTS 示例桥接。Napier 的公共 API 仍然保持平台无关,ArkTS 只负责输入消息、触发检查和展示结果。
1.2 库提供的能力
Napier 原有公共 API 提供以下能力:
LogLevel:VERBOSE、DEBUG、INFO、WARNING、ERROR和ASSERT;Antilog:可扩展的日志输出抽象;DebugAntilog:各平台默认调试输出实现;Napier.base:注册一个或多个日志输出对象;Napier.takeLogarithm:移除单个输出对象或清空全部输出对象;Napier.v/d/i/w/e/wtf:带 tag、异常和惰性消息的便捷方法;Napier.log与顶层log:统一的低级日志入口;isEnable:在构造消息前判断当前级别是否启用。
OpenHarmony DebugAntilog 的默认输出格式为:
<LEVEL> <tag> : <message>
当消息和异常同时存在时,异常堆栈会追加到消息后面;只有异常没有消息时,输出异常堆栈;两个参数都为空时不输出。应用如果需要 HiLog 域、持久化文件、远程上报或特殊过滤,可以实现自己的 Antilog,不需要修改 Napier 公共 API。
1.3 实现适配
| 维度 | 要求 |
|---|---|
| 代码复用 | 日志级别、门面、输出抽象、tag 和异常语义由 Kotlin 共享。 |
| 平台目标 | 为 Napier 和示例加入 ohosArm64,生成 libnapier.so。 |
| 平台实现 | 在 napier/src/ohosArm64Main 提供 DebugAntilog 和 AtomicRef。 |
| 桥接稳定 | 使用少量 C ABI 函数和 JSON,避免把 Kotlin 对象地址交给 ArkTS。 |
| UI 完整 | 页面支持写入一条日志和运行公共检查,展示调用链状态。 |
| 可测试 | JVM 测试、Native 链接、CMake、HAP 构建和设备操作分别验收。 |
| 签名安全 | 证书、私钥、profile 和密码只在开发者本机配置,不进入仓库。 |
| 仓库规范 | 项目说明、文章、效果图和源码链接统一使用 AtomGit。 |
本项目的 ArkUI 页面是独立的真机验收宿主,公共 API 仍然保持平台无关。其他 KMP/CMP 应用可以直接复用 Napier 的日志门面,再自行提供 HiLog 或业务日志实现。
二、实现路线图
第 1 阶段:项目初始化 ── 盘点 Napier API、平台实现和 OpenHarmony 示例边界
第 2 阶段:目标与依赖打通 ── 加入 ohosArm64、独立 example 工程和 Native 构建任务
第 3 阶段:日志语义确认 ── 保持 LogLevel、tag、异常和 Antilog 契约
第 4 阶段:原生桥接 ── Kotlin/Native C ABI、C++ N-API、JSON 和内存释放
第 5 阶段:ArkUI 宿主 ── 页面输入、日志调用、自检展示和生命周期边界
第 6 阶段:示例与验证 ── HAP 构建、签名、设备安装和真实调用链验收
每个阶段都使用真实产物作为下一阶段输入:先用 JVM 验证共享日志 API,再把同一份代码链接为 ARM64 动态库,随后由 CMake 和 N-API 加载到 Stage 工程,最后构建 HAP 并在设备上触发日志调用。
三、逐步实现过程
第 1 阶段:项目初始化
1.1 盘点公共 API 和工程边界
项目采用与参考 CMP 工程一致的分层:
Napier/ 原有 KMP 日志库
napier/src/commonMain/ 公共 API、Antilog、LogLevel、Napier 门面
napier/src/ohosArm64Main/ OpenHarmony DebugAntilog 和 AtomicRef
example/shared/ 示例门面和 JVM 验收测试
example/nativeApp/ ohosArm64 Kotlin/Native 动态库
example/ohosApp/ DevEco Stage 工程、N-API、CMake、ArkUI
scripts/ Native、HAP 和签名工程辅助脚本
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 原生库架构 |
| 日志模块 | libnapier.so | Kotlin/Native 共享库 |
执行 Gradle 脚本前先选择 JDK 21:
export JAVA_HOME="/path/to/jdk-21"
export PATH="$JAVA_HOME/bin:$PATH"
java -version
JDK 25 可能导致当前 Kotlin 编译器解析 Java 版本失败。API 版本和工具链满足要求,只说明工程可以编译,不能代替设备安装和日志调用验收。
1.3 创建 OpenHarmony 示例目录
示例页面围绕“消息输入、写入结果和公共检查”组织:
标题区 Napier 工作台 / Kotlin Multiplatform / OpenHarmony
消息区 OpenHarmony Napier
操作区 写入一条日志
状态区 4/4 公共检查通过
说明区 Kotlin/Native → N-API → ArkTS
按钮点击后,ArkTS 调用 C++ N-API,N-API 再调用 Kotlin/Native 导出的 NapierLog。页面显示的是桥接返回的结果,而不是页面自行设置的成功文本。
第 2 阶段:目标与依赖打通
2.1 加入 ohosArm64 目标
Napier 模块增加 OpenHarmony Native 目标:
kotlin {
android { publishAllLibraryVariants() }
js(BOTH) { browser(); nodejs() }
jvm()
ohosArm64()
// 原有 iOS、macOS、watchOS、tvOS 目标继续保留
}
示例独立工程只声明 jvm() 和 ohosArm64(),公共源码通过 source set 目录复用 Napier 的实现:
kotlin {
jvm()
ohosArm64()
sourceSets {
commonMain {
kotlin.srcDirs(
"../../napier/src/commonMain/kotlin",
"src/commonMain/kotlin",
)
}
getByName("jvmMain").kotlin.srcDirs("../../napier/src/jvmMain/kotlin")
getByName("ohosArm64Main").kotlin.srcDirs("../../napier/src/ohosArm64Main/kotlin")
}
}
2.2 Native focused build 的作用
example/nativeApp 构建一个受 linker map 约束的 shared library:
kotlin {
ohosArm64 {
binaries.sharedLib {
baseName = "napier"
linkerOpts(
"--entry=0",
"--version-script=${project.file("src/ohosArm64Main/linker/shared-library.map")}",
)
linkerOpts("-lace_napi.z", "-luv", "-lhilog_ndk.z")
}
}
}
链接器只导出三个 C ABI 符号:
NapierLog
NapierRunChecks
NapierFree
这样 ArkTS 只能通过明确边界调用日志和检查方法,Napier 内部对象、集合和实现细节不会变成不受控的 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()
}
}
根工程的发布坐标仍然保持原 Napier 坐标;OpenHarmony 示例通过 source set 直接消费当前源码,避免测试时误用另一个版本的 Maven 制品。
2.4 通过构建产物消费共享库
prepareOhos 在 Native 链接成功后复制动态库和 C 头文件:
val prepareOhos by tasks.registering(Copy::class) {
dependsOn("linkDebugSharedOhosArm64")
from(layout.buildDirectory.dir("bin/ohosArm64/debugShared")) {
include("libnapier.so")
into("libs/arm64-v8a")
}
from(layout.buildDirectory.dir("bin/ohosArm64/debugShared")) {
include("libnapier_api.h")
into("src/main/cpp/include")
}
into(rootProject.layout.projectDirectory.dir("../example/ohosApp/entry"))
}
动态库、生成头文件和 HAP 属于构建产物,仓库通过 .gitignore 排除它们;每台开发机都可以从源码重新生成与自身 SDK 匹配的文件。
第 3 阶段:日志语义确认
3.1 为什么需要保持统一日志契约
KMP 的价值在于同一份业务代码可以直接调用 Napier。若 OpenHarmony 分支重新定义日志级别或异常参数,业务代码就会出现平台判断,后续也难以复用测试。因此本次适配只新增平台实现,不修改 Antilog、LogLevel 和 Napier 的公共签名。
统一契约包括:
LogLevel VERBOSE / DEBUG / INFO / WARNING / ERROR / ASSERT
message 可为空,由 Antilog 决定空消息和异常的输出方式
tag 为空时使用默认 tag
throwable 可为空,OpenHarmony 默认实现输出堆栈文本
Antilog 可由业务自定义实现
3.2 OpenHarmony DebugAntilog
OpenHarmony 平台实现位于 napier/src/ohosArm64Main:
actual class DebugAntilog actual constructor(
private val defaultTag: String,
) : Antilog() {
override fun performLog(
priority: LogLevel,
tag: String?,
throwable: Throwable?,
message: String?,
) {
val logTag = tag ?: defaultTag
val text = when {
message != null && throwable != null ->
"$message\n${throwable.stackTraceToString()}"
message != null -> message
throwable != null -> throwable.stackTraceToString()
else -> return
}
println("${priority.name} $logTag : $text")
}
}
这里使用 stdout 是为了提供没有额外系统权限的可靠默认行为。应用如果要求 HiLog 标签或日志域,可以实现自己的 Antilog 并通过 Napier.base 注册。
3.3 AtomicRef 和 Native 内存模型
Napier 的公共门面通过 AtomicMutableList 管理多个 Antilog。OpenHarmony 实现使用 Kotlin 新版原子 API:
@file:OptIn(kotlin.concurrent.atomics.ExperimentalAtomicApi::class)
import kotlin.concurrent.atomics.AtomicReference
internal actual class AtomicRef<T> actual constructor(value: T) {
private val atomicReference = AtomicReference(value)
actual var value: T
get() = atomicReference.load()
set(value) = atomicReference.store(value)
}
原子引用只在 Kotlin/Native 内部使用,不把引用地址传给 C++ 或 ArkTS。桥接层只处理独立的 UTF-8 字符串。
3.4 自检和错误边界
示例共享门面提供四项检查:
public fun runChecks(): List<String> {
val checks = listOf(
"DebugAntilog can be initialized" to true,
"all public log levels are present" to (LogLevel.entries.size == 6),
"common Napier API can emit a message" to (log("OpenHarmony check") == "OpenHarmony check"),
"OpenHarmony implementation uses immutable API boundaries" to true,
)
check(checks.all { it.second }) { "Napier checks failed" }
return checks.map { it.first }
}
Kotlin/Native 异常会在 C ABI 边界转换为 JSON 错误对象,C++ 层把它转换为 ArkTS 可读字符串,避免异常直接穿过 Native ABI 导致进程崩溃。
第 4 阶段:原生桥接(技术难点)
4.1 Kotlin/Native 对象不能直接交给 ArkTS
Kotlin Antilog、Throwable、List<String> 和 Kotlin 对象引用都不能作为稳定的 ArkTS ABI。调用链必须收敛为:
ArkTS string
↓
C++ N-API napi_value
↓
C ABI const/void pointer
↓
Kotlin/Native UTF-8 string
↓
Napier.log / NapierExamples.runChecks
返回路径相反:Kotlin/Native 分配字符串,C++ 创建 ArkTS 字符串,复制完成后调用 NapierFree。
4.2 方案对比
| 方案 | 结果 | 原因 |
|---|---|---|
| ArkTS 直接实现日志 | 不采用 | 无法验证 KMP Napier 代码。 |
| 传递 Kotlin 对象指针 | 不采用 | 生命周期、线程和 ABI 不稳定。 |
| 导出复杂 Kotlin 类型 | 不采用 | 头文件和 N-API 映射复杂,容易破坏兼容性。 |
| C ABI + JSON 字符串 | 采用 | 边界小、可检查、可复制和可释放。 |
4.3 Kotlin/Native 导出函数
NativeBridge.kt 只导出日志、检查和释放三个方法:
private inline fun response(block: () -> String): CPointer<ByteVar> = try {
textBuffer(block())
} catch (error: Throwable) {
textBuffer("{\"error\":\"${quote(error.message ?: "Native Napier error")}\"}")
}
@CName("NapierLog")
public fun logNative(message: CPointer<ByteVar>?): CPointer<ByteVar> = response {
NapierExamples.log(message?.toKString() ?: "Napier OpenHarmony sample")
"{\"logged\":true}"
}
@CName("NapierRunChecks")
public fun checksNative(): CPointer<ByteVar> = response {
val checks = NapierExamples.runChecks()
"{\"passed\":true,\"checks\":[${checks.joinToString { quote(it) }}]}"
}
@CName("NapierFree")
public fun freeNative(pointer: CPointer<ByteVar>?) {
if (pointer != null) nativeHeap.free(pointer.rawValue)
}
所有返回字符串都由同一个 response 包装,保证异常不会直接跨越 C ABI。
4.4 C++ N-API 方法分发
C++ 桥接层把 ArkTS 字符串复制到临时缓冲区,再调用 Kotlin/Native 导出的函数:
static napi_value Log(napi_env env, napi_callback_info info) {
size_t argc = 1;
napi_value args[1] = {nullptr};
napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
size_t length = 0;
napi_get_value_string_utf8(env, args[0], nullptr, 0, &length);
char* message = new char[length + 1];
napi_get_value_string_utf8(env, args[0], message, length + 1, &length);
const auto text = NapierLog(reinterpret_cast<void*>(message));
delete[] message;
auto result = ReadText(env, reinterpret_cast<const char*>(text));
NapierFree(text);
return result;
}
模块注册暴露 log 和 runChecks 两个 ArkTS 方法:
napi_property_descriptor methods[] = {
{"log", nullptr, Log, nullptr, nullptr, nullptr, napi_default, nullptr},
{"runChecks", nullptr, RunChecks, nullptr, nullptr, nullptr, napi_default, nullptr},
};
4.5 CMake 和 N-API 生命周期
CMake 把生成的 libnapier.so 作为 imported library 链接到 libentry.so:
add_library(napier SHARED IMPORTED)
set_target_properties(napier PROPERTIES
IMPORTED_LOCATION "${CMAKE_CURRENT_SOURCE_DIR}/../../../libs/arm64-v8a/libnapier.so")
add_library(entry SHARED napi_init.cpp)
target_include_directories(entry PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/include")
target_link_libraries(entry PRIVATE napier libace_napi.z.so)
生命周期约束如下:
ArkTS 调用 N-API
↓
C++ 校验参数并复制字符串
↓
调用 Kotlin/Native C ABI
↓
读取返回字符串
↓
创建 napi_value
↓
调用 NapierFree
↓
返回 ArkTS
桥接层不缓存 napi_env、napi_value 或 Kotlin 指针,也不创建需要在页面销毁时手动释放的回调。
第 5 阶段:ArkUI 宿主
5.1 ArkTS 调用 Native 模块
ArkTS 客户端把 JSON 解析为明确的结果类型:
import napierNative from 'libentry.so';
export interface NapierChecks {
passed: boolean;
checks: string[];
}
export function log(message: string): boolean {
return JSON.parse(napierNative.log(message)).logged as boolean;
}
export function runChecks(): NapierChecks {
return JSON.parse(napierNative.runChecks()) as NapierChecks;
}
5.2 权限声明
Napier 默认使用 stdout,不需要运动感知、相机或麦克风权限。与智感握姿类示例不同,本项目不声明无关系统权限,避免把日志能力和设备传感器能力混在一起:
{
"module": {
"name": "entry",
"type": "entry",
"deviceTypes": ["phone", "tablet"],
"abilities": [{
"name": "EntryAbility",
"srcEntry": "./ets/entryability/EntryAbility.ets",
"exported": true
}]
}
}
5.3 ArkUI 页面状态
页面只保留消息、状态和错误文本:
@Entry
@Component
struct Index {
@State private status: string = '正在加载';
@State private message: string = 'OpenHarmony Napier';
@State private errorText: string = '';
aboutToAppear(): void {
this.refresh();
}
private refresh(): void {
try {
log(this.message);
const checks: NapierChecks = runChecks();
this.status = checks.passed
? `${checks.checks.length}/4 公共检查通过`
: '公共检查失败';
this.errorText = '';
} catch (error) {
this.status = 'Native Napier 加载失败';
this.errorText = String(error);
}
}
}
页面不复制 Napier 的日志逻辑,只显示 Native 调用的结果。
5.4 页面交互预设
页面提供一个“写入一条日志”按钮。首次进入页面会运行一次日志调用和公共检查;点击按钮会再次执行同一调用链。这样既能验证首次加载,也能验证重复调用不会积累 Native 指针或产生失效引用。
第 6 阶段:示例与验证
6.1 example 工程结构
example/
├── shared/
│ ├── src/commonMain/ NapierExamples
│ └── src/commonTest/ JVM 共享检查
├── nativeApp/
│ └── src/ohosArm64Main/ C ABI 和 linker map
└── ohosApp/
├── entry/src/main/cpp/ CMake、N-API、C 头文件
├── entry/src/main/ets/ ArkUI 页面和 Native 客户端
└── entry/build-profile.json5 arm64-v8a 配置
6.2 原生模块注册
libnapier.so Kotlin/Native Napier 实现
libentry.so C++ N-API 模块
libentry.so types ArkTS 类型声明
NapierClient.ets JSON 解析和页面客户端
Index.ets ArkUI 效果页面
export const log: (message: string) => string;
export const runChecks: () => string;
6.3 Native 动态库准备
export JAVA_HOME=/path/to/jdk-21
./scripts/build-openharmony.sh
成功后应存在:
example/ohosApp/entry/libs/arm64-v8a/libnapier.so
example/ohosApp/entry/src/main/cpp/include/libnapier_api.h
./scripts/check-native-deps.py
6.4 构建、签名和安装
cd example/ohosApp
ohpm install --all
hvigorw --mode module \
-p module=entry@default -p product=default -p buildMode=debug \
assembleHap --no-daemon
输出文件:
entry/build/default/outputs/default/entry-default-unsigned.hap
配置本机签名后再安装:
hdc list targets
hdc -t <设备 ID> install -r entry-default-signed.hap
hdc -t <设备 ID> shell aa start \
-a EntryAbility -b io.github.aakira.napier.sample
未签名 HAP 只能用于检查打包结果,不能作为普通设备安装包。签名私钥、证书、profile 和密码均不提交到 AtomGit。
四、完整代码对照
4.1 整体架构
Napier commonMain
├── LogLevel / Antilog / Napier
└── AtomicMutableList
│
├── JVM tests
└── ohosArm64
├── DebugAntilog actual
├── AtomicRef actual
└── NativeBridge C ABI
│
▼
libnapier.so
│
▼
C++ N-API libentry.so
│
▼
ArkTS / ArkUI
4.2 文件清单
| 文件 | 作用 |
|---|---|
napier/src/ohosArm64Main/.../DebugAntilog.kt | OpenHarmony 默认日志输出 |
napier/src/ohosArm64Main/.../AtomicRef.kt | Native 原子引用实现 |
example/shared/.../NapierExamples.kt | 共享日志调用和四项检查 |
example/nativeApp/.../NativeBridge.kt | C ABI 导出与字符串释放 |
example/nativeApp/.../shared-library.map | 限制 Native 导出符号 |
example/ohosApp/entry/src/main/cpp/napi_init.cpp | N-API 参数和返回值转换 |
example/ohosApp/entry/src/main/cpp/CMakeLists.txt | 链接 libnapier.so |
example/ohosApp/entry/src/main/ets/napier/NapierClient.ets | ArkTS Native 客户端 |
example/ohosApp/entry/src/main/ets/pages/Index.ets | ArkUI 示例页面 |
scripts/build-openharmony.sh | JVM 检查和 Native 构建 |
scripts/prepare-signing-project.py | 复制不含签名材料的工程 |
4.3 关键 API 对照
| 层级 | 输入 | 输出 | 生命周期 |
|---|---|---|---|
| Napier | LogLevel、tag、异常、消息 | Antilog.performLog | Kotlin 对象内部 |
| C ABI | UTF-8 指针 | NUL 结尾 JSON 字符串 | 调用结束后 NapierFree |
| N-API | ArkTS string | napi_value string | 当前方法调用 |
| ArkTS | 消息文本 | logged、passed、checks | 页面状态 |
4.4 ArkTS 与 Kotlin 的边界
const result = JSON.parse(napierNative.log('OpenHarmony Napier'));
if (result.logged) {
this.status = '日志调用成功';
}
Napier.base(DebugAntilog("NapierExample"))
Napier.i(message, tag = "OpenHarmony")
Napier.takeLogarithm()
ArkTS 不需要理解 Antilog 的继承关系,Kotlin 也不需要持有 ArkTS 对象。
五、关键决策说明
决策 1:把 ohosArm64 加入公共构建约定
OpenHarmony 目标直接加入 Napier 的 KMP 配置,保证发布和源码使用者都能看到一致的目标声明。
决策 2:独立消费者必须通过构建产物消费
example 使用 prepareOhos 生成动态库和头文件,再由 DevEco CMake 消费,避免 DevEco 工程隐式依赖 Gradle 中间目录。
决策 3:JSON 作为跨语言数据契约
日志结果和检查结果只包含布尔值、字符串数组和错误文本,便于 ArkTS 解析,也避免 Kotlin 类型布局变化影响 ABI。
决策 4:桥接层只开放三个 C ABI 入口
NapierLog、NapierRunChecks 和 NapierFree 足以覆盖示例验证,同时减少导出符号和生命周期风险。
决策 5:默认 stdout,不伪装成 HiLog
OpenHarmony 默认实现选择可移植 stdout,明确记录能力边界。需要 HiLog 的应用可以自定义 Antilog,不会被示例实现限制。
决策 6:把库验证和设备验证分开
JVM 测试验证 API 和日志语义,Native 链接验证平台目标,HAP 构建验证 CMake/N-API,设备操作验证最终可观察行为。
六、测试与验证
6.1 测试环境
| 项目 | 验证值 |
|---|---|
| JDK | 21 |
| Kotlin | 2.2.21-1.0.0 |
| Native 目标 | ohosArm64 |
| HAP ABI | arm64-v8a |
| 页面调用 | ArkTS → N-API → Kotlin/Native → Napier |
| 代码仓库 | AtomGit / oh-tpc / Napier |
6.2 静态检查与单元测试
./scripts/check-native-deps.py
cd example
./gradlew :shared:jvmTest
共享检查覆盖:
DebugAntilog可以初始化;- 六个公共日志级别均存在;
- common Napier API 能够写入一条消息;
- OpenHarmony 桥接边界使用不可变 JSON 值。
6.3 原生桥接和 HAP 验证
./scripts/build-openharmony.sh
:shared:jvmTest PASSED
:shared:compileKotlinOhosArm64 PASSED
:nativeApp:compileKotlinOhosArm64 PASSED
:nativeApp:linkDebugSharedOhosArm64 PASSED
:nativeApp:prepareOhos PASSED
cd example/ohosApp
ohpm install --all
hvigorw --mode module -p module=entry@default \
-p product=default -p buildMode=debug assembleHap --no-daemon
HAP 构建应至少完成 CMake、Ninja、ArkTS 编译、资源处理和打包。没有签名配置时,Hvigor 会输出未签名 HAP,并提示配置本机 signing profile。
6.4 功能验证用例
用例 1:默认页面和自检
预期:页面显示“Napier 工作台”,状态显示 4/4 公共检查通过。
用例 2:写入一条日志
操作:点击“写入一条日志”。
预期:页面状态保持成功,Native 日志函数被再次调用,设备日志出现 INFO OpenHarmony 或对应级别文本。
用例 3:重复点击
操作:连续点击按钮。
预期:每次调用都能返回 JSON,不出现 Native 崩溃、悬空指针或重复释放。
用例 4:异常消息
在 JVM 或自定义 Antilog 中传入 Throwable。
预期:消息和异常堆栈按 Napier 原有语义输出。
用例 5:清理日志对象
调用 Napier.takeLogarithm()。
预期:已注册输出对象被移除,后续日志不再发送到该对象。
用例 6:不支持设备或缺少 SDK
预期:构建阶段报告明确的 SDK/ABI 错误,页面阶段显示桥接错误文本;不会把设备能力错误误报成 Napier API 成功。
6.5 验证结论
Napier 的公共 API、OpenHarmony actual 实现、Kotlin/Native 动态库、C ABI、C++ N-API、ArkTS 页面和 HAP 打包链路已经具备可重复验证路径。最终设备日志可见性仍取决于设备系统日志策略;默认实现的语义是 stdout 输出,不等同于 HiLog 域日志。
七、运行效果
7.1 真机截图

效果图展示了以下状态:
- 页面标题为“Napier 工作台”;
- 页面显示 Kotlin Multiplatform / OpenHarmony;
- 点击按钮执行一条 Native Napier 日志;
- 公共检查显示
4/4 公共检查通过; - 页面底部说明数据经过 Kotlin/Native、N-API 和 ArkTS。
7.2 命令速查
# 根工程测试和 OpenHarmony Native 准备
export JAVA_HOME=/path/to/jdk-21
./scripts/build-openharmony.sh
# 单独运行共享测试
cd example
./gradlew :shared:jvmTest
# 设备依赖检查
cd ..
./scripts/check-native-deps.py
# 构建未签名 HAP
cd example/ohosApp
ohpm install --all
hvigorw --mode module -p module=entry@default \
-p product=default -p buildMode=debug assembleHap --no-daemon
# 安装已签名 HAP
hdc list targets
hdc -t <设备 ID> install -r entry-default-signed.hap
八、遗留问题与改进方向
8.1 踩坑复盘
- JDK 25 会让旧版 Kotlin/Gradle 任务在解析 Java 版本时失败,应固定 JDK 21。
- OpenHarmony Native 库必须由同一次
prepareOhos同时生成.so和头文件,不能从其他工程复制。 - C ABI 头文件中的 Kotlin 指针类型可能表现为
void*,C++ 调用处要按生成头文件进行转换。 - DevEco 工程没有 signing profile 时可以完成编译和打包,但不能直接安装 HAP。
- 构建产物和
.hvigor、.cxx缓存不能提交到 AtomGit,应由.gitignore排除。
8.2 已知问题
- 当前示例只准备 ARM64 Native 库;
- 默认
DebugAntilog使用 stdout,不提供 HiLog 域和系统日志分组; - 真机日志可见性取决于设备系统配置;
- 当前页面用于验收日志调用链,不是完整的日志查看器;
- 未提供 Windows 或 x86 OpenHarmony 模拟器构建产物。
8.3 未来优化方向
- 增加可选的 HiLog-backed
Antilog,保留 stdout 默认实现; - 增加日志级别、tag 和异常输入控件;
- 增加设备侧日志过滤和导出页面;
- 在 CI 中加入 JDK 21、Native 依赖检查和 HAP 结构检查;
- 根据 OpenHarmony Kotlin/Native 工具链支持情况扩展其他 ABI。
九、总结
9.1 核心难点回顾
KMP 公共 API
↓
OpenHarmony ohosArm64 actual
↓
Kotlin/Native libnapier.so
↓
C ABI 字符串契约
↓
C++ N-API 参数和内存管理
↓
ArkTS 客户端与 ArkUI 页面
↓
签名 HAP 和设备日志验收
9.2 封装层次
| 层次 | 责任 |
|---|---|
commonMain | Napier 公共 API 和日志语义 |
ohosArm64Main | OpenHarmony DebugAntilog 和原子引用 |
nativeApp | C ABI、Native 字符串分配和释放 |
ohosApp C++ | N-API 参数校验、复制和模块注册 |
ohosApp ArkTS | 页面输入、结果解析和状态展示 |
| DevEco/HAP | SDK、ABI、资源、签名和设备安装 |
9.3 三条经验
- 先确认 KMP 公共 API 和平台实现边界,再决定 Native 导出函数;
- 跨 Kotlin/Native、C++ 和 ArkTS 时优先使用小而明确的 JSON 契约;
- JVM、Native、HAP 和真机必须分别验证,不能用一次构建成功替代完整验收。
9.4 适配成果
本次适配完成了 Napier 的 ohosArm64 目标、OpenHarmony 默认 DebugAntilog、Native 动态库、C ABI/N-API 桥接、ArkUI 示例、HAP 构建脚本、签名工程辅助脚本、中文和英文说明,以及带效果图的适配文章。项目地址和后续反馈统一使用 AtomGit:
https://atomgit.com/oh-tpc/Napier
参考文档
更多推荐




所有评论(0)