本文记录 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 日期 / 日期范围选择器库:一个 DatePicker composable 覆盖单选与范围两种模式,内部由 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。
cells42 格网格: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 的移植,也不替代它。上游 DatePicker composable 原样留在 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 MultiplatformHarmonyOS Kotlin 2.2.21-1.0.0(eazytec Maven)
JDK21(Temurin,org.gradle.java.home 指向 C:\Program Files\Eclipse Adoptium\jdk-21.0.12.101-hotspot)
OpenHarmony 目标ohosArm64 + ohosX64(jvm() 保留作交叉验证)
Gradle8.14.1(上游 wrapper 8.7 升级,对齐 qrose / WheelPicker 已审钉法)
kotlinxkotlinx-datetime 0.7.1-0.5.0-15(ohos 制品线;上游官方线为 0.6.1)
DevEco product5.1.0(18) 兼容
DevEco Studio5.1.0 Release
ABIarm64-v8a + x86_64
Bundleorg.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 / DateTimePickerColorskotlinx-datetime + 一个 Compose import(ui.graphics.Color)拆分编译:Colors 拆走,其余原样进 commonMain
DatePicker.ktMaterial3 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 方法覆盖内核
DateTimePickerDispatchdateTimePickerDispatch(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.tomlKotlin 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.kt1 个 C ABI、六 op 分发、MiniJson、错误通道
harmonyApp/.../cpp/napi_init.cppN-API 导出、参数检查、DisposeString
harmonyApp/.../cpp/CMakeLists.txtIMPORTED 引入 KN so、$ORIGIN rpath、POST_BUILD 三件套
harmonyApp/.../ets/pages/Index.etsArkUI 验收页面
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)
NativeDateTimePickerDispatchC ABI JSON 导出(六 op)
N-APIdateTimePickerDispatch(req)向 ArkTS 暴露能力
ArkTSonCellClick / 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 / 手推上游行为对拍):

测试类用例数钉住什么
CalendarGridTest742 格规则:周一为首列、当月天数与 java.time 全量对拍(2023–2027)、前后月补位日期、闰年二月、跨年十二月、今天与过去禁用、显式禁用与非法输入
SelectionEngineTest10状态机怪癖:单选 / 范围路由、点击早于起点重置、第三击重置、范围含禁用日坍缩到第二个日期、禁用格与过去格不可点、跨月旗标重算
MonthArithmeticTest4addMonth / 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 功能用例

  1. 启动后版本握手行显示 fork 1.0.7-ohos.1 · Kotlin 2.2.21-1.0.0 · datetime 0.7.1-0.5.0-15(来自 op=info 真实 Native 调用,非静态文字);
  2. 首屏网格为 2026 年 10 月:周标题 M|T|W|T|F|S|S(桥下发),10-20 今天描边圆,9 月 28–30 与 11 月 1 为补位格(50% 字色);
  3. Single 模式连续点选:每次点击结果区更新 final 文本,事件日志按时间戳记录 onSelectDate(yyyy-MM-dd);
  4. Range 模式:首点无回调(event=NONE),二点成范围后 onRangeSelected(start, end) 触发,中间格 20% 底色、起点左圆角 / 终点右圆角;
  5. 打开 Disable past dates:今天之前的格子变灰不可点(引擎判定,非 UI 过滤);
  6. 打开 Disabled dates:当月 24/25 变灰;Range 跨过禁用日的选择坍缩为第二个日期的单选,日志记录的是 onSelectDate;
  7. 关闭 Clickable:点击任何格子无反应(上游 1.0.6 引入、1.0.7 保留的特性);
  8. 月份左右导航跨年(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 踩坑复盘

  1. 只改 ArkTS 页面不算 KMP 适配,必须让 :datetimepicker 真正编译成 ohosArm64 / ohosX64 动态库;但 Compose UI 库的 UI 层编不进去,源码集拆分(composeMain 隔离)是第一步而不是可选项;
  2. settings.gradle.kts 里 eazytec 双仓必须排在 pluginManagement 与 dependencyResolutionManagement 最前,否则配置期直接 Unresolved reference: ohosArm64;
  3. 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 不等于没有,逐个查元数据是唯一可靠办法;
  4. 0.7.x 的 API 微差要靠编译期暴露:until() 从 Int 变 Long(补 toInt())、MonthNames.ENGLISH_SHORT 改名 ENGLISH_ABBREVIATED(Bridge 首次 native 编译才报错,JVM 编译不出来——因为 Bridge 在 ohosMain);
  5. Kotlin 成员扩展函数(DateTimeOperations.addMonth / format / getMonthName)跨文件使用必须显式 import cz.kudladev.DateTimeOperations.format——上游 DatePicker.kt 就是这么 import 的,测试与 Bridge 第一版漏了会报 Unresolved reference;
  6. 上游 .gitignore 忽略了 gradle.properties / gradlew / gradlew.bat,fork 必须恢复提交,否则 AtomGit 上的仓库无法开箱构建;
  7. MiniJson 对字面 null 的处理是文本 "null":请求侧空值省略字段,桥侧 parseDate 对 "null" 容错,双保险;
  8. 测试预期不要手算星期:12 月跨年补位两次算错(35 格与 42 格),先写 java.time 断言再写预期才是对拍的意义;
  9. dayOfMonth() 格式化默认零填充(“Mon, January 01”),0.6.1 与 0.7.1 一致——写预期前先跑一次;
  10. HAP 缺当前 ABI 的 so 会报安装失败 9568347,abiFilters 必须双 ABI,每个 ABI 三件套缺一不可;
  11. KN 返回字符串必须 DisposeString 归还运行时,N-API 层不能 free();
  12. 签名材料由 DevEco 自动写入 build-profile.json5,仓库内 signingConfigs 保持空数组,证书绝不进入 AtomGit。

8.2 已知问题

  • 上游 DatePicker composable 与 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 三条经验

  1. Compose UI 库的适配答案是"拆"而不是"编":引擎 / 判定与渲染分离的库(日期、滚轮、折叠、条码都算),把判定侧编进 ohos、渲染侧交给 ArkTS,比等 CMP 工具链对齐现实得多;拆分线画在依赖边界上(本库恰好是一根 Compose import);
  2. 制品线可达性决定内核策略:WheelPicker 零依赖所以自带历法,本库有 ohos 副品线所以原样依赖 kotlinx-datetime——同一个社区、同一个月、两条相反的决策都是对的,因为决策依据是当天的制品可达性而不是教条;
  3. 交叉验证钉的是"怪癖"而不只是功能: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。

参考文档

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