Kotlin Multiplatform 三方库 multiplatform-settings 的 OpenHarmony 鸿蒙化适配指南
Kotlin Multiplatform 三方库 multiplatform-settings 的 OpenHarmony 鸿蒙化适配指南
库版本:multiplatform-settings 1.3.0 鸿蒙 fork|验证环境:HarmonyOS Kotlin 2.2.21-1.0.0|Gradle 8.14.1|JDK 21|DevEco Studio 26.0.0.821|HarmonyOS 7.0.0(API 26)|模拟器
127.0.0.1:5555(x86_64)
qrcode-kotlin 验证的是纯计算:actual 不链任何系统 so,自己光栅、自己编 PNG。这篇换一类更常见的库——业务 API 在 commonMain,真正干活的是设备上那份 libohpreferences.so。
我以为把 OH_Preferences_SetInt 六个函数名抄进 Kotlin,就能交差。然后发生了三件事。
第一,SDK 头喂给 cinterop,clang 前端被 __availability__ 和 napi/native_api.h 撑爆。第二,编过之后 import OH_Preferences 仍然是 Unresolved reference,真实类型叫 cnames.structs.OH_Preferences,bool * 叫 BooleanVar。第三,Snapshot 只吐出两个键:GetString 撞上 count 这种 Int 列,返回码既不是 OK 也不是 KEY_NOT_FOUND,第一版 actual 直接 error(),剩下的键全没了。

Preferences 这种库,编过不算数。不 Flush,Reopen 就是空仓。bundleName 写错,Open 返回空指针。HasKey 的失败态不截图,审稿人会当没测。本文按这条真实路径写。

