开源鸿蒙平台 KMP_CMP 三方库「系统文件选择器」适配全流程
本文记录
kmp-system-picker接入 OpenHarmony 系统文件选择能力的完整过程,覆盖 KMP/CMP 工程盘点、ohosArm64目标、Kotlin/Native 动态库、C ABI/N-API 桥接、ArkUI 真机页面、系统选择器调用、签名 HAP 和真机验收。本次适配复用 Kotlin 侧的选择请求模型、MIME/数量校验、不可变生命周期归约和验收逻辑,再由 ArkTS 调用 HarmonyOS
@kit.CoreFileKit的DocumentViewPicker与@kit.MediaLibraryKit的PhotoViewPicker。这样验证的是同一份 KMP 代码在 OpenHarmony ARM64 设备上的运行结果,而不是重新写一套只在页面里生效的选择逻辑。
项目地址: AtomGit/oh-tpc/kmp-system-picker
开发工具: 华为云码道
一、背景
1.1 为什么做开源鸿蒙平台 KMP/CMP 适配
系统文件选择器是 HarmonyOS 安全文件访问体系的核心入口。应用通过 DocumentViewPicker 选择文档、通过 PhotoViewPicker 选择图片和视频,文件访问权限由系统选择器安全授予,业务层不需要申请文件读写权限,也不需要自行实现文件浏览界面。
如果只把页面重新写成 ArkTS,页面可能会显示几个"选择文件"按钮,却无法证明共享 Kotlin 请求模型、Native 动态库和实际系统选择器已经连通。适配需要解决下面几个问题:
| 障碍 | 具体问题 |
|---|---|
| 目标缺失 | KMP 模块默认只有 JVM,必须增加 ohosArm64() 才能生成 OpenHarmony KLIB。 |
| 工具链不一致 | Kotlin/Native、OpenHarmony LLVM、Native SDK 和 Gradle 插件必须使用匹配版本。 |
| 系统能力边界 | 选择器属于系统能力,编译成功不代表每台设备都呈现相同的文件目录。 |
| 语言边界不同 | ArkTS 不能直接持有 Kotlin data class,必须经过 C ABI、C++ N-API 和 JSON。 |
| URI 语义差异 | 照片选择器返回的媒体 URI 只能配合 photoAccessHelper.getAssets 使用,不是通用文件 URI。 |
| 结果契约 | 返回的数量、MIME 类型和 URI 唯一性必须在共享模型里统一校验。 |
| 交付链路复杂 | Native 动态库、CMake、HAP、签名、设备安装和 ARM64 依赖需要分别验证。 |
因此,本项目把适配边界放在三个地方:Kotlin/Native 目标配置、C ABI/N-API 桥接层和 ArkUI 选择器调用层。请求模型和校验规则仍由 KMP 维护,ArkTS 只负责系统选择器生命周期、页面状态和取消/失败区分。
1.2 库提供的能力
file-picker 公共模块提供以下能力:
PickerCategory:文件、图片、视频和照片与视频四种内容类型;FilePickerOptions:不可变的选择请求,携带标题、选择模式、数量上限和 MIME 过滤;PickedFile:平台选择器返回的单条不可变元数据(URI、名称、MIME、大小);FilePickerOptions.validate:在结果进入共享业务逻辑前校验数量、重复和 MIME 过滤;FilePickerEngine.reduce:start/complete/cancel/fail/reset五个动作的不可变生命周期归约;FilePickerEngine.runChecks:在 JVM、Kotlin/Native 和 ArkTS 示例中复用同一组检查;toJson:生成稳定的跨语言 JSON;SystemFilePicker:给业务提供简单的options、catalog和initialState门面。
内置预设目录如下:
| KMP 预设 | 内容类型 | 选择模式 | 数量上限 |
|---|---|---|---|
documents | 文件 | 多选 | 20 |
images | 图片 | 多选 | 20 |
videos | 视频 | 多选 | 10 |
media | 照片与视频 | 多选 | 20 |
超出请求的结果、重复 URI 和不符合 MIME 过滤的文件都会在校验阶段被拒绝,旧版本应用不会因为平台返回异常数据而破坏共享状态。
1.3 实现适配
| 维度 | 要求 |
|---|---|
| 代码复用 | 预设目录、请求校验、生命周期归约、JSON 和自检由 Kotlin 共享。 |
| 平台目标 | 为公共模块和示例加入 ohosArm64,生成 libfile_picker.so。 |
| 桥接稳定 | 使用少量 C ABI 函数和 JSON,避免把 Kotlin 对象地址交给 ArkTS。 |
| UI 完整 | 页面支持内容类型选择、当前选择展示、打开系统选择器和自检展示。 |
| 可测试 | JVM 测试、Native 链接、HAP 构建、设备安装和系统选择器分别验收。 |
| 隐私安全 | 库不上传、复制或持久保存所选文件,URI 使用遵循系统授权规则。 |
| 签名安全 | 源码只保留签名配置入口,证书和密钥材料由开发者本机配置。 |
| 仓库规范 | 项目说明、文章、效果图和源码链接统一使用 AtomGit。 |
本项目的 ArkUI 页面是独立的真机验收宿主,公共 API 仍然保持平台无关。其他 KMP/CMP 应用可以复用
FilePickerOptions和FilePickerEngine,再自行决定如何呈现选择入口和结果列表。
二、实现路线图
第 1 阶段:项目初始化 ── 盘点 KMP 模块、请求模型和 OpenHarmony 示例边界
第 2 阶段:目标与依赖打通 ── 加入 ohosArm64、独立 example 工程和 Native 构建任务
第 3 阶段:请求与序列化 ── 建立 PickerCategory、Options、Reducer 和 JSON 契约
第 4 阶段:原生桥接 ── Kotlin/Native C ABI、C++ N-API、内存释放和类型检查
第 5 阶段:系统能力封装 ── ArkTS DocumentViewPicker/PhotoViewPicker 与页面生命周期
第 6 阶段:示例与验证 ── HAP 构建、签名、设备安装、选择器回调和效果图
每个阶段都使用真实产物作为下一阶段输入:先用 JVM 验证共享模型,再把相同代码链接为 ARM64 动态库,随后由 CMake 和 N-API 加载到 Stage 工程,最后安装签名 HAP 在真机上打开系统选择器。
三、逐步实现过程
第 1 阶段:项目初始化
1.1 盘点公共 API 和工程边界
项目采用与参考 CMP 工程一致的分层:
file-picker/ KMP 请求模型、预设目录、归约、自检和 JSON 边界
vico/ 与参考工程一致的库聚合层
sample/ android/desktop/shared/web/ios 主机入口
example/shared/ 示例门面和 JVM 验收测试
example/nativeApp/ ohosArm64 Kotlin/Native 动态库
example/ohosApp/ DevEco Stage 工程和 ArkUI 页面
scripts/ Native、HAP 和签名工程辅助脚本
docs/openharmony/ 验收记录和真机效果图
guide/ 集成指南
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 原生库架构 |
| 系统能力 | CoreFileKit / MediaLibraryKit | 文档与照片选择器 |
执行 Gradle 脚本前先选择 JDK 21:
export JAVA_HOME="/path/to/jdk-21"
java -version
真机上的文件目录内容与设备存储状态有关,API 版本满足要求只说明工程可以编译和安装,不能替代真机选择器验收。
1.3 创建 OpenHarmony 示例目录
示例页面没有把系统能力伪装成普通列表,而是围绕"内容类型选择、当前选择、打开系统选择器和自检"组织:
标题区 文件选择 / KMP 公共模型 + OpenHarmony System Picker
类型区 选择文件 / 选择图片 / 选择视频 / 选择照片与视频
结果区 当前选择 + 从系统空间选择文件
操作区 打开系统文件选择器
自检区 KMP 自检通过项
未选择文件时页面展示占位区,用于在不触发系统能力的设备上验证 KMP 和 N-API。只有点击"打开系统文件选择器"时,ArkTS 才调用系统选择器。
第 2 阶段:目标与依赖打通
2.1 加入 ohosArm64 目标
公共模块同时保留 JVM 测试和 OpenHarmony Native 目标:
plugins {
kotlin("multiplatform")
}
kotlin {
jvm()
jvmToolchain(21)
ohosArm64()
sourceSets {
commonTest.dependencies {
implementation(kotlin("test"))
}
}
}
JVM 目标让请求校验规则可以快速测试;ohosArm64 则把同一份 commonMain 代码编译成 OpenHarmony KLIB。
2.2 Native focused build 的作用
example/nativeApp 构建一个受 linker map 约束的 shared library:
kotlin {
ohosArm64 {
binaries.sharedLib {
baseName = "file_picker"
linkerOpts(
"--entry=0",
"--version-script=${project.file("src/ohosArm64Main/linker/shared-library.map")}",
)
linkerOpts("-lace_napi.z", "-luv", "-lhilog_ndk.z")
}
}
sourceSets {
commonMain.dependencies { implementation(project(":shared")) }
}
}
链接器只导出四个 C ABI 符号:
FilePickerCatalog
FilePickerGet
FilePickerRunChecks
FilePickerFree
这样 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/")
mavenCentral()
gradlePluginPortal()
}
}
dependencyResolutionManagement {
repositories {
mavenLocal()
maven("https://maven.eazytec-cloud.com/nexus/repository/maven-public/")
mavenCentral()
}
}
共享示例通过项目依赖消费本地 file-picker:
sourceSets {
commonMain.dependencies {
api(project(":file-picker"))
}
}
如果使用已经发布的 Maven 产物,业务 KMP 模块可以写成:
commonMain.dependencies {
implementation("com.ohos.filepicker:file-picker:1.0.0")
}
2.4 通过构建产物消费共享库
prepareOhos 在 Native 链接成功后复制动态库和 C 头文件:
val prepareOhos by tasks.registering(Copy::class) {
dependsOn("linkDebugSharedOhosArm64")
from(layout.buildDirectory.dir("bin/ohosArm64/debugShared")) {
include("libfile_picker.so")
into("libs/arm64-v8a")
}
from(layout.buildDirectory.dir("bin/ohosArm64/debugShared")) {
include("libfile_picker_api.h")
into("src/main/cpp/include")
}
into(rootProject.layout.projectDirectory.dir("ohosApp/entry"))
}
动态库、生成头文件和 HAP 属于构建产物,仓库通过 .gitignore 排除它们;每台开发机都可以从源码重新生成与自身 SDK 匹配的文件。
第 3 阶段:请求与序列化
3.1 为什么需要统一 JSON 契约
ArkTS、C++ 和 Kotlin/Native 不能直接共享 Kotlin 对象,因此边界使用 UTF-8 JSON:
OpenHarmony system picker
↓ selected URIs
FilePickerClient.ets
↓ uri + name
N-API getOptions()/getCatalog()
↓ C ABI FilePickerGet()/FilePickerCatalog()
Kotlin FilePickerEngine
↓ JSON options
ArkUI page
页面只消费 JSON,不复制预设规则。这样 JVM 测试和设备页面使用同一套 SystemFilePicker.options 目录和 validate 规则。
3.2 FilePickerOptions 和 PickedFile
公共请求模型位于 file-picker/src/commonMain:
public data class FilePickerOptions(
val id: String,
val title: String,
val category: PickerCategory = PickerCategory.DOCUMENTS,
val selectionMode: PickerSelectionMode = PickerSelectionMode.SINGLE,
val maxItems: Int = 1,
val mimeTypes: List<String> = listOf("*/*"),
) {
init {
require(id.matches(ID_PATTERN)) { "Picker id must be lowercase kebab-case" }
require(maxItems in 1..100) { "Picker supports between 1 and 100 items" }
require(selectionMode == PickerSelectionMode.MULTIPLE || maxItems == 1) {
"Single selection must have maxItems equal to one"
}
require(mimeTypes.isNotEmpty() && mimeTypes.size <= 20) { "Provide between 1 and 20 MIME filters" }
}
/** Ensures a platform result obeys the request before exposing it to callers. */
public fun validate(files: List<PickedFile>): Unit {
require(files.size <= maxItems) { "Picker returned more files than requested" }
require(files.map { it.uri }.toSet().size == files.size) { "Picker returned duplicate file URIs" }
files.forEach { file ->
val type = file.mimeType
require(type == null || mimeTypes.any { mime -> mime == "*/*" || mimeMatches(mime, type) }) {
"Picker returned a file outside the requested MIME filters"
}
}
}
}
PickedFile 在构造时校验 URI 长度、文件名、MIME 格式和大小非负,非法数据无法进入共享状态。
3.3 生命周期归约
FilePickerEngine.reduce 把选择器生命周期收敛为五个动作,complete 在进入 COMPLETED 前先调用 validate:
"complete" -> {
require(state.status == PickerStatus.PICKING) { "Picker must be open before completion" }
require(files.isNotEmpty()) { "Completed picker result must contain files" }
options(state.requestId).validate(files)
state.copy(status = PickerStatus.COMPLETED, files = files.toList(), message = "已选择 ${files.size} 个文件")
}
"cancel" -> {
require(state.status == PickerStatus.PICKING) { "Picker must be open before cancellation" }
state.copy(status = PickerStatus.CANCELLED, files = emptyList(), message = "已取消选择")
}
取消和失败是两个不同状态,页面可以把"用户主动取消"与"选择器异常"分开呈现。
第 4 阶段:原生桥接
4.1 C ABI 入口
example/nativeApp 用 @CName 导出三个返回 JSON 字符串的函数和一个释放函数:
@CName("FilePickerCatalog")
public fun catalogNative(): CPointer<ByteVar> = response {
FilePickerExamples.catalog().joinToString(prefix = "[", postfix = "]") { it.toJson() }
}
@CName("FilePickerGet")
public fun optionsNative(index: Int): CPointer<ByteVar> = response {
FilePickerExamples.options(index).toJson()
}
@CName("FilePickerRunChecks")
public fun checksNative(): CPointer<ByteVar> = response {
val checks = FilePickerExamples.runChecks()
"{\"passed\":true,\"checks\":${checks.joinToString(prefix = "[", postfix = "]") { it.jsonQuote() }}}"
}
@CName("FilePickerFree")
public fun freeNative(pointer: CPointer<ByteVar>?) {
if (pointer != null) nativeHeap.free(pointer)
}
response 包装器把 Kotlin 异常转成 {"error":...} JSON,N-API 层据此抛出 ArkTS 异常,避免 Native 崩溃直接穿透到页面。
4.2 N-API 导出
napi_init.cpp 注册 entry 模块,完成字符串转换、类型检查和内存释放;类型声明位于:
example/ohosApp/entry/src/main/cpp/types/libentry/index.d.ts
export const getCatalog: () => string;
export const getOptions: (index: number) => string;
export const runChecks: () => string;
ArkTS 使用:
import { getCatalog, getOptions, runChecks } from 'libentry.so';
4.3 ArkTS 解析与选择器调用
FilePickerClient.ets 负责 JSON 解析、错误提升和系统选择器调用:
export async function selectFiles(
context: common.UIAbilityContext,
request: PickerOptions,
): Promise<PickedFile[]> {
let uris: string[];
if (request.category === 'DOCUMENTS') {
const documentPicker = new picker.DocumentViewPicker(context);
const config = new picker.DocumentSelectOptions();
config.maxSelectNumber = request.selectionMode === 'SINGLE' ? 1 : request.maxItems;
uris = await documentPicker.select(config);
} else {
const photoPicker = new photoAccessHelper.PhotoViewPicker();
const config = new photoAccessHelper.PhotoSelectOptions();
config.maxSelectNumber = request.selectionMode === 'SINGLE' ? 1 : request.maxItems;
config.MIMEType = request.category === 'IMAGES'
? photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE
: request.category === 'VIDEOS'
? photoAccessHelper.PhotoViewMIMETypes.VIDEO_TYPE
: photoAccessHelper.PhotoViewMIMETypes.IMAGE_VIDEO_TYPE;
const result = await photoPicker.select(config);
uris = result.photoUris;
}
return uris.map((uri: string): PickedFile => ({ /* uri/name 映射 */ }));
}
文档走 DocumentViewPicker,图片、视频和混合媒体走 PhotoViewPicker,数量上限直接来自 KMP 请求的 maxItems。
四、完整代码对照
4.1 整体架构
OpenHarmony System Picker
│ DocumentViewPicker / PhotoViewPicker
▼
FilePickerClient.ets
│ selected URIs and names
▼
Index.ets -> libentry.so
│ N-API
▼
FilePickerCatalog / FilePickerGet / FilePickerRunChecks
│ C ABI
▼
libfile_picker.so
│ Kotlin/Native
▼
FilePickerEngine -> FilePickerOptions -> JSON
4.2 文件清单
| 文件 | 职责 |
|---|---|
file-picker/FilePicker.kt | 内容类型、请求模型、结果元数据和业务门面 |
file-picker/FilePickerEngine.kt | 预设目录、生命周期归约和自检 |
file-picker/FilePickerJson.kt | JSON 编码和字符串转义 |
example/nativeApp/NativeBridge.kt | C ABI、JSON 返回和内存释放 |
example/ohosApp/.../FilePickerClient.ets | 系统选择器调用、N-API JSON 解析和校验 |
example/ohosApp/.../Index.ets | 真机预览页面 |
example/ohosApp/entry/src/main/cpp/napi_init.cpp | N-API 导出和参数检查 |
scripts/build-openharmony.sh | 测试、Native 链接和产物复制 |
scripts/build-hap.sh | 已配置签名工程的 HAP 构建 |
docs/openharmony/VALIDATION.md | 自动检查和真机验收记录 |
4.3 关键 API 对照
| 层次 | API | 作用 |
|---|---|---|
| Kotlin | SystemFilePicker.options | 按 id 或序号获取预设请求 |
| Kotlin | FilePickerOptions.validate | 校验平台返回的数量、唯一性和 MIME |
| Kotlin | FilePickerEngine.reduce | 不可变生命周期归约 |
| Native | FilePickerGet | 返回一个 JSON 预设 |
| N-API | getOptions | 向 ArkTS 暴露预设方法 |
| ArkTS | FilePickerClient.selectFiles | 打开对应系统选择器 |
| ArkTS | FilePickerClient.checkNative | 读取 KMP 自检结果 |
4.4 ArkTS 与 Kotlin 的边界
ArkTS 只负责系统能力和界面生命周期:
const files = await selectFiles(this.context, this.request);
Kotlin 负责请求含义和不可变数据:
val request = SystemFilePicker.options("images")
request.validate(files)
两者之间只传输字符串和 JSON,不传输 Kotlin 对象、ArkTS class 实例或未校验的动态结构。
五、关键决策说明
决策 1:把 ohosArm64 加入公共构建约定
系统选择器运行在 ARM64 设备上,只有真正链接 ohosArm64 动态库,才能证明共享 Kotlin 代码可以进入 OpenHarmony 运行时。
决策 2:独立消费者必须通过构建产物消费
example 单独解析 file-picker,ohosApp 单独接收 .so 和头文件,避免根工程编译通过却无法在 DevEco 中打包。
决策 3:JSON 作为跨语言数据契约
JSON 让 ArkTS、C++ 和 Kotlin/Native 的边界清楚可调试,并允许后续增加可信字段而不暴露内部对象布局。
决策 4:桥接层只开放四个 C ABI 入口
目录、预设、自检和释放已经覆盖示例所需能力;减少 ABI 符号可以降低 Native 生命周期和兼容风险。
决策 5:校验放在共享模型而不是页面
validate 在 KMP 层执行数量、重复和 MIME 检查,ArkTS 页面不复制规则。这样 Android 或桌面宿主复用同一份校验,平台差异不会产生第二套判断。
决策 6:把库验证和设备验证分开
JVM 测试验证请求规则,Native 链接验证 ABI,Hvigor 验证 HAP,真机验证 DocumentViewPicker/PhotoViewPicker 回调。每一层都有明确的失败边界。
六、测试与验证
6.1 测试环境
本次真机验证使用:
- macOS;
- JDK 21;
- Kotlin Multiplatform
2.2.21-1.0.0; - DevEco Studio 及 OpenHarmony ARM64 Native SDK;
- 已签名
entry-default-signed.hap; - USB 连接的 HarmonyOS ARM64 真机。
6.2 静态检查与单元测试
./gradlew :file-picker:jvmTest :sample:shared:jvmTest
(cd example && ./gradlew :shared:jvmTest)
测试覆盖预设 id 唯一性、MIME 过滤匹配、单选/多选约束、不可变归约、JSON 字段和公共自检项。
6.3 原生桥接和 HAP 验证
./scripts/build-openharmony.sh
python3 scripts/check-native-deps.py \
/Applications/DevEco-Studio.app/Contents/sdk/default/openharmony/native \
example/ohosApp/entry/libs/arm64-v8a
./scripts/build-hap.sh example/ohosApp
验证结果:
libfile_picker.so: 0 unresolved strong imports
Hvigor BUILD SUCCESSFUL
entry-default-signed.hap generated
6.4 功能验证用例
用例 1:默认页面和自检
启动应用后页面显示"文件选择"标题和 KMP 自检通过项。预设目录由 Native getCatalog() 生成。
用例 2:四种内容类型
点击选择文件、选择图片、选择视频和选择照片与视频,页面切换当前选择类型,数量上限分别对应 20/20/10/20。这个用例不依赖设备硬件。
用例 3:打开系统文件选择器
点击"打开系统文件选择器",文档类型进入 DocumentViewPicker,图片/视频/混合类型进入 PhotoViewPicker,数量上限来自 KMP 请求的 maxItems。
用例 4:选择结果回显
在系统选择器中选定文件后,页面回显 URI 对应的名称和数量;结果先经过 KMP validate 再进入共享状态。真机效果图见第七章。
用例 5:取消选择
在系统选择器中直接返回,页面呈现取消状态而不是错误,与选择失败相互区分。
用例 6:选择器异常
选择器打开失败时页面展示失败文本,本地类型切换和 KMP 自检仍然可用。
6.5 验证结论
自动测试、Kotlin/Native ARM64 链接、ELF 依赖检查、Hvigor HAP 构建、签名安装和真机页面自检均已完成。真机已经打开系统选择器并回显所选文件,说明从系统选择器到 KMP 请求模型的链路可用。
七、运行效果
7.1 真机截图


