kotlin-multiplatform-diff OpenHarmony KMP 适配:从多平台源码到 ARM64 交互验收
本文记录
kotlin-multiplatform-diff适配 OpenHarmony 的完整过程:从 Kotlin Multiplatform 目标配置、OpenHarmony Kotlin/Native 工具链对齐,到独立消费工程、Kotlin/Native 动态库、C++ N-API 桥接、ArkTS 交互页面和 HAP 构建。这次适配的重点是让原有公共 diff 算法继续复用,同时为 OpenHarmony ARM64 增加真实可运行的消费示例。示例页面不是只展示“测试通过”,而是可以输入两段文本,调用 Kotlin/Native 中的真实库 API,显示补丁段、行内差异、逐行差异和应用补丁后的文本。
项目地址: AtomGit/oh-tpc/ohos_kotlin-multiplatform-diff
开发工具: 华为云码道
一、项目背景
1.1 kotlin-multiplatform-diff 是什么
kotlin-multiplatform-diff 是 java-diff-utils 的 Kotlin 多平台实现。库提供 Myers 差异算法、补丁生成与应用、行级差异、行内差异、HTML 标记、空白处理以及变更类型建模等能力。
公共代码位于 src/commonMain,原有工程已经支持 JVM、JavaScript、Wasm 和多种 Kotlin/Native 目标。OpenHarmony 应用需要 ARM64 Kotlin/Native 产物,因此本次适配增加 ohosArm64,并将它接入独立的示例工程。
1.2 为什么需要 OpenHarmony 目标
普通 JVM 或其他 Native 产物不能直接作为 OpenHarmony 应用的原生库。要让 ArkTS 页面使用同一套 diff 逻辑,需要解决以下问题:
| 障碍 | 具体问题 |
|---|---|
| 目标缺失 | 原工程没有 ohosArm64(),无法产生 OpenHarmony ARM64 KLIB。 |
| 工具链不一致 | Kotlin/Native 编译器、OpenHarmony sysroot 和 KMP 插件必须使用匹配的发行版本。 |
| 构建范围过大 | 全量配置所有平台会下载大量无关工具链,影响迭代速度。 |
| 消费方式不同 | 仅编译根工程不能证明第三方工程能通过 Maven metadata 和 KLIB 使用库。 |
| 应用边界不同 | Kotlin/Native 代码最终需要通过 C ABI 和 N-API 被 ArkTS 调用。 |
| 验收形式不同 | 编译通过不能说明用户真的能执行 diff 操作,需要一个可以输入文本并显示结果的页面。 |
适配的原则是保持 commonMain 中的算法和 API 不变,把 OpenHarmony 差异收敛在目标配置、消费工程、原生桥接和应用打包层。
1.3 适配后提供的能力
完成适配后,仓库提供:
ohosArm64Kotlin/Native 目标,面向 OpenHarmony ARM64 设备。-PopenharmonyOnly=truefocused build,减少本地开发时的无关目标配置。- 独立的
example/消费工程,通过mavenLocal()使用已发布库坐标。 shared模块中的 JVM 和 OHOS 共享验收逻辑。nativeApp模块生成libdiff.so,并导出DiffSampleCompare、DiffSampleRunChecks和DiffSampleFree。- C++ N-API 模块,将 ArkTS 字符串参数传给 Kotlin/Native,再把 JSON 结果返回给 ArkTS。
- ArkTS 交互式 diff 页面,支持编辑原文、新文和重新比较。
- 构建、签名工程准备、HAP 构建和 ARM64 ELF 依赖检查脚本。
1.4 版本矩阵
| 组件 | 版本或要求 |
|---|---|
| 适配库 | 1.3.0-ohos.1 |
| Maven 坐标 | io.github.petertrr:kotlin-multiplatform-diff:1.3.0-ohos.1 |
| Kotlin Gradle plugin / compiler / Native | 2.2.21-1.0.0 |
| Kotlin Coroutines | 本项目核心库不依赖协程 |
| OpenHarmony target | ohosArm64 |
| 应用 ABI | arm64-v8a |
| HarmonyOS target / compatible API | 6.0.0(20) |
| Gradle | 8.14.3 |
| JDK | 21 |
| DevEco Studio | 6 或更高版本 |
这里的 ohosArm64 是 Kotlin/Native 目标名称,arm64-v8a 是 DevEco/HAP 使用的 ABI 名称,两者需要同时配置。
二、整体实现路线
本次适配分为六个阶段:
第 1 阶段:盘点工程边界 ── 确认 commonMain、测试和原有多平台目标
第 2 阶段:增加 OHOS 目标 ── 加入 ohosArm64 和 focused build
第 3 阶段:独立消费验证 ── 通过 Maven 坐标构建 shared 模块
第 4 阶段:Native 桥接 ── Kotlin/Native 动态库、C ABI、C++ N-API
第 5 阶段:交互页面 ── ArkTS 输入文本并展示真实 diff 结果
第 6 阶段:构建和验收 ── JVM、OHOS、HAP、ELF 和设备分层验证
最终依赖方向如下:
ohosApp → nativeApp → shared → kotlin-multiplatform-diff
shared 通过发布坐标消费库,nativeApp 依赖 shared 并生成动态库,ohosApp 通过 C++ N-API 加载动态库。
三、根工程增加 OpenHarmony 目标
3.1 配置仓库和版本
OpenHarmony Kotlin/Native 发行包来自社区 Maven 仓库,因此插件仓库和依赖仓库都要加入该地址:
pluginManagement {
repositories {
maven("https://maven.eazytec-cloud.com/nexus/repository/maven-public/")
mavenCentral()
gradlePluginPortal()
}
}
dependencyResolutionManagement {
repositories {
mavenLocal()
maven("https://maven.eazytec-cloud.com/nexus/repository/maven-public/")
mavenCentral()
}
}
gradle.properties 固定 Kotlin/Native 从 Maven 下载,并声明适配版本:
kotlin.native.ignoreDisabledTargets=true
kotlin.mpp.stability.nowarn=true
kotlin.native.distribution.downloadFromMaven=true
version=1.3.0-ohos.1
项目的发布元数据和 SCM 地址统一指向 AtomGit:
mavenPublishing {
coordinates(
groupId = project.group.toString(),
artifactId = project.name,
version = project.version.toString(),
)
pom {
url.set("https://atomgit.com/oh-tpc/ohos_kotlin-multiplatform-diff")
scm {
url.set("https://atomgit.com/oh-tpc/ohos_kotlin-multiplatform-diff")
connection.set(
"scm:git:https://atomgit.com/oh-tpc/ohos_kotlin-multiplatform-diff.git",
)
}
}
}
3.2 声明 ohosArm64
根工程保留原有 JVM、JS、Wasm 和 Native 目标,在公共目标之外增加:
kotlin {
explicitApi()
compilerOptions {
apiVersion = KotlinVersion.KOTLIN_2_2
languageVersion = KotlinVersion.KOTLIN_2_2
}
ohosArm64()
jvm {
// 原有 JVM 配置
}
if (!providers.gradleProperty("openharmonyOnly")
.map(String::toBoolean)
.getOrElse(false)) {
js {
browser()
nodejs()
}
wasmJs {
browser()
nodejs()
}
// 原有 macOS、iOS、Linux、Windows、Android Native 等目标
}
}
openharmonyOnly 只改变目标配置范围,不改变公共源码:
./gradlew -PopenharmonyOnly=true tasks
在 focused build 中仍保留 JVM 和 OpenHarmony,跳过 JS、Wasm 以及其他不相关 Native 工具链。
3.3 为什么不能只改示例
如果只在 example/nativeApp 中声明 ohosArm64(),示例可能拥有一个 Native 目标,但根库不会产生 OpenHarmony KLIB。正确的关系是:
根库 commonMain
├─ JVM publication
├─ Kotlin Multiplatform metadata
└─ OHOS ARM64 publication
│
▼
example/shared 通过 Maven 坐标解析
因此 ohosArm64() 必须出现在根库构建配置中,示例工程再单独声明自己的目标。
四、独立消费工程
4.1 为什么新增独立 example/
根工程里的单元测试只能证明当前源码能运行,不能证明一个外部消费者能正确解析:
- Kotlin Multiplatform metadata。
- JVM artifact。
- OpenHarmony ARM64 artifact。
- 版本号和 POM。
mavenLocal()中的发布坐标。
因此示例工程不直接 project(":") 依赖根工程,而是使用真实坐标:
kotlin {
jvm()
jvmToolchain(21)
ohosArm64()
sourceSets {
commonMain.dependencies {
implementation(
"io.github.petertrr:kotlin-multiplatform-diff:1.3.0-ohos.1",
)
}
commonTest.dependencies {
implementation(kotlin("test"))
}
}
}
使用前先发布到本地 Maven:
./gradlew -PopenharmonyOnly=true \
-PsignPublications=false \
publishToMavenLocal
4.2 示例工程目录
example/
shared/
build.gradle.kts
src/commonMain/kotlin/.../AcceptanceChecks.kt
src/commonTest/kotlin/.../AcceptanceChecksTest.kt
nativeApp/
build.gradle.kts
src/ohosArm64Main/kotlin/.../NativeChecks.kt
src/ohosArm64Main/linker/shared-library.map
ohosApp/
AppScope/
entry/
src/main/ets/pages/Index.ets
src/main/cpp/napi_init.cpp
src/main/cpp/CMakeLists.txt
4.3 共享验收场景
shared 直接调用发布坐标中的 API,当前包含 9 类场景:
- 字符串 diff 和补丁应用。
CRLF、CR、LF换行归一化。- 行内 diff。
- 插入、删除和修改 delta 类型。
includeEqualParts包含相等部分。DiffRowGenerator行差异和 HTML 标记。- 忽略空白差异。
- Unicode 和空输入。
- 交互页面使用的比较模型。
示例测试仍然保持最小边界:
@Test
fun allScenariosPass() {
val results = runAcceptanceChecks()
assertEquals(9, results.size)
assertTrue(results.all { it.passed })
}
测试通过只能说明共享逻辑正确,后续还要继续经过 OHOS 编译、Native 链接和 HAP 构建。
五、把 diff 能力做成真实交互页面
5.1 不再只显示 PASS
验收页最初可以只显示每个检查是否通过,但这种页面不能让使用者感知库的实际功能。当前页面增加两个多行输入框:
- 原文 / Original:输入待比较的原始文本。
- 新文 / Revised:输入修改后的文本。
点击“比较 / Compare”后,ArkTS 将两段文本传给 Native bridge。Native bridge 再调用 Kotlin/Native 中的 compareTexts,结果返回:
{
"patchDeltaCount": 1,
"inlineDeltaCount": 2,
"appliedText": "alpha\nbeta changed\ndelta\ngamma",
"rows": [
{"tag":"EQUAL", "oldLine":"alpha", "newLine":"alpha"},
{"tag":"CHANGE", "oldLine":"beta", "newLine":"beta changed"},
{"tag":"INSERT", "oldLine":"", "newLine":"delta"},
{"tag":"EQUAL", "oldLine":"gamma", "newLine":"gamma"}
]
}
页面会展示补丁段数量、行内差异数量、逐行结果以及应用补丁后的完整文本。
5.2 Kotlin 共享比较模型
共享模块把库 API 组合为页面需要的结果模型:
data class ComparisonResult(
val patchDeltaCount: Int,
val inlineDeltaCount: Int,
val appliedText: String,
val rows: List<DiffRow>,
)
fun compareTexts(source: String, target: String): ComparisonResult {
val sourceLines = source.split(lineBreak)
val targetLines = target.split(lineBreak)
val patch = diff(sourceLines, targetLines)
return ComparisonResult(
patchDeltaCount = patch.deltas.size,
inlineDeltaCount = diffInline(source, target).deltas.size,
appliedText = patch.applyTo(sourceLines).joinToString("\n"),
rows = DiffRowGenerator(
showInlineDiffs = true,
reportLinesUnchanged = true,
).generateDiffRows(sourceLines, targetLines),
)
}
这里的 diff、diffInline、DiffRowGenerator 和 Patch.applyTo 都来自被适配的三方库,没有在 ArkTS 中重写一份算法。
5.3 ArkTS 页面操作
页面提供“比较 / Compare”和“示例 / Example”两个按钮。示例按钮填入一组默认文本,比较按钮执行真实调用:
private runComparison(): void {
try {
const value = JSON.parse(
compare(this.sourceText, this.targetText),
) as ComparisonResult;
this.comparison = value;
this.errorMessage = '';
} catch (error) {
this.comparison = undefined;
this.errorMessage = String(error);
}
}
ForEach 根据 rows 显示 EQUAL、CHANGE、INSERT 和 DELETE,应用补丁后的内容单独显示在结果区域。底部的库自检只用于确认动态库和库 API 已正确加载。
5.4 交互效果图
下面是示例在 OpenHarmony 页面中的实际效果。页面可以编辑两段文本,点击比较后查看结果:

图中可以看到:
- 输入区域支持多行文本。
- “比较 / Compare”触发真实 diff 计算。
- “示例 / Example”快速填充演示内容。
- 结果区域显示补丁段和行内差异数量。
- 逐行差异展示
EQUAL等 delta 标签。 - 页面底部保留库自检结果,但它不替代交互功能演示。
六、Kotlin/Native 到 ArkTS 的桥接
6.1 调用链
ArkTS Index.ets
│ compare(source, target)
▼
C++ N-API module
│ DiffSampleCompare(const char*, const char*)
▼
Kotlin/Native libdiff.so
│ compareTexts(source, target)
├─ diff(sourceLines, targetLines)
├─ diffInline(source, target)
├─ DiffRowGenerator(...)
└─ Patch.applyTo(sourceLines)
│ JSON string + DiffSampleFree()
▼
ArkTS JSON.parse -> page state -> result cards
库本身不直接暴露 ArkTS 类型。只有示例 Native 模块提供稳定的 C ABI,避免把 Kotlin 对象布局和生命周期泄漏到 C++。
6.2 Kotlin/Native 导出入口
@CName("DiffSampleCompare")
fun compareNative(source: String, target: String): CPointer<ByteVar> =
try {
textBuffer(comparisonJson(compareTexts(source, target)))
} catch (error: Throwable) {
textBuffer("{\"error\":${quote(error.message ?: error.toString())}}")
}
@CName("DiffSampleFree")
fun freeNativeChecks(result: CPointer<ByteVar>?) {
if (result != null) nativeHeap.free(result)
}
runNativeChecks 仍然保留,用于执行 9 个共享自检;compareNative 用于用户输入的实时比较。返回值统一是 UTF-8 JSON 字符串,C++ 复制完成后调用 DiffSampleFree 释放 Native 堆内存。
6.3 C++ N-API 参数转换
C++ 从 ArkTS 回调中读取两个字符串,调用 Native 导出函数,再把 JSON 转换成 ArkTS 字符串:
static napi_value Compare(napi_env env, napi_callback_info info) {
size_t argc = 2;
napi_value args[2] = {nullptr, nullptr};
napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
std::string source;
std::string target;
if (!ReadString(env, args[0], source) ||
!ReadString(env, args[1], target)) {
napi_throw_type_error(env, nullptr, "compare arguments must be strings");
return nullptr;
}
auto text = DiffSampleCompare(source.c_str(), target.c_str());
napi_value result = nullptr;
const auto status = napi_create_string_utf8(
env,
reinterpret_cast<const char*>(text),
NAPI_AUTO_LENGTH,
&result);
DiffSampleFree(text);
return status == napi_ok ? result : nullptr;
}
N-API 只处理字符串和函数调用;差异算法、补丁应用和结果构造全部留在 Kotlin/Native 侧。
6.4 导出表
动态库使用链接器 map,只导出需要被 C++ 调用的入口:
{
global:
DiffSampleCompare;
DiffSampleRunChecks;
DiffSampleFree;
local: *;
};
这样可以避免导出无关符号,也让 CMake 链接阶段能明确找到交互入口。
七、构建和签名
7.1 一键构建
使用 JDK 21 执行:
export JAVA_HOME="/path/to/jdk-21"
./scripts/build-openharmony.sh
脚本依次完成:
- 根库发布到
mavenLocal()。 - 根库 JVM 测试。
- 根库
ohosArm64编译。 - 独立
sharedJVM 测试。 nativeApp:prepareOhos,生成并复制libdiff.so和头文件。
也可以分步执行:
./gradlew -PopenharmonyOnly=true \
jvmTest compileKotlinOhosArm64 compileTestKotlinOhosArm64
(cd example && ./gradlew -PopenharmonyOnly=true \
:shared:jvmTest :nativeApp:prepareOhos)
产物位置:
build/classes/kotlin/ohosArm64/main/klib/
example/ohosApp/entry/libs/arm64-v8a/libdiff.so
example/ohosApp/entry/src/main/cpp/include/libdiff_api.h
动态库和生成头文件通过 .gitignore 排除,不进入源码提交。
7.2 准备 DevEco 工程
DevEco 对包含特殊字符或非 ASCII 字符的目录路径比较敏感。可以把应用复制到独立的签名目录:
python3 scripts/prepare-signing-project.py \
"$HOME/ohos_kotlin_multiplatform_diff_signing"
脚本会复制 ArkTS、C++、资源和配置,排除构建缓存、OHPM 模块、CMake 临时目录和证书文件。如果目标目录已经有 build-profile.json5,脚本会保留目标目录中的签名配置。
7.3 构建 HAP
DEVECO_HOME="/Applications/DevEco-Studio.app/Contents" \
DEVECO_SDK_HOME="/Applications/DevEco-Studio.app/Contents/sdk" \
./scripts/build-hap.sh "$HOME/ohos_kotlin_multiplatform_diff_signing"
脚本执行 ohpm install --all 和 hvigorw ... assembleHap。未配置签名时可以完成编译和打包流程,但最终设备安装需要在 DevEco 中配置有效签名。
7.4 ELF 依赖审计
对解压后的 HAP 原生库检查架构和强符号:
python3 scripts/check-native-deps.py \
/path/to/sdk/20/native \
/path/to/extracted-hap/libs/arm64-v8a
该脚本检查:
- 产物是否为 AArch64 ELF。
- OHOS SDK sysroot 和应用动态库是否提供所需符号。
- 是否存在未解析的强依赖。
这是静态检查,不能替代签名后的真机运行。
八、验证结果
8.1 已完成的自动化验证
当前适配已经完成以下检查:
| 检查 | 结果 |
|---|---|
| 根库 JVM 测试 | 通过 |
根库 compileKotlinOhosArm64 | 通过 |
根库 compileTestKotlinOhosArm64 | 通过 |
独立示例 shared:jvmTest | 通过,9 个场景 |
nativeApp:prepareOhos | 通过 |
| ArkTS 编译 | 通过 |
| CMake/Ninja 原生桥接编译 | 通过 |
| 未签名 HAP 构建流程 | 通过 |
测试结果中的“通过”只代表对应层级已经通过。JVM 测试通过不能替代 OHOS 编译,OHOS 编译通过也不能替代设备安装。
8.2 真机验收步骤
- 运行
scripts/build-openharmony.sh。 - 使用
prepare-signing-project.py复制签名工程。 - 在 DevEco Studio 中配置 API 20 ARM64 签名。
- 构建并安装 HAP。
- 修改原文和新文,点击“比较”。
- 确认页面显示新的补丁段、行内差异、逐行结果和应用补丁后的文本。
- 点击“示例”验证预置案例,再切换后台和恢复前台。
当前仓库没有提交任何设备证书、私钥、密码或签名产物。真机结果应由实际设备和签名环境记录。
九、关键文件
| 文件 | 作用 |
|---|---|
build.gradle.kts | 根工程 KMP 目标、版本、发布和 OHOS focused build。 |
settings.gradle.kts | AtomGit 相关仓库所需的插件与依赖仓库配置。 |
example/shared/.../AcceptanceChecks.kt | 共享库验收和交互比较模型。 |
example/nativeApp/.../NativeChecks.kt | Kotlin/Native C ABI 入口和 JSON 序列化。 |
example/nativeApp/.../shared-library.map | 动态库导出符号控制。 |
example/ohosApp/entry/src/main/cpp/napi_init.cpp | C++ N-API 参数读取和 Native 调用。 |
example/ohosApp/entry/src/main/ets/pages/Index.ets | 交互式文本输入、比较按钮和结果展示。 |
scripts/build-openharmony.sh | 发布、测试、OHOS 编译和 Native 库准备。 |
scripts/prepare-signing-project.py | 复制 DevEco 签名工程。 |
scripts/build-hap.sh | OHPM 安装和 HAP 构建。 |
scripts/check-native-deps.py | ARM64 ELF 和符号依赖检查。 |
docs/images/kotlin-multiplatform-diff-openharmony-demo.jpg | OpenHarmony 交互页面效果图。 |
十、适配中的关键经验
10.1 目标配置要放在库构建层
只在示例中声明 ohosArm64,无法生成真正可消费的库产物。目标必须和根库的 KMP 配置一起定义,示例再以发布坐标接入。
10.2 用 focused build 控制工具链下载
OpenHarmony 适配需要额外的 Native 编译器、LLVM 和 sysroot。openharmonyOnly 让开发者只配置 JVM/OHOS,普通构建则保留原有平台矩阵。
10.3 交互验证比单纯 PASS 更有说服力
自检列表适合发现回归,但不能表现用户真正如何使用 diff 库。交互页面把输入、算法计算和结果输出串在一起,可以直接观察补丁和行差异是否符合预期。
10.4 C ABI 只传字符串
通过 JSON 字符串传输结果,可以把 Kotlin 对象、Kotlin 集合和 Native 内存生命周期留在 Kotlin/Native 内部。C++ 和 ArkTS 只需要处理 UTF-8 字符串,桥接边界更稳定。
10.5 构建、打包和真机分层
应分别记录 JVM、KLIB、动态库、CMake、ArkTS、HAP 和设备结果。一个层级通过不能自动推出下一个层级通过。
10.6 AtomGit 地址必须全链路一致
源码文档、POM SCM、问题反馈入口和 Git remote 全部使用:
https://atomgit.com/oh-tpc/ohos_kotlin-multiplatform-diff
克隆和更新远端:
git clone https://atomgit.com/oh-tpc/ohos_kotlin-multiplatform-diff.git
cd ohos_kotlin-multiplatform-diff
git remote -v
十一、总结
本次适配没有复制一套鸿蒙专用 diff 算法,而是保留 commonMain 的公共实现,在构建层增加 ohosArm64,在示例层增加真实消费和原生桥接,在应用层提供可操作的文本比较页面。
最终链路如下:
commonMain diff API
│
▼
Kotlin/Native ohosArm64
│ libdiff.so
▼
C++ N-API
│ JSON
▼
ArkTS interactive page
│
├─ 输入原文和新文
├─ 显示补丁段和行内差异
├─ 显示逐行 DiffRow
└─ 显示应用补丁后的文本
适配完成后,OpenHarmony 使用者可以像其他 KMP 消费者一样通过 Maven 坐标接入库,也可以直接打开示例页面操作真实 diff 功能。项目地址为 AtomGit/oh-tpc/ohos_kotlin-multiplatform-diff。
参考文档
- OpenHarmony Kotlin Multiplatform 适配分支
- OpenHarmony 应用开发文档
- OpenHarmony N-API 文档
- OpenHarmony ArkTS 文档
- java-diff-utils 上游项目
环境: Kotlin 2.2.21-1.0.0 · HarmonyOS API 20 · Gradle 8.14.3 · JDK 21 · OpenHarmony ohosArm64
更多推荐




所有评论(0)