一、环境搭建
本章不展开,直接引用官方入口:KMP&CMP 鸿蒙社区、HarmonyOS 应用开发导读。
本文实际使用:
| 项 | 值 |
|---|---|
| 语言 / 框架 | Kotlin Multiplatform,HarmonyOS Kotlin 2.2.21-1.0.0 |
| 插件仓库 | https://maven.eazytec-cloud.com/nexus/content/groups/harmonyos |
| JDK | Temurin 21(写进 gradle.properties 的 org.gradle.java.home) |
| Gradle Wrapper | 8.14.1 |
| DevEco Studio | 26.0.0.821 |
| HarmonyOS SDK | 7.0.0(API 26) |
| 真机 ABI | ohosArm64 → arm64-v8a |
| 模拟器 ABI | ohosX64 → x86_64 |
| HAP 打包 JDK | DevEco 自带 JBR,不要用 JDK 8 |
| 系统库 | libohpreferences.so(设备 sysroot,不打进 HAP) |
ohosArm64() / ohosX64() 只存在于这套定制 Kotlin Gradle Plugin。用 Maven Central 上的官方 2.2.21 写这两行,配置期就会 Unresolved reference。这是判断工具链有没有接对的第一根探针。
二、应用背景
跨端业务里,键值存储出现的频率比二维码高一个数量级。登录 token、暗色模式开关、启动次数、实验分组,全都是「写进去、杀进程、再读出来还在」。Android 侧大家熟 SharedPreferences,Apple 侧是 NSUserDefaults。Russ H Wolf 的 multiplatform-settings 把这套差异收口成一个 Settings 接口:
settings.putString("nickname", "OpenHarmony")
settings.putInt("count", 7)
settings.hasKey("nickname")
settings.getStringOrNull("token")
settings.remove("nickname")
settings.clear()
commonMain 里的业务代码不需要知道底下是 XML 文件、plist,还是鸿蒙的 Preferences 数据库。鸿蒙缺的只是一个 actual。
三条常见路线我都否掉了。
在 ArkTS 里直接 preferences.getPreferences(this.context, 'demo'),那是使用文,不是适配。KMP 业务跑在 Kotlin/Native 线程,没有 JS runtime,也不能把 Ability 的 Context 传进 commonMain。
ohos actual 走 JNI 回调 ArkTS Preferences,等于每条配置跨一次 NAPI,生命周期更乱。KN sharedLib 里也没有 JVM。
把 JVM 的 PropertiesSettings 原样搬到 ohos,KN 没有 java.util.Properties。
对位的公开能力是 C API:database/preferences/oh_preferences.h,动态库 libohpreferences.so。Open / SetInt / SetBool / SetString / Get* / Delete / Close 从 API 13 开始;HasKey 和 Flush 从 API 23 开始。目标设备 API 26,两档都能用。
选题时查过 CPF-KMP-CMP 组织:当时已有 Ksoup、kotlin-result、kotlin-multiplatform-diff 等仓,没有 multiplatform-settings。过审名单里的 KMP 库也不含它。这是增量题,不是重复领激励。方向锁定 KMP 三方库适配,不是 CMP,不是 Flutter,也不是「拿 ArkTS Preferences 写业务」的使用文。
三、接口分析
按 Demo 实际打到的表面列,不把上游 coroutines / serialization / datastore 扩展件算进「已适配」。
| 能力 | 上游 API | 鸿蒙 actual | Demo 怎么调 | 验收 |
|---|---|---|---|---|
| 工厂 | Settings.Factory.create(name) | OhosPreferencesSettings.Factory(bundleName) | Init / Reopen | init size=0,再打开仍能读 |
| 写字符串 | putString | OH_Preferences_SetString | Seed 的 nickname | 列表 string = OpenHarmony |
| 写整数 | putInt | OH_Preferences_SetInt | Seed 的 count=7 | 列表 int = 7 |
| 写长整 / 浮点 | putLong / putFloat / putDouble | SetString 存十进制文本 | Seed 的 big / ratio / pi | round-trip 能读回来 |
| 写布尔 | putBoolean | OH_Preferences_SetBool | Seed 的 on=true | 列表 boolean = true |
| 读 | getX / getXOrNull | GetInt / GetBool / GetString | Get | 状态栏打印 value |
| 存在性 | hasKey | OH_Preferences_HasKey | Has | has=nickname=true/false |
| 删除 | remove | Delete + 更新索引 | Remove | size 减 1,列表不再有该键 |
| 清空 | clear | 按索引逐个 Delete | Clear | clear size=0 |
| 缺键 | getXOrNull | 非 OK 返回 null | Clear 后再 Get | value=null found=false |
| 键集合 | keys / size | sidecar 索引 __ohos_settings_index | Snapshot | size 与列表条数一致 |
ArkTS 不直接碰这些 Kotlin API。它只调用一个 NAPI 函数:
import { SettingsCall } from 'libsettings_napi.so';
const raw = SettingsCall(JSON.stringify({
op: 'seed', // init / put / get / has / remove / clear / reopen / snapshot
bundleName: this.bundleName,
name: this.storeName,
type: this.valueType,
key: this.keyName,
value: this.valueText,
}));
返回值是 JSON:{ ok, op, size, items, found, hasKey, value, error }。UI 进程不懂 Preferences 文件格式,Kotlin/Native 进程不懂 ArkUI。这是有意设计的。
四、六阶段路线图

