开源鸿蒙平台 CMP 三方库「datetimepicker-kmp」适配全流程
本文记录
datetimepicker-kmp接入 OpenHarmony 的完整过程,覆盖 Compose UI 库的源码集拆分、鸿蒙 kotlinx-datetime 制品线选型、日历引擎与选择状态机编译、ohosArm64/ohosX64目标、Kotlin/Native 动态库、C ABI/N-API 桥接、ArkUI 真机页面、HAP 构建和真机验收。
datetimepicker-kmp是基于 Compose Multiplatform(Material3)+kotlinx-datetime的 Kotlin Multiplatform 日期 / 日期范围选择器库:一个DatePickercomposable 覆盖单选与范围两种模式,内部由DateTimeOperations生成 42 格月视图网格(前后月补齐、今天标记、禁用日期、过去日期禁用),DateTimePickerDefaults承载月份名 / 星期名 / 时区 / 格式化器配置,1.0.6 新增DateTimePickerColors颜色配置与clickable点击开关。本次适配目标为上游最新版本1.0.7;1.0.6引入的DateTimePickerColors与clickable在1.0.7中继续保留。本次适配与社区已过审的两条路线都不同:qrose / WheelPicker 走"零依赖内核提取 + 自带历法",KStore 走"制品线齐全 → 整库真编译";本库是 Compose UI 库——UI 层进不了鸿蒙工具链(社区 CMP 1.9.2 / Kotlin 2.2.21 与上游 CMP 1.7.0 / Kotlin 2.1.0 对不齐),而日历引擎只依赖kotlinx-datetime,社区 Maven 上又恰好存在带ohosArm64/ohosX64变体的 datetime 副品线(0.7.1-0.5.0-15)。因此走出第三条路:Compose UI 隔离进未注册的composeMain永不编译,日历引擎原样依赖 ohos 副品线 kotlinx-datetime 编译,上游 composable 里的选择状态机 1:1 移植成SelectionEngine供 ArkTS 调用——判定在 Kotlin,渲染在 ArkTS,真机上跑的选择规则与 Android/iOS 像素级一致(连"范围含禁用日坍缩到第二个日期"这类怪癖都保持)。
项目地址: AtomGit/oh-tpc/datetimepicker-kmp.git
上游仓库: Kudl1k/datetimepicker-kmp
开发工具: 华为云码道
环境安装: 环境安装指南
一、背景
1.1 为什么做 OpenHarmony 平台 KMP/CMP 适配
datetimepicker-kmp 为 Kotlin Multiplatform 应用提供"日期选择"这一件事的 Material3 答案:DatePicker(range = false) 单选、range = true 范围双选,onSelectDate / onRangeSelected 双回调,今天自动描边、过去日期可禁、指定日期可禁、前后月补齐成整周网格、月份左右切换、格式化结果文本随手可取。KMP 项目把这些能力放在 commonMain,由 Android、iOS 共享。OpenHarmony 应用同样需要日历选择能力,但上游构建(Kotlin 2.1.0 / CMP 1.7.0 / Gradle 8.7)不会生成 OpenHarmony 变体,而鸿蒙社区也没有共享这份 Kotlin 判定逻辑的日期组件。
如果只把示例页面改成 ArkTS 手写日历,页面可以显示网格,却无法证明上游的日历引擎与选择规则在 OpenHarmony ARM64 设备上完成编译、链接和执行——补齐 42 格、闰年二月、跨年十二月、范围坍缩这些细节,手写一遍就是重新造一个行为不保证一致的轮子。适配需要同时验证日历引擎、选择状态机、kotlinx-datetime 运行时、Native 动态库、C ABI、N-API、ArkUI 页面和 HAP 交付链路。
| 障碍 | 具体问题 |
|---|---|
| 目标缺失 | 必须增加 ohosArm64() / ohosX64() 才能生成 OpenHarmony KLIB。 |
| Compose 进不了鸿蒙 | 上游是 Material3 Compose UI 库;社区 CMP 1.9.2 / HarmonyOS Kotlin 2.2.21 与上游 CMP 1.7.0 / Kotlin 2.1.0 工具链对不齐,DatePicker composable 无法编入 ohos。 |
| 依赖矩阵 | 引擎依赖 kotlinx-datetime(上游钉 0.6.1 官方线),官方线没有 ohos 变体,必须换到社区副品线且 API 兼容。 |
| 语言边界不同 | ArkTS 不能直接持有 Kotlin 对象,需要 C ABI、C++ N-API 和 JSON。 |
| UI 状态机跨不了桥 | 选择路由(点第一个 / 第二个 / 更早的日期)与 derivedStateOf 重算是 Compose 侧概念,不能丢在 ArkTS 里手抄。 |
| 交付链路复杂 | .so、CMake、N-API、HAP、签名和 hdc 都要单独验收。 |
因此,本项目把边界放在:日历引擎与选择状态机的 ohos 编译、根构建替换(cmp_qrose 模式)和 C ABI/N-API 桥接层。日历规则与选择语义完全由上游原版实现(或其 1:1 移植)负责,ArkTS 只负责调用 Native、解析 JSON 和渲染网格。
1.2 库提供的能力
Demo 提供一个真实的日历选择会话,对应桥接的全部入口:
- 握手(info):返回 fork / 上游 / Kotlin / datetime 版本与 op 列表,来自真实 Native 调用而非静态文字;
- 配置(config):重建
DateTimePickerDefaults——时区、禁用日期列表、禁过去日期、月份名 / 星期名(预置或自定义); - 月视图(month):42 格日历网格(周一为首列、前后月补齐、今天 / 当月 / 禁用标记)+ 当前选择状态在网格上的旗标重算;
- 点选(select):把一次日期格点击交给
SelectionEngine按上游规则路由,返回新选择状态、格式化结果文本、回调事件类型与旗标刷新后的网格; - 格式化(format):用配置的 formater 输出 “Thu, October 15” 风格文本;
- 今天(today):把 ArkTS 时钟的 epoch 毫秒换算成配置时区的 yyyy-MM-dd。
响应包含:
| 字段 | 作用 |
|---|---|
ok | 操作是否成功,失败时 error 带原文。 |
year / month / monthName | 当前月视图定位与显示名(“October 2026”)。 |
dayLetters | 周标题行(“M|T|W|T|F|S|S”,周一为首列,镜像上游 day.name[0] 渲染)。 |
start / end / final | 选择状态与格式化结果(单选 end 为 null,范围两端齐全)。 |
event | 本次转移触发的上游回调:DATE / RANGE / NONE。 |
cells | 42 格网格:day,iso,cur,today,dis,sel,rng,startOfRange 八字段逗号分隔、分号连格。 |
引擎侧完整能力(42 格生成、闰年 / 跨年边界、前后月补齐、禁用规则、月份加减、月份名、格式化、选择路由与范围重算)全部在 Kotlin commonMain 里编译进入 .so,JVM 侧保留 21 项 jvmTest 交叉验证用例(对拍 java.time 与手推的上游行为),证明 ohos .so 里执行的日历逻辑没有漂移。
1.3 实现适配
| 维度 | 要求 |
|---|---|
| 代码复用 | DateTimeOperations.kt 原样编译(仅 3 处最小适配:拆出 Compose 的 DateTimePickerColors、getCalendarDates 加可选 today 参数、until() 返回值随 0.7.x 变 Long 补 toInt());新增 SelectionEngine.kt(上游 composable 状态机的 1:1 移植);Compose UI 隔离进 src/composeMain/ 永不编译。 |
| 平台目标 | 根构建直接声明 ohosArm64 + ohosX64,保留 jvm() 作交叉验证,库模块出 KLIB,example/nativeApp 出双 ABI 动态库。 |
| 构建隔离 | 根构建替换为极简 ohos 构建(cmp_qrose 模式);上游 AGP / Compose / vanniktech 发布插件全部随根构建替换剥离——本 fork 不发 Maven Central。 |
| 桥接稳定 | 使用 1 个 C ABI 和 UTF-8 JSON,不传递 Kotlin 对象地址;异常永远不跨 FFI。 |
| 依赖纪律 | 只把 kotlinx-datetime 从官方 0.6.1 换到 ohos 副品线 0.7.1-0.5.0-15(0.7.1 API 与 0.6.1 用法兼容,仅弃用警告),其余零依赖改动。 |
| UI 完整 | 单页覆盖全部桥接入口:日历网格、Single/Range 切换、禁过去、禁指定日期、clickable 开关、结果文本、事件回调日志和版本握手。 |
| 仓库规范 | 项目说明、文章、效果图和源码链接统一使用 AtomGit。 |
本项目的 ArkUI 页面不是 Compose Multiplatform UI 的移植,也不替代它。上游
DatePickercomposable 原样留在src/composeMain/供 Android/iOS 侧继续编译;ArkTS Demo 只是把桥暴露的日历判定闭环画出来。
二、实现路线图
第 1 阶段:项目初始化 ── 盘点源码与 Compose 依赖边界、确认 datetime ohos 制品线、确定交付物
第 2 阶段:目标与依赖打通 ── 根构建替换、eazytec Maven 置前、Kotlin 2.2.21-1.0.0 钉版、Gradle 升 8.14.1
第 3 阶段:桥协议建模 ── 六 op JSON 契约、42 格 cells 编码、错误通道
第 4 阶段:原生桥接 ── @CName C ABI、C++ N-API、参数校验和内存释放
第 5 阶段:ArkUI 宿主 ── 日历网格渲染、模式与开关面板、结果与回调日志
第 6 阶段:示例与验证 ── HAP、签名、设备安装、真机运行和效果图
三、逐步实现过程
第 1 阶段:项目初始化
1.1 工程边界
datetimepicker/ 上游库模块(内核进 ohos 构建,jvm + ohosArm64 + ohosX64)
datetimepicker/src/commonMain/ 日历引擎 DateTimeOperations(原样)+ SelectionEngine(新增移植)
datetimepicker/src/composeMain/ 上游 Compose UI:DatePicker.kt + DateTimePickerColors.kt(永不编译)
datetimepicker/src/jvmTest/ 交叉验证测试(对拍 java.time 与手推上游行为)
example/nativeApp/ Kotlin/Native 动态库(Bridge + MiniJson)
example/harmonyApp/ DevEco Stage、CMake N-API 和 ArkTS 页面
README.OpenHarmony.md / _CN.md 鸿蒙适配说明(中英双份)
README.upstream.md 上游原文保存
构建拓扑走 cmp_qrose 模式(根构建即 ohos 构建):settings.gradle.kts 只 include :datetimepicker 与 :example:nativeApp,rootProject.name = "ohos_datetimepicker"。上游的 AGP / Compose / Compose Compiler / vanniktech mavenPublish 插件与 mavenPublishing 发布段全部随根构建替换剥离——本 fork 不发 Maven Central。
1.2 工具链矩阵
| 项目 | 配置 |
|---|---|
| Kotlin Multiplatform | HarmonyOS Kotlin 2.2.21-1.0.0(eazytec Maven) |
| JDK | 21(Temurin,org.gradle.java.home 指向 C:\Program Files\Eclipse Adoptium\jdk-21.0.12.101-hotspot) |
| OpenHarmony 目标 | ohosArm64 + ohosX64(jvm() 保留作交叉验证) |
| Gradle | 8.14.1(上游 wrapper 8.7 升级,对齐 qrose / WheelPicker 已审钉法) |
| kotlinx | kotlinx-datetime 0.7.1-0.5.0-15(ohos 制品线;上游官方线为 0.6.1) |
| DevEco product | 5.1.0(18) 兼容 |
| DevEco Studio | 5.1.0 Release |
| ABI | arm64-v8a + x86_64 |
| Bundle | org.terminator.ohos.datetimepicker |
$env:JAVA_HOME = "C:\Program Files\Eclipse Adoptium\jdk-21.0.12.101-hotspot"
java -version
1.3 源码依赖度盘点
| 源文件 | 内容 | 依赖形态 | 处置 |
|---|---|---|---|
DateTimeOperations.kt | 日历引擎 + CalendarDate / DateTimePickerDefaults / DateTimePickerColors | kotlinx-datetime + 一个 Compose import(ui.graphics.Color) | 拆分编译:Colors 拆走,其余原样进 commonMain |
DatePicker.kt | Material3 composable(月份头、箭头、周标题、42 格、选择/范围/今天/禁用样式) | 纯 Compose | 隔离进 src/composeMain/,不是注册的 source set |
整个库只有两个源文件,这是这批适配里体量最小的;但 DateTimeOperations.kt 尾部挂着 DateTimePickerColors(依赖 androidx.compose.ui.graphics.Color),一根 Compose import 会把整个 commonMain 拖下水。处置是把它拆成独立文件挪进 composeMain,与 DatePicker.kt 一起隔离——上游 Compose API 的可见性一个字符都不改,只是换了目录。
选择状态机是另一件必须搬进内核的东西。上游里它散落在 DatePicker composable 的 clickable lambda(点击路由)与 derivedStateOf 块(旗标重算)中,与 Compose 状态 API 深度纠缠。把它逐行移植成 SelectionEngine(纯 Kotlin、无状态、入参出参全显式),ArkTS 才能在不抄逻辑的前提下驱动同一套规则——这是"判定在 Kotlin,渲染在 ArkTS"的成立前提。
1.4 页面边界
标题区 datetimepicker-kmp · OPENHARMONY / 版本握手行(fork · Kotlin · datetime · ops)
日历区 月份名 + 左右箭头 / 周标题(dayLetters,周一为首列)/ 42 格网格
今天 primary 描边圆 / 选中 primary 实心圆白字 / 范围 20% 底色 + 起终点大圆角
禁用灰字不可点 / 非当月 50% 字色 / 格宽撑满 aspectRatio 1
面板区 Single/Range 切换 / Disable past dates / Disabled dates(当月 24/25)/ Clickable 开关
结果区 选中结果文本(final)+ 事件回调日志(onSelectDate / onRangeSelected,时间戳滚动)
关于区 版本握手、库简介、仓库与社区链接(AtomGit)
页面启动时先调 op=info 做握手,再 op=config 安装默认配置,now 由 ArkTS Date() 提供后调 op=month 出首屏网格。
第 2 阶段:目标与依赖打通
2.1 制品线验证(本适配的成败手)
先查社区 Maven(https://maven.eazytec-cloud.com,maven-public 组仓)上有没有 ohos 的 kotlinx-datetime 制品。用 Nexus REST 搜索 + .module 元数据逐个验证,结论是存在一整条带 ohos 变体的副品线:
org.jetbrains.kotlinx:kotlinx-datetime:0.7.1-0.5.0-15
variants: ohosArm64ApiElements-published / ohosX64ApiElements-published / jvmApi-published
requires: kotlin-stdlib 2.2.21-0.5.0-16 ← 2.2.21 线,与本仓 Kotlin 2.2.21-1.0.0 同代
三条排除记录同样重要:其一,0.8.0-0.1.0-02 也带 ohos 变体,但 requires kotlin-stdlib 2.4.20-0.1.0-02,配套的是 2.4.20 新工具链线,与本仓 2.2.21 钉法不匹配;其二,0.8.0-0.6.x-compat / 0.7.1-0.6.x-compat 这类"0.6.x 兼容版"反而没有 ohos 变体(它们只是 API 兼容层);其三,官方 0.6.1 当然没有 ohos 变体。最终钉 0.7.1-0.5.0-15:0.7.1 的 API 与上游 0.6.1 的用法完全兼容(LocalDate / TimeZone / DateTimeUnit / DateTimeFormat / MonthNames 全在,Clock 仅转弃用警告),2.2.21 基线的 KLIB 元数据与鸿蒙 Kotlin 2.2.21-1.0.0 互相可读。
2.2 工具链钉版(为什么是 2.2.21-1.0.0 + Gradle 8.14.1)
沿用社区已批准的钉法:HarmonyOS Kotlin 2.2.21-1.0.0(qrose / WheelPicker / Flippable 同款),Gradle 从上游 8.7 升到 8.14.1(2.2.21 线的已审 wrapper 版本)。gradle.properties 固定 JDK 21 与 Kotlin/Native 分发参数,版本号升为 1.0.7-ohos.1:
org.gradle.java.home=C:\\Program Files\\Eclipse Adoptium\\jdk-21.0.12.101-hotspot
version=1.0.7-ohos.1
kotlin.native.ignoreDisabledTargets=true
kotlin.native.distribution.downloadFromMaven=true
settings.gradle.kts 把 eazytec 仓库写在最前:
pluginManagement {
repositories {
maven("https://maven.eazytec-cloud.com/nexus/repository/maven-public/")
maven("https://maven.eazytec-cloud.com/nexus/content/groups/harmonyos")
gradlePluginPortal()
google()
mavenCentral()
}
}
rootProject.name = "ohos_datetimepicker"
include(":datetimepicker")
include(":example:nativeApp")
gradle/libs.versions.toml 收敛到两行版本——整条构建里 Compose 彻底缺席:
[versions]
kotlin = "2.2.21-1.0.0"
kotlinxDatetime = "0.7.1-0.5.0-15"
[libraries]
kotlinx-datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "kotlinxDatetime" }
[plugins]
kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
2.3 编译真实引擎
datetimepicker/build.gradle.kts 收敛为纯 KMP 模块:
kotlin {
applyDefaultHierarchyTemplate()
jvmToolchain(21)
jvm() // 交叉验证目标:内核规则在 JVM 上与 java.time 对拍
ohosArm64()
ohosX64()
sourceSets {
commonMain.dependencies {
implementation(libs.kotlinx.datetime) // 0.7.1-0.5.0-15,ohos 副品线
}
jvmTest.dependencies {
implementation(kotlin("test"))
}
}
}
实测:
./gradlew :datetimepicker:compileKotlinJvm # BUILD SUCCESSFUL(仅上游代码的弃用警告)
引擎三处最小适配都在 DateTimeOperations.kt 里,各自带注释说明缘由:DateTimePickerColors 拆走去 composeMain;getCalendarDates(localDate, today = getLocalDate()) 增加可选参数让桥传入确定时间(ArkTS 拥有"now",内核保持确定性——WheelPicker 同款纪律);start.until(end, DateTimeUnit.DAY).toInt()(0.6.1 返回 Int,0.7.x 改 Long)。
SelectionEngine 是全仓唯一的新增业务源文件,移植时把上游两个代码块逐行对齐:
object SelectionEngine {
fun select(month, clicked, range, start, end, today = ...): Selection
fun evaluate(month, range, start, end, today = ...): Selection // 月份导航后的旗标重算
}
连上游的三条怪癖都原样保留:范围含禁用日 → 坍缩成第二个日期的单选并触发 onSelectDate(不是 onRangeSelected);range 模式点击早于起点 → 重置选择到点击日;!range 才触发 onSelectDate。禁用格点击守卫返回 event = NONE——对应上游 enabled = false 时"无状态键变化、derivedStateOf 不重跑、无回调"的语义。
2.4 测试的处置:ohos 没有测试 runner
ohos 目标没有测试 runner,处置与已审仓库同款:测试统一在 JVM 跑(jvmTest),根构建全局禁用 ohos 测试任务:
subprojects {
tasks.matching { it.name.contains("Ohos", ignoreCase = true) && it.name.contains("Test") }
.configureEach { enabled = false }
}
JVM 测试跑的就是编进 .so 的那份 commonMain,交叉验证的有效性由此成立。
第 3 阶段:桥协议建模
3.1 JSON 契约
cz.kudladev/**(上游引擎 + SelectionEngine 移植,原样编译)
↓ getCalendarDates / select / evaluate / format
Bridge.kt(@CName × 1,扁平 JSON 进出)
↓ UTF-8 JSON
N-API dateTimePickerDispatch()
↓
ArkUI Index.ets(日历 + 面板单页)
请求与响应:
请求 : {"op":"info"}
{"op":"config","tz":"Europe/Prague","disablePast":true,"disabled":"2026-10-24,2026-10-25",
"monthNames":"ENGLISH_FULL","dayNames":"ENGLISH_ABBREVIATED"}
{"op":"month","month":"2026-10-01","now":"2026-10-20","range":false,"start":null,"end":null}
{"op":"select","clicked":"2026-10-15","month":"2026-10-01","now":"2026-10-20",
"range":true,"start":"2026-10-10","end":null}
{"op":"format","iso":"2026-10-15"}
{"op":"today","epoch":1763030000000}
响应 : {"ok":true,"year":2026,"month":10,"monthName":"October 2026",
"dayLetters":"M|T|W|T|F|S|S","start":"2026-10-15","end":null,
"final":"Thu, October 15","event":"DATE","count":42,
"cells":"28,2026-09-28,0,0,0,0,0,0;...;1,2026-11-01,0,0,0,0,0,0"}
{"ok":false,"error":"..."}
三个设计点:其一,42 格网格打包成字符串(分号连格、逗号连字段,字段全为 ASCII 数字与 ISO 日期),避免在 ArkTS 强类型上下文里 JSON.parse 出嵌套对象——这是 FlexibleBottomSheet 适配时记录过的运行时限制,字符串网格解析一行 split(';').map(s => s.split(',')) 到位;其二,dayLetters 由内核下发(镜像上游 DayOfWeek.entries 渲染 day.name[0] 的 M T W T F S S),渲染端不自己排序星期;其三,now 永远由 ArkTS 传入(op=today 用 epoch 毫秒 + 配置时区换算),内核自身不依赖系统时钟,行为完全确定、可测试。
3.2 状态归桥还是归页
选择状态(start / end)由 ArkTS 持有并随每次请求回传,桥不保存会话——与 KStore 每请求现开店同款的无状态纪律。判定则全部回桥:点击路由、范围旗标、坍缩检查、格式化文本、回调事件类型都由 SelectionEngine 算完返回,ArkTS 只做 @State 刷新。月份导航调 op=month(内部走 evaluate 重算旗标),点击调 op=select,改配置先 op=config 再取网格。
3.3 错误通道
导出整体 try/catch,Kotlin 异常永远不跨 FFI:
@CName("DateTimePickerDispatch")
fun dateTimePickerDispatch(request: String): String {
return try {
val json = MiniJson.parse(request)
when (json.str("op")) { /* 六 op 分发 */ }
} catch (t: Throwable) {
err(t.message ?: t::class.simpleName ?: "error")
}
}
非法 JSON、缺 now / clicked、非法 ISO 日期、不认识的 op、disabledDates 里的非法串(上游 DateTimePickerDefaults init 块的 IllegalArgumentException),全部走同一个 ok:false 通道,ArkTS 侧统一渲染。
第 4 阶段:原生桥接(技术难点)
4.1 三层桥接
ArkTS
│ JSON string
▼
C++ N-API entry(libdatetimepicker_napi.so)
│ const char*
▼
Kotlin/Native C ABI(libohosdatetimepicker.so)
│ DateTimePickerDispatch
▼
UTF-8 JSON + DisposeString
直接导出 Kotlin 对象会暴露 ABI 和生命周期问题;让页面复刻选择规则又等于手抄状态机,所以选择 C ABI + JSON——单函数分发,op 即协议版本。
4.2 Kotlin/Native 导出函数
@CName("DateTimePickerDispatch")
fun dateTimePickerDispatch(request: String): String // 六 op 一口进
| C ABI 符号 | ArkTS 方法 | 覆盖内核 |
|---|---|---|
DateTimePickerDispatch | dateTimePickerDispatch(req) | info / config / month / select / format / today 全部分发 |
版本握手不设独立符号(KStore 的 KStoreVersion 模式),统一走 op=info,返回 fork 1.0.7-ohos.1 / Kotlin 2.2.21-1.0.0 / datetime 0.7.1-0.5.0-15——一次握手把三条版本线全部带到真机。
4.3 C++ N-API 方法分发
napi_property_descriptor desc[] = {
{"dateTimePickerDispatch", nullptr, NapiDateTimePickerDispatch, nullptr, nullptr, nullptr,
static_cast<napi_property_attributes>(napi_default | napi_enumerable), nullptr},
};
回调统一三步:napi_typeof 校验必须是 string、OH_LOG_INFO 打字节数、调 KN 函数并包装返回。KN 返回的字符串必须经 libohosdatetimepicker_symbols()->DisposeString 归还 Kotlin 运行时,绝不 free()。libohosdatetimepicker_api.h 按社区已审仓库同款手写(只声明 ExportedSymbols 结构与 DateTimePickerDispatch),不拖整份生成头。
4.4 CMake 和类型声明
add_library(ohosdatetimepicker SHARED IMPORTED)
set_target_properties(ohosdatetimepicker PROPERTIES
IMPORTED_LOCATION ${KN_SO}
IMPORTED_NO_SONAME TRUE)
add_library(datetimepicker_napi SHARED napi_init.cpp)
target_link_libraries(datetimepicker_napi PUBLIC
libace_napi.z.so
libhilog_ndk.z.so
ohosdatetimepicker)
OHOS_ARCH 为空或 libohosdatetimepicker.so 缺失直接 FATAL_ERROR(提示先跑 link 任务);$ORIGIN rpath 加 POST_BUILD 拷贝保证 NAPI so、KN so、libc++ 三件套同目录,dlopen 时互相可见。
export const dateTimePickerDispatch: (request: string) => string;
第 5 阶段:ArkUI 宿主
5.1 启动握手和状态注入
aboutToAppear(): void {
this.now = toDateIso(new Date()); // "now" 由 ArkTS 拥有
this.dispatchInfo(); // op=info 版本握手
this.dispatchConfig(); // op=config 安装默认配置
this.refreshGrid(); // op=month 首屏网格
}
private onCellClick(cell: Cell): void {
if (!this.clickable || cell.dis === 1) return; // clickable 开关 + 禁用格短路
const req: SelectRequest = {
op: 'select', clicked: cell.iso, month: this.monthIso,
now: this.now, range: this.range,
};
if (this.startIso !== null) req.start = this.startIso; // 空值省略字段,不发 JSON null
const res = JSON.parse(dateTimePickerDispatch(JSON.stringify(req))) as GridRes;
this.apply(res); // start/end/final/cells 全量刷新
}
请求侧一条纪律:start / end 为空时省略字段而不是发 JSON null——桥的手写 MiniJson 把字面 null 当文本 "null" 处理,省略字段才是干净通道(桥侧同步做了 "null" 容错双保险)。
5.2 页面交互
单页自上而下:标题与版本握手行、月份名 + 左右箭头、周标题行(dayLetters.split('|'))、42 格网格、模式与开关面板、结果文本、事件回调日志、关于页脚。网格渲染规则与上游 DateTimePickerColors 的角色一一对应:今天 primary 描边圆、选中 primary 实心圆白字、范围内 20% 底色且起点左两角 / 终点右两角大圆角(对应上游 RoundedCornerShape(100.dp) 的单侧圆角)、禁用灰字(onSurface 20%)、非当月 50% 字色、格宽 aspectRatio(1) 撑满。面板四开关:Single/Range、Disable past dates、Disabled dates(演示注入当月 24/25)、Clickable;每次变更先 op=config 再取网格。
5.3 对比度和错误边界
事件日志按 [HH:mm:ss] 时间戳滚动,event=DATE 显示 onSelectDate(...)、RANGE 显示 onRangeSelected(start, end)、NONE 不记录;ok:false 时错误原文进结果区与日志。Native 调用本身抛错(理论不该发生)同样被 try/catch 收进日志区。
第 6 阶段:示例与验证
6.1 工程结构
example/
├── nativeApp/
│ ├── build.gradle.kts 双 ABI sharedLib(baseName=ohosdatetimepicker)+ cxxShared + copyKnSo*
│ └── src/ohosMain/kotlin/example/nativeapp/
│ └── Bridge.kt @CName × 1 + MiniJson(扁平 JSON,继承自 cmp_qrose 审核模式)
└── harmonyApp/
├── build-profile.json5 signingConfigs 留空(签名由 DevEco 自动生成回填)
├── AppScope/app.json5 bundleName org.terminator.ohos.datetimepicker、versionName 1.0.7-ohos.1
└── entry/
├── build-profile.json5 externalNativeOptions + abiFilters [arm64-v8a, x86_64]
├── libs/{arm64-v8a,x86_64}/ linkDebugShared* 经 copyKnSo* 回填的三件套(gitignore)
├── src/main/cpp/CMakeLists.txt IMPORTED 引入 KN so、$ORIGIN rpath
├── src/main/cpp/napi_init.cpp N-API 导出、参数检查、DisposeString
├── src/main/cpp/include/libohosdatetimepicker_api.h
├── src/main/cpp/types/libdatetimepicker_napi/{index.d.ts, oh-package.json5}
└── src/main/ets/pages/Index.ets ArkUI 验收页面
6.2 构建、签名和安装
./gradlew :datetimepicker:jvmTest # 21 项交叉验证
./gradlew :example:nativeApp:linkDebugSharedOhosArm64 :example:nativeApp:linkDebugSharedOhosX64
cd example/harmonyApp
hvigorw assembleHap -p product=default -p buildMode=debug --no-daemon
签名在 DevEco Studio 的 File → Project Structure → Signing Configs 中勾选自动生成,证书写入本机,不进入仓库(仓库内 build-profile.json5 的 signingConfigs 保持空数组)。
hdc list targets -v
hdc -t <设备序列号> install -r \
entry/build/default/outputs/default/entry-default-signed.hap
hdc -t <设备序列号> shell aa start \
-a EntryAbility -b org.terminator.ohos.datetimepicker -m entry
四、完整代码对照
4.1 整体架构
cz.kudladev/DateTimeOperations(上游日历引擎,原样编译)
│ getCalendarDates(42 格) / addMonth / subtractMonth / getMonthName / format / Defaults
cz.kudladev/SelectionEngine(上游状态机 1:1 移植)
│ select / evaluate(点击路由 + 范围旗标 + 坍缩检查)
Bridge.kt + MiniJson
│ DateTimePickerDispatch(info/config/month/select/format/today)
libdatetimepicker_napi.so
│ dateTimePickerDispatch
Index.ets
│ JSON parse + @State 更新
▼
ArkUI 真机页面(日历 + 面板 + 日志)
4.2 文件清单
| 文件 | 职责 |
|---|---|
settings.gradle.kts | 极简 ohos 构建、eazytec Maven 置前、ohos_datetimepicker |
gradle/libs.versions.toml | Kotlin 2.2.21-1.0.0 + kotlinx-datetime 0.7.1-0.5.0-15 钉版(两行版本) |
build.gradle.kts(根) | 版本 1.0.7-ohos.1、全局禁用 Ohos*Test* 任务 |
datetimepicker/build.gradle.kts | 纯 KMP:jvm + ohosArm64 + ohosX64,commonMain 仅 datetime 一依赖 |
datetimepicker/src/commonMain/.../DateTimeOperations.kt | 上游引擎原样 + 3 处带注释的最小适配 |
datetimepicker/src/commonMain/.../SelectionEngine.kt | 唯一新增业务源文件(状态机移植) |
datetimepicker/src/composeMain/ | 上游 Compose UI 隔离(DatePicker.kt + DateTimePickerColors.kt) |
datetimepicker/src/jvmTest/ | 21 项交叉验证(CalendarGrid / SelectionEngine / MonthArithmetic) |
example/nativeApp/build.gradle.kts | 双 ABI sharedLib、cxxShared 定位 libc++、copyKnSo* 回填 |
nativeApp/.../Bridge.kt | 1 个 C ABI、六 op 分发、MiniJson、错误通道 |
harmonyApp/.../cpp/napi_init.cpp | N-API 导出、参数检查、DisposeString |
harmonyApp/.../cpp/CMakeLists.txt | IMPORTED 引入 KN so、$ORIGIN rpath、POST_BUILD 三件套 |
harmonyApp/.../ets/pages/Index.ets | ArkUI 验收页面 |
README.OpenHarmony.md / _CN.md | 鸿蒙适配说明(中英双份,CN 为主文档 17 节) |
4.3 关键 API 对照
| 层次 | API | 作用 |
|---|---|---|
| 引擎 | getCalendarDates(month, today) | 42 格网格:前后月补齐、今天 / 当月 / 禁用标记 |
| 引擎 | addMonth / subtractMonth / getMonthName / format | 月份导航、标题与结果文本 |
| 引擎 | DateTimePickerDefaults | 时区 / 月份名 / 星期名 / 禁用日期 / 禁过去 / formater |
| 状态机 | SelectionEngine.select | 一次点击的完整路由(含禁用守卫与坍缩检查) |
| 状态机 | SelectionEngine.evaluate | 月份导航后的旗标重算(对应上游 derivedStateOf) |
| Native | DateTimePickerDispatch | C ABI JSON 导出(六 op) |
| N-API | dateTimePickerDispatch(req) | 向 ArkTS 暴露能力 |
| ArkTS | onCellClick / refreshGrid | 触发点选 / 取网格并刷新 UI |
4.4 ArkTS 与 Kotlin 的边界
const res = JSON.parse(dateTimePickerDispatch(JSON.stringify({
op: 'select', clicked: '2026-10-25', month: '2026-10-01',
now: '2026-10-20', range: true, start: '2026-10-15',
}))) as GridRes;
// res.event === 'RANGE';res.start === '2026-10-15';res.end === '2026-10-25'
// res.final === 'Thu, October 15 - Sun, October 25';res.cells 带全部范围旗标
val selection = SelectionEngine.select(
month = LocalDate(2026, 10, 1), clicked = LocalDate(2026, 10, 25),
range = true, start = LocalDate(2026, 10, 15), end = null,
today = LocalDate(2026, 10, 20),
)
两者之间只传输扁平 JSON 字符串,不传输 Kotlin 对象或未校验的动态结构。
五、关键决策说明
决策 1:拆源码集而不是替换 UI 框架
上游全部价值在 commonMain 的两份文件里,其中日历引擎只依赖 kotlinx-datetime。把 Compose UI 挪进未注册的 composeMain(不进 sourceSets、永不编译),引擎与状态机留在 commonMain 原样编译——Android/iOS 侧将来重新注册 source set 即可恢复,fork 对上游的侵入被压到三个带注释的最小适配点。
决策 2:kotlinx-datetime 换副品线,不自带历法
WheelPicker 适配时自带了 CivilDateTime(Howard Hinnant 算法的 proleptic 格里历),因为它的内核纪律是零依赖。本库上游本来就依赖 kotlinx-datetime,社区 Maven 又已有带 ohos 变体的 0.7.1-0.5.0-15(requires kotlin-stdlib 2.2.21-0.5.0-16,与本仓 2.2.21 钉法同代)——再手抄历法等于把 getCalendarDates 的边界行为重写一遍,引入漂移风险。换线代价只有两处 API 适配(until() 返回 Long、MonthNames.ENGLISH_SHORT 改名 ENGLISH_ABBREVIATED),全部带注释可查。
决策 3:选择状态机移植成 SelectionEngine,判定不进 ArkTS
点击路由与旗标重算上游写在 composable 里,ArkTS 手抄必然漂移(尤其是"范围含禁用日坍缩到第二个日期并触发 onSelectDate"这条反直觉规则)。把它移植成显式入参出参的纯 Kotlin 对象,21 项 JVM 测试里 10 项专门钉住这些怪癖,真机上 ArkTS 只是薄渲染层——这就是"判定在 Kotlin,渲染在 ArkTS"。
决策 4:42 格网格用字符串打包,不用嵌套 JSON
ArkTS 运行时在强类型上下文里拒绝 JSON.parse 派生的嵌套对象(FlexibleBottomSheet 适配记录过)。网格打包成 day,iso,cur,today,dis,sel,rng,startOfRange 八字段逗号、分号连格的字符串,split 两次到位,协议双方都零歧义。
决策 5:jvmTest 交叉验证 + “now” 归 ArkTS
ohos 没有测试 runner,引擎规则全部在 JVM 上与 java.time 对拍(月视图 2023–2027 全量月份、闰年二月、跨年十二月、5 行 / 6 行网格、补位日期、禁用与过去规则);桥的每次调用都显式携带 now,内核不依赖系统时钟——确定性既让测试可复跑,也让"今天"来自真机时钟而非编译期常量。
六、测试与验证
6.1 测试环境
本次验证使用 Windows(x86_64)、JDK 21(Temurin 21.0.12)、Gradle 8.14.1、DevEco Studio 5.1.0 Release 与鸿蒙真机(arm64)。
6.2 JVM 交叉验证
./gradlew :datetimepicker:jvmTest
21 项用例全部通过(编进 .so 的那份 commonMain 与 java.time / 手推上游行为对拍):
| 测试类 | 用例数 | 钉住什么 |
|---|---|---|
CalendarGridTest | 7 | 42 格规则:周一为首列、当月天数与 java.time 全量对拍(2023–2027)、前后月补位日期、闰年二月、跨年十二月、今天与过去禁用、显式禁用与非法输入 |
SelectionEngineTest | 10 | 状态机怪癖:单选 / 范围路由、点击早于起点重置、第三击重置、范围含禁用日坍缩到第二个日期、禁用格与过去格不可点、跨月旗标重算 |
MonthArithmeticTest | 4 | addMonth / subtractMonth 与 java.time.plusMonths 全量对拍(含 31 日钳到月末)、月份名与默认格式化(“Thu, October 15”)、getLocalDate 冒烟 |
换线没有漂移的直接证据:0.6.1 → 0.7.1-0.5.0-15 后引擎源文件几乎零改动,测试里唯一被上游行为"纠正"的预期是 dayOfMonth() 的零填充(“Mon, January 01”——0.6.1 与 0.7.1 默认一致,是手写预期时想当然)。
6.3 HAP 验证
:datetimepicker:compileKotlinJvm: BUILD SUCCESSFUL
:datetimepicker:jvmTest: BUILD SUCCESSFUL(21/21)
:example:nativeApp:linkDebugSharedOhosArm64 / OhosX64: BUILD SUCCESSFUL
copyKnSoArm64 / copyKnSoX64: 三件套回填 entry/libs/{arm64-v8a,x86_64}
llvm-nm -D: DateTimePickerDispatch / libohosdatetimepicker_symbols 均为全局 T 符号
未 strip 的 debugShared libohosdatetimepicker.so arm64 约 8.2 MB、x86_64 约 7.7 MB(含 Kotlin/Native 运行时与 kotlinx-datetime),libc++_shared.so 约 1.8 MB——比 KStore 的 14.8 MB 小得多,因为引擎只拖了 datetime 一条依赖线。
6.4 功能用例
- 启动后版本握手行显示
fork 1.0.7-ohos.1 · Kotlin 2.2.21-1.0.0 · datetime 0.7.1-0.5.0-15(来自op=info真实 Native 调用,非静态文字); - 首屏网格为 2026 年 10 月:周标题
M|T|W|T|F|S|S(桥下发),10-20 今天描边圆,9 月 28–30 与 11 月 1 为补位格(50% 字色); - Single 模式连续点选:每次点击结果区更新
final文本,事件日志按时间戳记录onSelectDate(yyyy-MM-dd); - Range 模式:首点无回调(
event=NONE),二点成范围后onRangeSelected(start, end)触发,中间格 20% 底色、起点左圆角 / 终点右圆角; - 打开 Disable past dates:今天之前的格子变灰不可点(引擎判定,非 UI 过滤);
- 打开 Disabled dates:当月 24/25 变灰;Range 跨过禁用日的选择坍缩为第二个日期的单选,日志记录的是
onSelectDate; - 关闭 Clickable:点击任何格子无反应(上游 1.0.6 引入、1.0.7 保留的特性);
- 月份左右导航跨年(12 月 → 次年 1 月)网格正确补位。
6.5 验证结论
日历引擎、选择状态机、kotlinx-datetime 运行时、Native 动态库、C ABI、N-API、ArkUI 页面和 HAP 已完成链路验证。应用在鸿蒙真机上成功安装并进入前台,单选模式的今天描边、选中实心圆与 onSelectDate 回调日志均按上游规则工作(见下节效果图);范围 / 禁用 / clickable 等面板能力与跨月行为由 JVM 交叉验证与桥接协议共同覆盖。
七、运行效果
7.1 真机截图