截图中可以看到:
- 页面标题为"文件选择",右上角标记
OHOS平台; - 副标题说明 KMP 公共模型和 OpenHarmony System Picker;
- 内容类型区提供选择文件、选择图片、选择视频和选择照片与视频四个入口,数量上限与 KMP 预设一致(20/20/10/20);
- 当前选择为"选择文件",未选择时展示"从系统空间选择文件"占位区;
- 点击"打开系统文件选择器"后进入系统安全访问界面,应用仅可访问用户选定的文件;
- 文件访问权限由系统选择器安全授予,页面不申请额外的文件读写权限。
7.2 命令速查
# 根工程测试和 OpenHarmony Native 准备
export JAVA_HOME="/path/to/jdk-21"
./scripts/build-openharmony.sh
# 单独运行共享测试
(cd example && ./gradlew :shared:jvmTest)
# 设备依赖检查
python3 scripts/check-native-deps.py \
/Applications/DevEco-Studio.app/Contents/sdk/default/openharmony/native \
example/ohosApp/entry/libs/arm64-v8a
# 在已配置签名的工程中构建 HAP
./scripts/build-hap.sh example/ohosApp
# 安装和启动
hdc -t <设备序列号> install -r \
example/ohosApp/entry/build/default/outputs/default/entry-default-signed.hap
hdc -t <设备序列号> shell aa start \
-a EntryAbility -b com.ohos.filepicker.sample
八、遗留问题与改进方向
8.1 踩坑复盘
- 只改 ArkTS 页面不算 KMP 适配:必须把共享模型真正编译成
ohosArm64动态库。 - N-API 不负责业务判断:C++ 只做类型检查、字符串转换和释放,请求与校验语义放在 Kotlin。
- 选择器结果必须先校验再使用:数量、重复 URI 和 MIME 过滤统一走
FilePickerOptions.validate。 - 媒体 URI 不是通用文件 URI:照片选择器返回的 URI 只能配合
photoAccessHelper.getAssets使用。 - 签名配置需要绑定产品:
products[].signingConfig必须指向signingConfigs,否则 Hvigor 会继续生成未签名 HAP。
8.2 已知问题
- 文档选择器返回的名称从 URI 推断,系统不直接提供显示名,部分编码字符需要解码;
- 照片选择器结果暂不回填 MIME 类型和文件大小,页面按媒体类别展示;
- 文章中的签名配置只适用于本地开发机,不能直接复制到其他环境;
- 当前示例是单页面选择模型,多页面应用需要在业务层集中管理选择请求。
8.3 未来优化方向
- 增加跨平台
expect/actual启动接口,让 Android 或桌面端可以提供模拟选择器; - 将选择状态封装为
Flow<FilePickerState>,减少业务层回调管理; - 为 Compose Multiplatform 页面提供选择入口和结果列表示例;
- 补充媒体资产的显示名、MIME 和大小回填;
- 在持续集成中加入 Native 链接和 HAP 未签名构建任务。
九、总结
9.1 核心难点回顾
本次适配真正需要处理的不是一个 picker.select 调用,而是一条完整跨端链路:
OpenHarmony System Picker
→ ArkTS 选择器调用和结果映射
→ C++ N-API
→ Kotlin/Native C ABI
→ KMP 请求模型
→ JSON
→ ArkUI 真机页面
9.2 封装层次
- KMP 层:定义稳定的预设、请求、归约、校验和 JSON;
- Native 层:生成 ARM64 动态库,并通过 C ABI 输出有限入口;
- N-API 层:完成参数检查、字符串转换和内存释放;
- ArkTS 层:管理系统选择器调用、页面生命周期、取消/失败区分和视觉展示;
- DevEco 层:完成 CMake、HAP、签名、安装和运行。
9.3 三条经验
- 先让共享模型在 JVM 和 Native 通过,再接入 ArkUI;
- 用 JSON 和少量 C ABI 代替跨语言对象传递;
- 把自动测试、HAP 构建和真机选择器分别记录,避免把"编译成功"误认为"选择链路可用"。
9.4 适配成果
当前 kmp-system-picker 已完成:
PickerCategory四类内容与FilePickerOptions不可变请求模型;- 数量、唯一性和 MIME 过滤的共享校验;
- JVM 和 OpenHarmony ARM64 共用的自检逻辑;
- Kotlin/Native + C ABI + N-API 桥接;
DocumentViewPicker/PhotoViewPicker真机选择;- 取消与失败状态的区分呈现;
- 签名 HAP 构建、设备安装和真机效果图;
- 与参考 CMP 工程一致的模块和脚本组织;
- AtomGit 项目文档和 OpenHarmony 验收记录。
参考文档
更多推荐



所有评论(0)