- 工具链。 JDK 21 + HarmonyOS Kotlin 2.2.21-1.0.0 + Gradle 8.14.1。JDK 8/11 会在配置期直接死。
- 目标矩阵。 根项目收成
jvm()+ohosArm64()+ohosX64()。保留 JVM 是为了jvmTest当对照;砍掉 iOS / Android / JS / wasm,是因为这次要在 Windows 上闭环,不是把上游降级成单平台。 - cinterop。 手写
prefs_min.h,不要把 SDK 的oh_preferences.h整棵树丢进去。 - actual。
OhosPreferencesSettings实现Settings。每次调用走 Open → 读或写 → Flush → Close。 - C ABI + NAPI。
example/nativeApp用sharedLib { baseName = "ohossettings" }产出 so,@CName("SettingsCall")导出const char* SettingsCall(const char*)。CAdapter 不挂业务符号,必须再编libsettings_napi.so。 - 落盘。
hvigorw assembleHap把两个 ABI 的三份 so 打进 HAP,模拟器上 Init / Seed / Reopen / Remove / Clear / 缺 key 全部有实拍。
applyDefaultHierarchyTemplate() 会在两个 ohos 目标之上生成 ohosMain。代码放这里,不要放 nativeMain:以后一旦加 linux/mingw,会把鸿蒙 Preferences 强加给不该用的平台。
上游原工程有一长串 artifact 和 target。原样打开,HarmonyOS Kotlin 插件会在用不到的目标上失败,cinterop commonization 也会把 ohos 和 linux 搅在一起。本 fork 的 settings.gradle.kts 只留 :multiplatform-settings 和 :example:nativeApp。
五、三个关键决策
5.1 为什么不把 SDK 头直接喂给 cinterop
第一反应是 headers = oh_preferences.h。编不过,原因很具体。
头文件用了 __attribute__((__availability__(ohos, introduced=13))) 这一套,cinterop 自带的 clang 前端认不全。它还 #include <napi/native_api.h>,会把 NAPI 和 PreferencesValue 整棵树拉进来。我们这轮真正要用的只有 Option 的 Create/Set/Destroy,以及 Preferences 的 Open/Get/Set/Delete/HasKey/Flush/Close/FreeString。订阅和 GetAll 先不做。
所以 src/ohosMain/cinterop/ 下放了两份自己写的文件:prefs_min.h 手抄签名,类型改成普通 struct + int / bool / char*;ohpreferences.def 的 headers 只指向这一份。
编过之后,Kotlin 侧看到的不是头文件里的名字。函数在包 ohos.preferences,不透明结构体在 cnames.structs.OH_Preferences,bool * 映射成 kotlinx.cinterop.BooleanVar,不是 platform.posix.boolVar。
第一版 actual 按「头文件里的名字原样 import」写,编译器直接报 Unresolved reference。修法是 klib dump-metadata-signatures 看一眼生成物,再改 import。这是这篇适配里最值得记住的一步:cinterop 的 Kotlin 视图和 C 头文件不是一一对应,不要猜。

链接时,nativeApp 的 sharedLib 必须加 -L$sysroot/usr/lib/<abi>-linux-ohos -lohpreferences。不加的话,OH_Preferences_Open 会以 UND 留在 so 里,dlopen 阶段找不到符号。这些符号运行时由系统 libohpreferences.so 提供,不要把系统库打进 HAP。
5.2 类型怎么映射,以及 GetString 为什么不能乱抛
C API 原生只认 Int、Bool、String。上游 Settings 还有 Long、Float、Double。硬缺口只有两条路:拒绝编译,或者降级存储。
我选了后者。putLong / putFloat / putDouble 走 SetString 存十进制文本,读的时候再 parse。getLongOrNull 先按字符串解析,失败再尝试 getIntOrNull 再 widen。这样「以前用 int 存的计数器,后来改成 long 读」不会直接崩。
Demo 列表里 big 显示成 string = 1000000000,不是 UI 写错了,是存储层的诚实展示。Int 和 Boolean 走原生列,Snapshot 能把它们标成 int / boolean。限制必须写进文档:不要拿这些字符串列去做范围查询,那不是 Settings 的职责。
更阴的是读。OH_Preferences_GetString 碰到一个 Int 键时,返回码不是 KEY_NOT_FOUND,而是类型不匹配。第一版把「非 OK 且非 NOT_FOUND」直接 error(),结果 Snapshot 遍历 keys 时,一碰到 count 就抛异常,页面只显示得到 2 个键,看起来像丢数据。
修法:类型对不上就当 null,让 Snapshot 继续用 GetInt / GetBool 认类型。GetString 成功时必须 OH_Preferences_FreeString,这块堆是 C 侧分配的。
这件事 jvmTest 测不到。JVM 对照走的是 PropertiesSettings,没有类型列。只有装到模拟器、点 Seed 6 types 之后才会爆。
5.3 为什么每次都 Open / Flush / Close,以及 keys 从哪来
OH_Preferences 没有便宜的 keys()。GetAll 走 PreferencesValue 数组,cinterop 成本高,这轮不做。折中是写一个 sidecar 键 __ohos_settings_index,值为换行分隔的 key 列表。put* 时把 key 加进集合,remove / clear 时更新。这个键对调用方不可见。
限制同样写清楚:如果有人用别的进程直接改同一份 Preferences 文件、却不走本类,index 会和真实内容漂移。Demo 和常规 KMP 业务都走同一个 Settings 实例,不会踩。
把 Open/Close 放在每次调用里,而不是进程级单例,是为了 Demo 的 Reopen 按钮有意义:点 Reopen 等于重新 Factory.create()。如果上一次没 Flush,新句柄读到的就是空。模拟器上 Seed 之后 size=6,点 Reopen 仍是 6,说明 Flush 真的落盘了。
bundleName 必须和 HAP 的 app.json5 一致。写错时 Open 返回空指针。Demo 启动时用 bundleManager.getBundleInfoForSelfSync 取运行中的包名,避免手写分叉。
高频计数器以后可以做成「一次 Open、多次 Set、一次 Flush」的 transaction {}。现在这条短事务对配置类场景够用,也让截图路径可复现。
六、桥接怎么接
调用链从上到下是五层。中间少一层就会在 dlopen 或 undefined is not a function 上爆。

