开源鸿蒙平台 KMP 三方库 KStore 适配全流程:从 ohosArm64 target 到真机文件持久化验证
大家好,我是熊猫钓鱼,欢迎大家点赞和关注,谢谢!

欢迎加入 KMP/CMP 鸿蒙化社区:https://atomgit.com/CPF-KMP-CMP
适配后仓库地址(AtomGit):https://atomgit.com/oh-tpc/ohos_kstore
Kotlin Multiplatform 里有一类需求,看起来不起眼,但几乎每个 App 都会撞上:把一个对象存到磁盘上,下次启动再原样读回来。用户设置、未提交的草稿、离线缓存、上次停留的位置,最后都会落到这件事上。KStore 就是解决这件事的轻量方案——一个基于 kotlinx.coroutines、kotlinx.serialization 和 kotlinx.io 的 KMP 存储库,提供带互斥锁的读写、内存缓存、默认值和迁移支持。
但它和所有 KMP 库一样,第一道坎是同一个:官方发布物里没有 ohosArm64 的产物。你在 commonMain 里写下 implementation("io.github.xxfast:kstore-file:1.1.0"),Gradle 解析依赖时会直接告诉你找不到匹配的 variant。不是网络问题,也不是版本问题——这个库从来没有为 OpenHarmony 这个 target 编译过。
摘要
本文完整记录了将 KMP 存储库 KStore 适配到 OpenHarmony 的实战过程。文章从选型动机出发,说明 KStore 为何适合作为适配样本;随后梳理了库的源码结构,并分六步展开适配:接入 HarmonyOS Kotlin 定制版、声明 ohosArm64 target、处理沙箱路径、导出能力给 ArkTS、打包 HAR 接入鸿蒙工程、真机操作验证。文章重点剖析了本次适配真正的坑——沙箱路径问题,并给出了"目录从应用层拿、再传给 KMP 层"的解决方案。最后附上八条踩坑清单与小结,为同类 KMP 库的鸿蒙化适配提供参考。
目录
- 一、为什么挑 KStore 下手
- 二、先摸清库的源码结构,再动手
- 三、第一步:接入 HarmonyOS Kotlin 定制版
- 四、第二步:声明 target,然后让编译器告诉你缺什么
- 五、第三步:沙箱路径——本次适配真正的坑
- 六、第四步:把能力导出给 ArkTS
- 七、第五步:打包 HAR 并接入鸿蒙工程
- 八、第六步:操作验证
- 九、踩坑清单
- 十、小结
一、为什么挑 KStore 下手
KStore 特别适合作为一次"从 0 到 1"的适配样本,原因有三个。
第一,它足够小。kstore 加 kstore-file 两个模块的核心代码不到四百行,KStore<T> 的公开 API 只有 get / set / update / delete / reset / updates 六个入口,边界非常清晰,不会有读不完的源码树。
第二,它的难点集中且极具代表性。KStore 的代码里没有任何"鸿蒙特有问题",真正卡住适配的是一个隐含假设:它要求调用方显式给一个 Path,并且默认这个路径是可写的。Android 上你写 context.filesDir,iOS 上你写 NSDocumentDirectory,Desktop 上你写 user.home——各平台都有一个"众所周知"的落脚点。而 OpenHarmony 应用的沙箱目录是运行时由框架分配的,编译期不可知,KMP 侧更拿不到。这个缺口和我们之前适配 kotlinx-datetime 时遇到的"沙箱里没有 /usr/share/zoneinfo"是同一类问题:库假设平台会提供某个东西,而鸿蒙沙箱不提供。
第三,适配完成后真机上一眼就能验证。页面上显示"磁盘上的值",杀进程、重启、再打开——值还在,就说明落盘链路真的通了,不需要复杂的交互就能确认结果。
这篇文章记录的是完整过程:从 fork 源码、接入 HarmonyOS Kotlin 定制版、声明 ohosArm64 target,到处理沙箱路径、导出符号给 ArkTS、打成 HAR 放进鸿蒙工程,最后在真机上跑通并验证。
二、先摸清库的源码结构,再动手
动手改之前先看清楚这个库怎么组织,否则很容易在错误的地方加代码。KStore 1.1.0 的源码分成三块:
kstore(common 模块) 放的是全部对外 API 和与平台无关的逻辑。KStore<T> 内部用一个 Mutex 串行化读写,用一个 MutableStateFlow 做内存缓存,updates 这个 Flow 每次订阅都会先强制从磁盘重读一次。这些逻辑不依赖任何平台能力,因此在所有 target 上共用同一份实现。这也是适配工作量看起来不大的原因——绝大部分代码不需要碰。
kstore-file 放的是"把值落到文件"这件事。核心是 FileCodec<T>:
public class FileCodec<T : @Serializable Any>(
private val file: Path,
private val tempFile: Path,
private val json: Json,
private val serializer: KSerializer<T>,
) : Codec<T>
它用的是 kotlinx.io 的 SystemFileSystem,不是 okio——这一点值得留意,因为社区里 kotlinx-io 本体已经有人在 OpenHarmony 上适配过了,KStore 恰好站在这块地基上。
FileCodec 的写入策略是"先写临时文件、再原子改名":值写到 tempFile,成功后用 SystemFileSystem.atomicMove 把它移到目标文件;失败则删掉临时文件。临时文件由 uniqueTempFile(file) 生成,形如 <file>.<uuid>.temp,和目标文件在同一个目录下——这不是随手写的,因为 rename 只有在同一个文件系统内才是原子的。这个细节后面会变成一个坑。
kstore-storage 是浏览器侧的实现(localStorage),和 OpenHarmony 无关,本次不涉及。
还有一处关键设计,在 kstore/src/commonMain/kotlin/io/github/xxfast/kstore/utils/Dispatchers.kt:
internal expect val StoreDispatcher: CoroutineDispatcher
KStore 把"文件 IO 跑在哪个调度器上"抽象成了 expect。而它的 actual 是按平台逐个写的——androidMain、appleMain、desktopMain、jsMain、wasmJsMain、linuxMain、windowsMain 各有一份 Dispatchers.kt,没有 nativeMain 兜底。这意味着每新增一个 target,就必然缺一个 actual。这正是我们要补的第一块。
三、第一步:接入 HarmonyOS Kotlin 定制版
这是整个适配的前置条件,也最容易被忽略。ohosArm64() 这个 target 在 Kotlin 官方主线发行版里并不存在,它是 OpenHarmony 适配生态里的定制能力。如果你用官方 Kotlin 插件直接写 ohosArm64(),Gradle 会报 Unresolved reference,因为插件根本不认识这个 target 名字。
所以第一步是把工程使用的 Kotlin 切到 HarmonyOS Kotlin 定制版(当前对应 Kotlin 2.2.21-1.0.0 这一发行线),并在 settings.gradle.kts 里把插件仓库指向 KMP/CMP 鸿蒙化发行版对应的仓库:
// settings.gradle.kts
pluginManagement {
repositories {
// HarmonyOS Kotlin 定制版插件仓库(地址见 CPF-KMP-CMP 发布说明)
maven("https://atomgit.com/CPF-KMP-CMP")
gradlePluginPortal()
mavenCentral()
google()
}
}
dependencyResolutionManagement {
repositories {
mavenCentral()
google()
}
}
这里有一个很容易踩的版本错配。KStore 上游的 gradle/libs.versions.toml 里写的是:
kotlin = "2.4.10"
kotlinx-coroutines = "1.11.0"
kotlinx-serialization = "1.11.0"
kotlinx-io = "0.9.1"
这些数字不能照抄。定制版 Kotlin 与它配套的 coroutines / serialization / io 版本是绑定的,必须整体降到定制版对应的版本线上,具体版本号以 CPF-KMP-CMP 的发布说明为准。如果你只把 kotlin 改掉、其余保留上游版本,配置阶段就会因为 klib 版本不兼容而失败。
环境上我用的是 DevEco Studio 26.0.0 Release + JDK 21 + Gradle 8.14.1,真机 ROM 为 HarmonyOS 6.1 以上。定制版 Kotlin 与 DevEco Studio 之间有配套关系,不建议随意混搭。
还有一个环境上的坑要先处理掉:KStore 上游同时声明了 Android target,用的是 com.android.kotlin.multiplatform.library 插件,要求 AGP 9.3.1 和 compileSdk = 36。如果构建机上没有装 Android SDK,./gradlew :kstore:compileKotlinOhosArm64 会在配置阶段就报错,而不是在编译 Kotlin/Native 时报错。先装好 Android SDK,或者临时把两个模块里的 android { } 块注掉再编译。
我的开发界面如下:

调试界面如下:

四、第二步:声明 target,然后让编译器告诉你缺什么
插件就位之后,在 kstore/build.gradle.kts 和 kstore-file/build.gradle.kts 里补上 target 声明。这里我刻意没有一次性写完所有适配代码,而是先只加 target,把"缺什么"交给编译器报出来——这是 KMP 适配里最高效的做法,比对着源码猜要准得多。
// kstore/build.gradle.kts
kotlin {
explicitApi()
applyDefaultHierarchyTemplate()
// ……上游已有的 android / desktop / js / wasmJs / apple / linuxX64 / windows 保持不变
// 本次新增:OpenHarmony
ohosArm64()
}
注意 applyDefaultHierarchyTemplate() 这一行是上游本来就有的。ohosArm64 作为 native target 会被并入 nativeMain 分组——但前面说过,KStore 的 StoreDispatcher 是按平台叶子逐个实现的,没有 nativeMain 兜底,所以该补的还是 ohosArm64Main 这一层。
kstore-file 模块同样加上 ohosArm64()。至于 kstore-storage(浏览器侧),不声明即可。
声明完成后跑一次编译:
./gradlew :kstore:compileKotlinOhosArm64
Kotlin/Native 的编译任务命名规则是 compileKotlin<首字母大写的 target 名>。第一次编译会失败,报出:
Expected declaration 'StoreDispatcher' has no actual declaration in module <kstore> for target ohosArm64
这就是我们等的那条错误。补上实现——直接照 linuxMain 那一份写,OpenHarmony 的运行时是 POSIX 兼容的,文件 IO 的调度器语义和 Linux 一致:
// kstore/src/ohosArm64Main/kotlin/io/github/xxfast/kstore/utils/Dispatchers.kt
package io.github.xxfast.kstore.utils
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.IO
internal actual val StoreDispatcher: CoroutineDispatcher = Dispatchers.IO
叶子 source set(ohosArm64Main)在任何情况下都会被 Kotlin 自动创建,所以这个文件直接放进去就行,不需要额外改 sourceSets 块。
再编一次,通过。target 就算接上了。
五、第三步:沙箱路径——本次适配真正的坑
编译通过之后,真正的麻烦才开始。写一个最小验证:
val store = storeOf<Note>(file = Path("kstore_demo.json"))
store.set(Note(text = "hello", revision = 1))
println(store.get()) // 期望拿到刚写进去的值
结果是:要么直接抛 FileNotFoundException,要么"set 看起来成功了,get 却返回 null"。数据根本没有落到你以为的地方。
根因在第二节里已经埋下伏笔。Path("kstore_demo.json") 是一个相对路径,SystemFileSystem 会按进程的当前工作目录去解析它。而 OpenHarmony 应用进程的工作目录不是一个可依赖的可写数据目录;应用真正该用的位置是沙箱里的 files 目录,形如 /data/storage/el2/base/haps/entry/files——这个路径在编译期未知,由框架在运行时按 bundle 名和模块名分配,KMP 侧既猜不出来,也没有一个跨平台 API 能拿到它。
这不是 KStore 的 bug,而是"平台没有提供它期望的数据源"。解决方案只有一条:目录从应用层拿,再传给 KMP 层。 鸿蒙的应用上下文里 context.filesDir 就是这个目录,而 ArkTS 侧的 getContext(this) 能直接拿到它。
这里有个设计上的取舍值得说明。最直觉的做法是改 KStore 的公共 API,在 commonMain 里加一个 expect fun filesDir(): String。但这条路会立刻反噬:kstore 和 kstore-file 声明了 android、iOS、jvm、js、wasmJs、linux、windows 一长串 target,在 commonMain 里加一个 expect,就意味着每一个 target 都要补一个 actual,否则整条构建链全红。为一个平台的需求去改所有平台的代码,明显不划算。
所以我选择不动库的公共 API:KStore 只加 target 和 StoreDispatcher 的 actual,路径注入放在一个只编译 ohosArm64 的桥接模块里。库本身保持"调用方给路径"的原始模型,鸿蒙侧的路径来源由应用层决定。
六、第四步:把能力导出给 ArkTS
新建一个 kstore-ohos 模块,只声明 ohosArm64,依赖 kstore-file:
// kstore-ohos/build.gradle.kts
plugins {
kotlin("multiplatform")
}
kotlin {
ohosArm64 {
binaries.sharedLib {
baseName = "kmpkstore"
}
}
sourceSets {
val ohosArm64Main by getting {
dependencies {
implementation(project(":kstore-file"))
implementation(libs.kotlinx.coroutines)
implementation(libs.kotlinx.serialization.json)
}
}
}
}
这里不需要 export KStore 的符号——我们只导出自己写的 @CName 函数,KStore 的代码只要被链进动态库就够了。序列化插件也不用在这个模块里单独加,上游根 build.gradle.kts 已经对 allprojects 应用了 org.jetbrains.kotlin.plugin.serialization。
桥接层的核心是这样:
// kstore-ohos/src/ohosArm64Main/kotlin/ohos/kstore/bridge/KStoreBridge.kt
package ohos.kstore.bridge
import io.github.xxfast.kstore.KStore
import io.github.xxfast.kstore.file.storeOf
import kotlinx.coroutines.runBlocking
import kotlinx.io.files.Path
import kotlinx.serialization.Serializable
import kotlin.native.CName
@Serializable
data class Note(val text: String, val revision: Int)
private var store: KStore<Note>? = null
// 沙箱目录由 ArkTS 侧通过 context.filesDir 注入。
// OpenHarmony 沙箱内没有可猜的标准数据目录,不能像 Desktop 那样用 user.home
@CName("kmp_kstore_init")
fun kstoreInit(filesDir: String): String = runCatching {
val file = Path("$filesDir/kstore_demo.json")
store = storeOf<Note>(file = file)
file.toString()
}.getOrElse { "ERR: ${it.message}" }
@CName("kmp_kstore_save")
fun kstoreSave(text: String): String {
val s = store ?: return "ERR: not initialized"
return runCatching {
// update 会先从磁盘读回旧值,再写回新值——
// revision 能否累加,就是"读回来的到底是不是磁盘上的东西"的证据
runBlocking {
s.update { prev -> Note(text = text, revision = (prev?.revision ?: 0) + 1) }
}
"rev=${runBlocking { s.get() }?.revision}"
}.getOrElse { "ERR: ${it.message}" }
}
@CName("kmp_kstore_load")
fun kstoreLoad(): String {
val s = store ?: return ""
return runCatching {
val note = runBlocking { s.get() } ?: return ""
"rev=${note.revision}|${note.text}"
}.getOrElse { "" }
}
@CName("kmp_kstore_clear")
fun kstoreClear(): String {
val s = store ?: return "ERR: not initialized"
return runCatching {
// set(null) 会把目标文件删掉,而不是写一个空文件
runBlocking { s.set(null) }
"ok"
}.getOrElse { "ERR: ${it.message}" }
}
save 里用 update 而不是 set,是刻意为了验证。update 会先 read 再 write,如果 revision 能跨进程累加(第一次 1、重启后再存变 2),就说明旧值确实是从磁盘读回来的,而不是被内存缓存或硬编码兜住了。
再补 C++ 侧的 NAPI 注册:
// src/main/cpp/napi_init.cpp
#include "napi/native_api.h"
#include <string>
extern "C" const char* kmp_kstore_init(const char* filesDir);
extern "C" const char* kmp_kstore_save(const char* text);
extern "C" const char* kmp_kstore_load();
extern "C" const char* kmp_kstore_clear();
static std::string GetStringArg(napi_env env, napi_value value) {
size_t len = 0;
napi_get_value_string_utf8(env, value, nullptr, 0, &len);
std::string out(len + 1, '\0');
napi_get_value_string_utf8(env, value, out.data(), out.size(), &len);
out.resize(len);
return out;
}
static napi_value KstoreInit(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);
std::string dir = GetStringArg(env, args[0]);
napi_value result;
napi_create_string_utf8(env, kmp_kstore_init(dir.c_str()), NAPI_AUTO_LENGTH, &result);
return result;
}
static napi_value KstoreSave(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);
std::string text = GetStringArg(env, args[0]);
napi_value result;
napi_create_string_utf8(env, kmp_kstore_save(text.c_str()), NAPI_AUTO_LENGTH, &result);
return result;
}
static napi_value KstoreLoad(napi_env env, napi_callback_info info) {
napi_value result;
napi_create_string_utf8(env, kmp_kstore_load(), NAPI_AUTO_LENGTH, &result);
return result;
}
static napi_value KstoreClear(napi_env env, napi_callback_info info) {
napi_value result;
napi_create_string_utf8(env, kmp_kstore_clear(), NAPI_AUTO_LENGTH, &result);
return result;
}
EXTERN_C_START
static napi_value Init(napi_env env, napi_value exports) {
napi_property_descriptor desc[] = {
{ "init", nullptr, KstoreInit, nullptr, nullptr, nullptr, napi_default, nullptr },
{ "save", nullptr, KstoreSave, nullptr, nullptr, nullptr, napi_default, nullptr },
{ "load", nullptr, KstoreLoad, nullptr, nullptr, nullptr, napi_default, nullptr },
{ "clear", nullptr, KstoreClear, nullptr, nullptr, nullptr, napi_default, nullptr },
};
napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc);
return exports;
}
EXTERN_C_END
static napi_module demoModule = {
.nm_version = 1,
.nm_flags = 0,
.nm_filename = nullptr,
.nm_register_func = Init,
.nm_modname = "kmpkstore",
.nm_priv = nullptr,
.reserved = { 0 },
};
extern "C" __attribute__((constructor)) void RegisterModule(void) {
napi_module_register(&demoModule);
}
HAR 模块的入口声明:
// Index.d.ts
export const init: (filesDir: string) => string;
export const save: (text: string) => string;
export const load: () => string;
export const clear: () => string;
// Index.ets
import nativeLib from 'libkmpkstore.so';
export const init: (filesDir: string) => string = nativeLib.init;
export const save: (text: string) => string = nativeLib.save;
export const load: () => string = nativeLib.load;
export const clear: () => string = nativeLib.clear;
七、第五步:打包 HAR 并接入鸿蒙工程
编译出来的动态库要按鸿蒙的约定放好目录,才能被正确打进 HAR:
src/main/
├── cpp/
│ ├── napi_init.cpp
│ └── types/libkmpkstore/Index.d.ts
├── ets/Index.ets
└── libs/arm64-v8a/libkmpkstore.so
HAR 模块的 oh-package.json5:
{
"name": "ohos_kstore",
"version": "1.0.0",
"description": "KStore OpenHarmony 适配",
"main": "Index.ets",
"types": "Index.d.ts"
}
接入前建议先确认符号确实导出了,这一步能省掉大量排查时间:
llvm-nm -D libkmpkstore.so | findstr kmp_kstore
能看到 T kmp_kstore_init、T kmp_kstore_save 这几行,说明 Kotlin/Native 侧的导出是成功的。
然后在鸿蒙工程的页面里把沙箱目录注入进去:
// Index.ets(页面)
import { init, save, load, clear } from 'ohos_kstore';
import { common } from '@kit.AbilityKit';
@Entry
@Component
struct Index {
@State filePath: string = '';
@State text: string = '';
@State persisted: string = '(空)';
aboutToAppear() {
// 沙箱目录只能从应用上下文拿:编译期未知,KMP 侧更拿不到
const filesDir: string = (getContext(this) as common.UIAbilityContext).filesDir;
this.filePath = init(filesDir);
this.refresh();
}
refresh() {
const v: string = load();
this.persisted = v === '' ? '(空)' : v;
}
build() {
Column({ space: 12 }) {
Text('KStore on OpenHarmony')
.fontSize(18).fontWeight(FontWeight.Bold)
Text(`文件:${this.filePath}`)
.fontSize(12).fontColor('#666666')
TextInput({ placeholder: '输入要持久化的内容' })
.onChange((v: string) => { this.text = v })
Row({ space: 12 }) {
Button('保存').onClick(() => { save(this.text); this.refresh(); })
Button('清空').onClick(() => { clear(); this.refresh(); })
}
Text(`磁盘上的值:${this.persisted}`)
.fontSize(20).fontColor('#0A59F7')
}
.width('100%').height('100%')
.justifyContent(FlexAlign.Center)
.padding(24)
}
}
八、第六步:操作验证
部署后,页面会先显示 KMP 侧返回的实际文件路径,以及 (空)。
执行运行如下:

输入 hello kstore 点保存,磁盘值变为 rev=1|hello kstore。此时杀掉应用进程再重新打开,页面直接显示 rev=1|hello kstore——这一条就证明了数据是从磁盘读回来的。

接着再输入 第二次 点保存,值变成 rev=2|第二次;再杀进程重启,仍然是 rev=2。revision 能跨进程累加,说明 update 真的读到了磁盘上的旧值,而不是被内存里的缓存兜住。

点"清空"后显示 (空),点击清空后数值为空,

重启依然为空——因为 KStore 的 set(null) 是把文件删掉,不是写一个空文件。
再用 hdc shell 进沙箱确认文件确实存在:
hdc shell ls /data/storage/el2/base/haps/entry/files
能看到 kstore_demo.json,说明路径注入链路是真正走通的。为了排除"碰巧对上"的可能,我又做了一组对照:把注入的目录换成一个不存在的路径,init 会直接返回 ERR: ...,页面显示的路径也跟着变——这证明这个目录是真的被用上了,而不是被硬编码兜住了。
九、踩坑清单
坑一:ohosArm64() 报未定义。 十有八九是还在用 Kotlin 官方主线插件。这个 target 只在 HarmonyOS Kotlin 定制版里存在,必须先把插件版本切过去。
坑二:Kotlin 版本错配。 上游 libs.versions.toml 里的 kotlin = "2.4.10"、kotlinx-coroutines = "1.11.0"、kotlinx-serialization = "1.11.0"、kotlinx-io = "0.9.1" 都不能照抄,必须整体对齐定制版的配套版本线,否则配置阶段就因 klib 不兼容失败。
坑三:构建机没装 Android SDK。 KStore 上游带 Android target(AGP 9.3.1 / compileSdk = 36),缺 SDK 时连 compileKotlinOhosArm64 都会在配置阶段失败。先装 SDK,或临时注掉 android { } 块。
坑四:StoreDispatcher 没有 actual。 KStore 的 StoreDispatcher 是按平台叶子逐个实现的,没有 nativeMain 兜底,所以新增 target 必然缺一份 Dispatchers.kt。
坑五:路径用相对路径,写不进去或读不回来。 Path("xxx.json") 会按进程工作目录解析,而鸿蒙沙箱的 CWD 不可依赖。表现为抛 FileNotFoundException,或者"写成功了但读不回来"。正确做法是从 ArkTS 侧注入 context.filesDir,不要在 native 侧硬编码路径绕过去。
坑六:自定义 tempFile 时跨目录,事务性丢失。 FileCodec 的原子性依赖"临时文件和目标文件同目录、用 rename 改名"。如果你手改 tempFile 指到别的目录,atomicMove 会退化成 copy + delete,崩溃时可能留下半个文件。KStore 默认生成的 uniqueTempFile(file) 已经在同目录,别动它。
坑七:llvm-nm -D 能看到符号,但 ArkTS 侧 import 不到。 这是最迷惑人的一类问题。Kotlin/Native 导出的 C 符号和 ArkTS 能 import 的模块接口不是一回事——中间还隔着 NAPI 注册这一层。按符号名 → nm_modname → Index.d.ts → oh-package.json5 的 main 这个顺序逐一核对即可。
坑八:改完 Kotlin 代码,产物没更新。 Kotlin/Native 的编译缓存比较激进,遇到产物与代码不一致时先 ./gradlew clean 再重新构建,比反复找代码问题高效。
十、小结
KStore 的适配过程其实很典型:真正的难点从来不在 Kotlin 代码本身,而在平台侧的隐含假设。 这个库默认"调用方会给出一个可写的文件路径",而 Android / iOS / Desktop 恰好各有一个众所周知的数据目录;OpenHarmony 的应用沙箱目录是运行时分配的,编译期不可知。只要识别出这个假设,把路径来源换成应用层注入,问题就解决了。整个适配改动量很小——一个 actual、一个只编译 ohosArm64 的桥接模块——但如果没有想清楚这一点,就很容易在 native 侧反复折腾却始终读不到数据。
顺着这个思路往下看,还有几个值得做的方向:Decompose 的难点会落在生命周期与状态保存恢复上(它的依赖 Essenty 已经有人适配过了),kable 的难点会落在蓝牙这套纯硬件能力的桥接上。它们和路径、时区问题属于同一类——都是平台能力与库预期之间的错位。
欢迎加入 KMP/CMP 鸿蒙化社区,一起共建 OpenHarmony 跨平台生态:
https://atomgit.com/CPF-KMP-CMP
适配后仓库地址(AtomGit):
https://atomgit.com/oh-tpc/ohos_kstore
AI 工具推荐:推荐使用码道(CodeArts)辅助开发 —— https://developer.huaweicloud.com/codeartsco.html?source=dmzntgwatomgit1&sourcead=dmzntgwatomgiths
环境信息:DevEco Studio 26.0.0 Release / HarmonyOS Kotlin 2.2.21-1.0.0 / Gradle 8.14.1 / JDK 21 / 真机 ROM 6.1+ / KStore 1.1.0
更多推荐


所有评论(0)