7.2 命令速查
git clone https://atomgit.com/oh-tpc/datetimepicker-kmp.git
cd datetimepicker-kmp
export JAVA_HOME="/path/to/jdk-21"
./gradlew :datetimepicker:jvmTest # 21 项交叉验证
./gradlew :example:nativeApp:linkDebugSharedOhosArm64 :example:nativeApp:linkDebugSharedOhosX64
cd example/harmonyApp
hvigorw assembleHap -p product=default -p buildMode=debug --no-daemon
hdc install -r entry/build/default/outputs/default/entry-default-signed.hap
hdc shell aa start -a EntryAbility -b org.terminator.ohos.datetimepicker -m entry
hdc shell hilog | grep DateTimePickerDemo
八、遗留问题与改进方向
8.1 踩坑复盘
- 只改 ArkTS 页面不算 KMP 适配,必须让
:datetimepicker真正编译成ohosArm64/ohosX64动态库;但 Compose UI 库的 UI 层编不进去,源码集拆分(composeMain 隔离)是第一步而不是可选项; settings.gradle.kts里 eazytec 双仓必须排在pluginManagement与dependencyResolutionManagement最前,否则配置期直接Unresolved reference: ohosArm64;kotlinx-datetime的 ohos 副品线版本选择要看.module的requires:0.8.0-0.1.0-02带 ohos 变体但 requires stdlib 2.4.20(新工具链线),0.8.0-0.6.x-compat反而没有 ohos 变体——名字里带 compat 不等于有 ohos,名字里没 ohos 不等于没有,逐个查元数据是唯一可靠办法;- 0.7.x 的 API 微差要靠编译期暴露:
until()从Int变Long(补toInt())、MonthNames.ENGLISH_SHORT改名ENGLISH_ABBREVIATED(Bridge 首次 native 编译才报错,JVM 编译不出来——因为 Bridge 在 ohosMain); - Kotlin 成员扩展函数(
DateTimeOperations.addMonth/format/getMonthName)跨文件使用必须显式import cz.kudladev.DateTimeOperations.format——上游DatePicker.kt就是这么 import 的,测试与 Bridge 第一版漏了会报Unresolved reference; - 上游
.gitignore忽略了gradle.properties/gradlew/gradlew.bat,fork 必须恢复提交,否则 AtomGit 上的仓库无法开箱构建; MiniJson对字面null的处理是文本"null":请求侧空值省略字段,桥侧parseDate对"null"容错,双保险;- 测试预期不要手算星期:12 月跨年补位两次算错(35 格与 42 格),先写 java.time 断言再写预期才是对拍的意义;
dayOfMonth()格式化默认零填充(“Mon, January 01”),0.6.1 与 0.7.1 一致——写预期前先跑一次;- HAP 缺当前 ABI 的 so 会报安装失败
9568347,abiFilters必须双 ABI,每个 ABI 三件套缺一不可; - KN 返回字符串必须
DisposeString归还运行时,N-API 层不能free(); - 签名材料由 DevEco 自动写入
build-profile.json5,仓库内signingConfigs保持空数组,证书绝不进入 AtomGit。
8.2 已知问题
- 上游
DatePickercomposable 与DateTimePickerColors留在composeMain不编译;Android/iOS demo 需重新注册 source set 才能恢复(fork 定位是鸿蒙交付); - android / iOS framework 目标与 mavenPublish 发布段剥离,本 fork 不发 Maven Central;
DateTimePickerDefaults是进程级单例状态(上游如此),桥的config覆写全局——ArkTS UI 线程单线程调用下无碍(KN 亦无synchronized);Clock/Instant.toLocalDateTime/dayOfMonth在 0.7.1 均为弃用警告(保留上游写法,行为不变);- HAP 安装必须使用与 bundle name
org.terminator.ohos.datetimepicker匹配的签名。
8.3 未来优化方向
- 补充 Range / 禁用 / clickable 模式的真机截图与自动化截图脚本;
- 桥接补
op=format的自定义格式模板参数(当前用配置的 formater); - 上游 Coming soon 的 Time picker / Time range picker 发布后跟进移植;
- 评估
.har形态发布,让 ArkTS 工程直接依赖而不是走 so 三件套; - 在 CI 中加入 ohos 编译、ELF 符号检查和 HAP 构建。
九、总结
9.1 核心难点回顾
上游 datetimepicker-kmp(引擎 + Compose UI 两文件)
→ 确认 datetime ohos 制品线(0.7.1-0.5.0-15,requires 2.2.21 线)
→ 工具链对齐 Kotlin 2.2.21-1.0.0(Gradle 升 8.14.1)
→ 根构建替换(cmp_qrose 模式,Compose 全部缺席)
→ 拆源码集:commonMain 引擎 + composeMain 隔离
→ SelectionEngine 移植(上游状态机 1:1,怪癖保留)
→ C ABI(@CName × 1)+ 六 op 扁平 JSON + 字符串网格
→ C++ N-API(DisposeString 纪律)
→ ArkTS 渲染层(判定全走桥)
→ ArkUI 真机验收(单选 / 今天 / 回调日志)
9.2 封装层次
- 引擎层:上游原版
DateTimeOperations(42 格生成、禁用规则、月份运算、格式化); - 状态机层:上游 composable 逻辑的 1:1 移植
SelectionEngine(路由 / 旗标 / 坍缩); - Native 层:生成双 ABI 动态库并通过 1 个 C ABI 输出 JSON;
- N-API 层:完成参数检查、字符串转换和 Native 内存释放;
- ArkTS 层:管理页面状态、网格渲染、面板开关、结果与日志;
- DevEco 层:完成 CMake、HAP、签名、安装和真机运行。
9.3 三条经验
- Compose UI 库的适配答案是"拆"而不是"编":引擎 / 判定与渲染分离的库(日期、滚轮、折叠、条码都算),把判定侧编进 ohos、渲染侧交给 ArkTS,比等 CMP 工具链对齐现实得多;拆分线画在依赖边界上(本库恰好是一根 Compose import);
- 制品线可达性决定内核策略:WheelPicker 零依赖所以自带历法,本库有 ohos 副品线所以原样依赖 kotlinx-datetime——同一个社区、同一个月、两条相反的决策都是对的,因为决策依据是当天的制品可达性而不是教条;
- 交叉验证钉的是"怪癖"而不只是功能:21 项测试里最有价值的是钉住"范围含禁用日坍缩到第二个日期""range 模式首点无回调"这类反直觉行为——这些是手抄 ArkTS 时最容易漂移、也最难被肉眼发现的地方。
9.4 适配成果
当前适配已完成:
datetimepicker-kmp 1.0.7(MIT)日历引擎与选择状态机的双 ABI Kotlin/Native 编译(上游引擎原样 + 1 个移植源文件,3 处带注释最小适配);- HarmonyOS kotlinx-datetime 制品线在日期库上的落地(
0.7.1-0.5.0-15,ohosArm64 / ohosX64 变体经.module验证); - 1 个 C ABI 符号和 C++ N-API 包装层(
llvm-nm验证全局 T 符号),六 op 扁平 JSON 协议 + 字符串网格; - DevEco Stage ArkUI 单页验收页面(日历网格、Single/Range、禁过去 / 禁指定 / clickable 开关、结果文本、事件回调日志、版本握手);
- 21 项 JVM 交叉验证全绿(对拍
java.time与手推上游行为)+ 签名、设备安装和真机验证(见效果图); - AtomGit 项目文档、OpenHarmony README(中英双份)和本地效果图。
适配过程 FAQ
以下问题来自
ohosArm64目标接入、NAPI 桥接、HAP 构建与真机验收的实际流程,按出现频率排序。
Q1:DevEco Studio Sync 失败或 ohpm 依赖下载超时,怎么处理?
- 确认 DevEco Studio 版本与工程
build-profile.json5的compatibleSdkVersion一致; - 在
File > Settings > Build, Execution, Deployment > Ohpm配置可用的 ohpm 仓库镜像或代理; - 删除
oh_modules、oh-package-lock.json5与.hvigor缓存后重新 Sync; - 公司网络需要代理时,同时给 ohpm 与 hvigor 配置
http.proxy。
Q2:CMake 报 Missing .../libxxx.so 或找不到 Kotlin/Native 动态库?
- 先在
example目录执行./gradlew :nativeApp:linkDebugSharedOhosArm64(模拟器用linkDebugSharedOhosX64); - 确认
libxxx.so与libc++_shared.so已复制到ohos/entry/libs/<abi>/; - 检查
entry/build-profile.json5的abiFilters与设备 ABI 是否一致(真机arm64-v8a,模拟器x86_64); - 清理
entry/.cxx与entry/build后重试。
Q3:ArkTS 报 Cannot find module 'libxxx_napi.so' 或调用返回 undefined?
- 确认 CMake 的
add_library(<target> SHARED ...)名称与import ... from 'libxxx_napi.so'完全一致; - 解包 HAP,确认
libs/<abi>/libxxx_napi.so已打包; - 删除
entry/build、entry/.cxx与oh_modules后重新构建; - 检查
module.json5的srcEntry与页面路径是否正确。
Q4:真机安装报 signature verification failed 或 install failed?
- 在 DevEco Studio 的
Signing Configs配置证书、Profile 与密码; bundleName必须与签名 profile 中的 bundle name 一致;- 设备需加入 profile 的
deviceIds,调试证书有效期需覆盖当前时间; - 用
hdc list targets确认设备在线,再执行hdc install -r xxx-signed.hap。
Q5:应用启动白屏、闪退或 NAPI 调用崩溃?
- 用
hdc hilog | grep -i "napi\|kotlin"查看原生日志; - 确认
libc++_shared.so与 Kotlin/Native 工具链版本匹配,且 ABI 与设备一致; - 检查 C ABI 返回的字符串是否由 Native 侧分配、ArkTS 侧复制后是否调用
XxxFree释放; - 在 DevEco 的 Native 调试配置中附加
lldb,定位空指针与越界访问。
Q6:Gradle 报 ohosArm64 目标不存在或 Kotlin/Native 编译失败?
- 使用鸿蒙发行版 Kotlin(如
2.2.21-1.0.0),并在settings.gradle.kts配置 eazytec 仓库; - Gradle 建议 8.x、JDK 21,版本不匹配时先统一工具链;
- 确认
kotlin { ohosArm64() }已应用,且local.properties的 SDK 路径有效; - 清理
~/.gradle/caches中对应版本的 Kotlin/Native 缓存后重试。
Q7:HAP 构建报 SDK 版本不匹配或 compatibleSdkVersion 错误?
- 对齐 DevEco Studio、HarmonyOS SDK 与
build-profile.json5中的compatibleSdkVersion; - 升级 DevEco Studio 后同步升级 hvigor 与 SDK,避免
modelVersion与 IDE 不匹配; - 命令行构建时设置
DEVECO_SDK_HOME指向 DevEco 的sdk目录。
Q8:命令行 hvigor 构建失败,但 DevEco 里能构建?
- 使用 DevEco 自带的 node 与 hvigor:
"$DEVECO/Contents/tools/node/bin/node" "$DEVECO/Contents/tools/hvigor/bin/hvigorw.js" ...; - 设置
JAVA_HOME、NODE_HOME、DEVECO_SDK_HOME; - 加上
--no-daemon排除守护进程缓存影响,并清理entry/build与.hvigor。
参考文档
更多推荐


所有评论(0)