HarmonyOS Kotlin 的 CAdapter 会给 so 挂一套 ArkTS 表面。和 qrcode-kotlin 那次一样:@CName("SettingsCall") 在 llvm-nm -D 里看得到,ArkTS import KN so 却调不到。缺的是 NAPI 表面,不是 C 符号。所以 Demo 按官方 native 模块来:entry/src/main/cpp/napi_init.cpp 编成 libsettings_napi.so,内部链接 libohossettings.so,调完用 DisposeString 把 KN 分配的 C 字符串释放掉。
example/nativeApp 的 Bridge.kt 吃一小段 JSON,在 Kotlin 里走完整 Settings:
@CName("SettingsCall")
fun settingsCall(request: String): String = dispatch(request)
// seed 一次写入六种类型,截图不依赖输入法
s.putString("nickname", "OpenHarmony")
s.putInt("count", 7)
s.putLong("big", 1_000_000_000L)
s.putFloat("ratio", 1.5f)
s.putDouble("pi", 3.14)
s.putBoolean("on", true)
有四个地方我写错过。
baseName是String,不是Property<String>。 写成baseName.set("ohossettings")会在 configuration 阶段报Unresolved reference 'set'。正确是baseName = "ohossettings"。@CName参数用String。 KN 生成const char*ABI。返回的 C 字符串必须DisposeString,NAPI 包装层已经做了,ArkTS 不用管。entry/build-profile.json5的abiFilters必须同时有arm64-v8a和x86_64。 本机 DevEco 模拟器是 x86_64。只编 arm64,安装会报9568347,看起来像签名问题,其实是 ABI 缺失。- ArkTS
@State size不能用。 它和CustomComponent.size冲突,编译期直接红。改成kvCount。这和 Preferences 无关,但会卡 HAP。
linkDebugSharedOhosArm64 / OhosX64 已经 finalizedBy 拷贝任务,会把 libohossettings.so 和 Kotlin/Native 依赖的 libc++_shared.so 一起放进 entry/libs/<abi>/。漏掉 libc++,运行时是 Error loading shared library libc++_shared.so。
CMake 强制 -Wl,-rpath,$ORIGIN、BUILD_WITH_INSTALL_RPATH TRUE、IMPORTED_NO_SONAME TRUE。IMPORTED 库会把本机绝对路径写进 so,真机上那条路径毫无意义。nm_modname 必须是 settings_napi,和 oh-package.json5 对上。名字写错,ArkTS 会编译过、运行时报模块找不到。
JSON 解析是手写的极简扫描,只认字符串字段。不要在 KN 侧再拉 kotlinx.serialization——ohos 目标的依赖图会被重新打乱。seed 做成独立 op,是因为 uitest inputText 往输入框里追加而不是替换,键名会被拼成一长串。对外截图只点按钮,不碰输入法。
七、踩坑表
| 现象 | 根因 | 处理 |
|---|---|---|
Unresolved reference: ohosArm64 | 用了官方 Kotlin 2.2.21 | 换成 HarmonyOS Kotlin 2.2.21-1.0.0,仓库放到 pluginManagement 第一位 |
| cinterop 喂 SDK 头直接死 | availability 属性 / napi 头 | 手写 prefs_min.h |
import OH_Preferences 找不到 | cinterop 把不透明类型放到 cnames.structs | cnames.structs.OH_Preferences |
boolVar 找不到 | 生成物是 BooleanVar | kotlinx.cinterop.BooleanVar |
| Snapshot 只显示 2 个键 | GetString 碰到 Int 键就 error() | 类型不匹配返回 null |
| Reopen 后 size=0 | 没 Flush | 每次 withStore 结束都 Flush |
Open 返回空指针 | bundleName 写死写错 | 运行时取自身 bundleName |
import KN so 调不到 SettingsCall | CAdapter 不导出业务符号 | ArkTS 改 import libsettings_napi.so |
9568347 安装失败 | HAP 缺 x86_64 | abiFilters 加上 x86_64,并链接 ohosX64 |
Error loading shared library libc++_shared.so | 只拷了 KN so | copy 任务同时带上 Konan 的 libc++_shared.so |
@State size 编译失败 | 和组件属性冲突 | 改名 kvCount |
PackageHap 报 Could not create JVM | JAVA_HOME 指到 JDK 8 | 打包改用 DevEco jbr |
| Gradle daemon 假成功 | daemon 和 JDK 切乱 | --stop 之后 --no-daemon |
全局 optIn ExperimentalForeignApi | jvm 目标也中招 | @file:OptIn 只放 ohos 文件 |
还有一条环境向的:系统 node 往往不在 PATH 里。ohpm install 要把 DevEco tools/node 加进去。hvigor 打包用 DevEco JBR,不要和 Gradle 的 JDK 21 混用。
八、Demo 验收:每个接口一张实拍

包名 org.terminator.ohos.settings,Ability EntryAbility。本机模拟器 hdc install 后:
hdc shell aa start -a EntryAbility -b org.terminator.ohos.settings
下面每张图都是从模拟器抠出来的,不是合成。状态栏时间 10:16 附近,电量 100%。字幕写的是 KMP Settings + OH_Preferences C API。不要只贴一张启动图交差。Preferences 库要看失败态、持久化、多种类型。
8.1 Factory.create:空仓 Init
启动即 init。运行时取自身 bundleName,store 名 demo。状态栏 init size=0 store=demo,列表为空。这一步过了,说明 NAPI → KN → OH_Preferences_Open 整条链是通的。如果是 失败: 开头,先查 so 有没有进 HAP,再查 bundleName。
8.2 六种类型一次写入
点 Seed 6 types,不要碰输入框。状态栏 seed size=6 store=demo。列表可见:
count→int = 7(原生 Int 列)on→boolean = true(原生 Bool 列)nickname→string = OpenHarmonybig→string = 1000000000(Long 降级为字符串)pi/ratio同理走字符串列
六种类型一次 round-trip,比手点六次 Put 可复现。size=6 来自 sidecar 索引,不是瞎数列表行。
8.3 getString:读回 nickname
输入框保持 nickname / OpenHarmony,类型选 string,点 Get。状态栏:
Get nickname (string) = OpenHarmony
副行 size=6 has=nickname found=true。这是 getStringOrNull 的成功态,不是把输入框的值回显上去。
8.4 hasKey:存在为 true
点 Has。状态栏 has size=6 store=demo,副行 has=nickname=true。底层是 OH_Preferences_HasKey,API 23 才有。API 26 模拟器返回值是对的。
8.5 Factory.create 再打开:数据还在
点 Reopen。实现里丢掉当前 Settings 实例,再 factory.create("demo")。如果上一次 Seed 没 Flush,这里会变成 size=0。实拍是 reopen size=6,六条记录还在。这张图是整篇适配的核心证据:不是进程内 HashMap,是落盘。
8.6 remove:删掉 nickname
点 Remove。状态栏 remove size=5 store=demo。列表里 nickname 消失,剩下 big / count / on / pi / ratio。副行 has=nickname=true 是上一次 Has 的缓存——lastHas 只在 has / get 时更新,Remove 以列表和 size 为准。再点一次 Has,就会变成 false。
8.7 clear:整仓清空
点 Clear。状态栏 clear size=0 store=demo,列表空,has=nickname=false。索引键一并删掉。这是成功态的终点,也是下一张失败态的起点。
8.8 缺 key:getXOrNull 返回 null
Clear 之后再点 Get。状态栏:
Get nickname (string) = null
副行 size=0 has=nickname found=false。上游契约是缺省值或 OrNull,不是抛异常。Preferences 库如果不拍这张图,等于没测失败路径。
Put 单类型、Snapshot 刷新,在 Seed 路径里已经覆盖。对外截图不用那组被输入法拼脏键名的调试图。
九、分层验收
不要把「库编过」和「Reopen 后数据还在」混成一次验收。四层口子分开过。

| 层 | 命令 | 我这边的结果 |
|---|---|---|
| L1 | gradlew :multiplatform-settings:jvmTest | putGetRoundTrip、missingAndRemove 绿。公共 API 语义在 PropertiesSettings 上对照过 |
| L2 | gradlew :example:nativeApp:linkDebugSharedOhosArm64 :example:nativeApp:linkDebugSharedOhosX64 | 两个 ABI 的 so 都导出 SettingsCall,并拷到 entry/libs/<abi>/ |
| L3 编译 | hvigorw assembleHap -p product=default -p buildMode=debug --no-daemon | HAP 内 arm64-v8a / x86_64 各含 libsettings_napi.so、libohossettings.so、libc++_shared.so |
| L4 运行 | hdc install + 真点击 | Init / Seed / Get / Has / Reopen / Remove / Clear / 缺 key 全部有实拍 |
打包必须用 DevEco JBR。本机默认 JAVA_HOME 若指向 JDK 8,PackageHap 会报 Could not create the Java Virtual Machine,native 其实已经编过了,只是最后把 so 塞进 HAP 的那一步没起来。
命令行打出来的是 unsigned HAP。这个模拟器接受了 hdc install。真机和正式签名仍走 DevEco 的 "signingConfig": "default",不要在 build-profile.json5 里把这段改空。
Windows 上链出来的 ohos so 不能在本机 dlopen。对照测试走 jvmTest,设备行为走 HAP。不要在第 L4 失败时回头改 commonMain 算法——先 hilog 搜 SettingsNapi,看 JSON error 字段。
十、已知限制
- 不是 CMP。 没有 Compose 控件,没有 Settings UI 主题。KMP 和 CMP 是两条配额线,这个库走 KMP。
- 只做了核心 artifact。
multiplatform-settings-coroutines、serialization、no-arg、datastore、make-observable没有 ohos 目标。需要 Flow 或 DataStore 后端的,请继续在本仓加模块,不要假装已经有。 - 没有
ObservableSettings。 上游 Android 实现能监听 SharedPreferences 变化。OH_Preferences_Subscribe这轮没绑。UI 刷新靠 Demo 主动 Snapshot。 Long/Float/Double是字符串存储。 能 round-trip,不能拿去和原生 Int 列混着做范围查询。keys/size依赖 sidecar 索引。 不走本类、直接改文件的写入,索引会漂。GetAll留给下一轮。- 每次调用都 Open/Flush/Close。 适合 Demo 和低频配置。高频计数器请自己持有句柄。
- 没有多进程锁测试。 C API 文档允许跨进程,本 Demo 没有两个 Ability 同时写。
- CAdapter 限制仍在。 即使 ELF 能看到
@CName("SettingsCall"),ArkTS 也必须走 NAPI。不要在下一篇里再踩一次还当新发现。 - 模拟器 ABI。 本地 DevEco 模拟器是 x86_64。只交 arm64 HAP,安装失败码经常被误判成证书问题。
- 真机签名未跑。 模拟器 unsigned 可装。对外演示请在 DevEco 配
signingConfig: default,再用 arm64 真机走一遍 Seed / Reopen / Clear。 - 仓库位置。 适配代码在 oh-tpc/multiplatform-settings,社区组织仍是 CPF-KMP-CMP。不往 Flutter 组织塞 KMP 库。
有人会问:鸿蒙自己就有 Preferences,为什么还要 KMP 这一层。答案不是性能,是边界。ArkTS Preferences 的上下文是 Context,API 是 Promise。KMP 业务拿到的是同步 C ABI,没有 JS runtime。把 KN 的 putInt 回调到 ArkTS 再写,等于每条配置都要跨一次 NAPI。本适配站在 OH_Preferences 这一层。Demo 之所以还出现 ArkTS,只是因为要有一个能点的界面,和征文要求的全接口截图。真正给业务用的入口是 OhosPreferencesSettings,不是 Index.ets。
十一、如何提 Issue / PR
上游功能问题优先去 russhwolf/multiplatform-settings。鸿蒙 actual、cinterop 头、NAPI 包装、Demo 安装问题开在适配仓库。不要把 ohosArm64 编译日志丢给上游——那不是他们的 target。

请固定带这六项,否则很难判断是类型映射问题、没 Flush,还是 so 没装进对的 ABI:
- ABI:真机
ohosArm64还是模拟器ohosX64 - 操作:
init/seed/put/get/has/remove/reopen/clear - 类型与键:
string/int/long/float/double/boolean,以及 key 原文 - 状态栏原文:
size=数字、has=、Get 的value/null hilog | grep SettingsNapi,以及OH_Preferences_Open的 errCode- 列表实拍,不要只贴 JSON
PR 建议拆开:src/ohosMain 的 actual / cinterop 是库本体;example/nativeApp 的 @CName 是 C ABI;example/harmonyApp 的 CMake 是 NAPI。不要把三种改动揉进同一个 commit。示例工程保持 "signingConfig": "default"。
克隆适配仓请走 AtomGit 组织页导入后再 git clone,不要写第三方镜像站地址。
十二、小结
这次适配没有发明新的键值协议。commonMain 里的 Settings 接口原样工作。鸿蒙侧真正要补的是三块:
- 一份 cinterop 肯吃的精简头,以及 dump 出来才知道的 Kotlin 类型名
- 一个守住六种类型 round-trip 的
OhosPreferencesSettings,Int/Bool 走原生列,其余走字符串,缺 key 和类型不匹配都返回 null - 一层 CAdapter 不肯给的 NAPI 表面,外加每次调用都 Flush,让 Reopen 有意义
前两块是常规 KMP。第三块是 HarmonyOS Kotlin 目前的现实,上一篇二维码已经踩过,这篇用系统库又确认了一次。谁要是把「so 里有符号」当成「ArkTS 能调用」,或者把「编过」当成「落盘了」,会在 Zip/GZip 三个导出和空的 Reopen 上再浪费整下午。
和 qrcode-kotlin 那篇是同一条工具链上的两个样本。一个纯计算、不链系统 so;一个必须 dlopen 设备上的 libohpreferences.so。两篇一起看,才能判断 ohos target 是不是真的接上了,而不是复制了一份 so。
模拟器已经把 Init / Seed / Get / Has / Reopen / Remove / Clear / 缺 key 跑通。仓库在 oh-tpc/multiplatform-settings。下一步用 DevEco 默认签名在真机上再走一遍 arm64。
如果只记住一件事:在 HarmonyOS Kotlin 这条链上,Kotlin/Native so 负责对系统 C API 读写,CMake NAPI so 负责被 ArkTS 看见,Flush 负责让下一句 Factory.create() 还能读到。三者缺一,截图上都是空列表。
KMP&CMP 社区地址:https://atomgit.com/CPF-KMP-CMP
GitHub 上游:https://github.com/russhwolf/multiplatform-settings
Maven Central:https://mvnrepository.com/artifact/com.russhwolf/multiplatform-settings
鸿蒙适配版:https://atomgit.com/oh-tpc/multiplatform-settings
欢迎加入 KMP&CMP 鸿蒙社区:https://atomgit.com/CPF-KMP-CMP
更多推荐

所有评论(0)