开源鸿蒙平台 CMP 三方库「kmp-onboarding」适配全流程

本文记录 kmp-onboarding 接入 OpenHarmony 的完整过程,覆盖引导页 UI 库盘点、Compose 依赖隔离决策、语义镜像核心编写、ohosArm64 / ohosX64 目标、Kotlin/Native 动态库、C ABI/N-API 桥接、ArkUI 引导页真机页面、HAP 构建和真机验收。

kmp-onboarding 是 JetBrains Compose Multiplatform 的 App 引导页(onboarding)UI 库:IntroductionScreen 用 HorizontalPager 承载多页引导(官方 Demo 三页),底部一行控件(左槽 Skip/Back、中间 WormDots 虫形指示器、右槽 Next/Done),每页标题/正文/图标配视差动画,页面背景支持渐变与纯色,IntroState 提供程序化翻页。本次适配与社区早期路线(qrose / haze / PreCompose / voyager 的"零依赖内核提取")有本质区别,与 KStore 的"真编译整库引擎"也不同:这是一个纯 UI 库 ,100% 源码(连 PageViewModel 数据类都持有 @Composable 槽位与 Brush/TextStyle/Dp)粘在 androidx.compose 上,鸿蒙侧没有可用的 Compose 渲染层,也没有可提取的"零依赖内核"。因此本仓走 "语义镜像"路线 (SpotlightOnboarding / ktoast 同款):把上游的翻页状态机、按钮槽位可见性规则、指示器插值几何、背景解析优先级与视差增益逐条对齐地 重写为纯 Kotlin 核心并原样编译进 OpenHarmony 动态库,Compose API 原样停放到 src/composeMain 等工具链对齐后接回;ArkTS 演示页通过 C ABI 驱动这份核心,Swiper 按快照翻页、指示器按"库核心计算的几何"绘制------真机上跑的每一条翻页规则、每一个指示器尺寸、每一个视差系数都来自 Kotlin 侧的库代码而不是页面手写。

项目地址: AtomGit/oh-tpc/kmp-onboarding.git

上游仓库: yskuem/kmp-onboarding

开发工具: 华为云码道

环境安装: 环境安装指南

一、背景

1.1 为什么做 OpenHarmony 平台 KMP/CMP 适配

kmp-onboarding 为 Compose Multiplatform 应用提供"App 首启引导"这一件事的完整答案:IntroductionScreen(pages) 一个入口包住整屏引导,PageViewModel(title, body, image, decoration) 建模每一页,IntroState 封装 PagerState 提供带动画语义的程序化翻页(next()/previous() 用 spring(stiffness = 500f),skipToEnd()/animateScrollTo() 用 tween(350)),ControlsRow 的两个 when 分支决定左槽(非末页优先 Skip、否则翻过页后 Back)与右槽(末页 Done 否则 Next),WormDots 按拖动进度对点宽/高/透明度做插值产生"虫形拉伸",PageView 用三段 graphicsLayer 给标题/正文/图标加视差。KMP 项目把这些能力放在 commonMain,由 Android、iOS 共享。OpenHarmony 应用同样需要这套引导能力,但上游构建(Compose Multiplatform 1.9.1 / Kotlin 2.2.20 / AGP 8.11.2)不会生成 OpenHarmony 变体。

如果只把示例页面改成 ArkTS,页面可以滑动可以点按钮,却无法证明引导状态机在 OpenHarmony ARM64 设备上完成编译、链接和执行。适配需要同时验证状态机语义、指示器几何、槽位规则、Native 动态库、C ABI、N-API、ArkUI 引导页和 HAP 交付链路。

障碍 具体问题
Compose 全面依赖 上游 8 个源文件 100% 依赖 androidx.compose:状态层用 PagerState/animateScrollToPage/derivedStateOf,几何用 Dp/DpSize/Brush/TextStyle/Shape,绘制用 HorizontalPager/Crossfade/AnimatedVisibility/graphicsLayer,槽位用 @Composable lambda。没有任何"纯 Kotlin 内核"可直接抽取。
工具链不一致 社区鸿蒙线是 HarmonyOS Kotlin 2.2.21-1.0.0(eazytec Maven)+ CMP 1.9.2 制品线;上游 Compose 源集基于 Kotlin 2.2.20 + CMP 1.9.1 的 Jetpack 谱系,ohos 侧编译不了 composeMain 源码集。
AGP 不可共存 上游用新版 com.android.kotlin.multiplatform.library 插件(AGP 8.11.2),与鸿蒙工具链无法共存于一个 Gradle 构建,根构建必须整体替换。
语言边界不同 ArkTS 不能直接持有 Kotlin 对象,@Composable 槽位(image/titleWidget/footer)是编译期 lambda,跨不过 C ABI。
UI 语义跨不了桥 IntroState 的 suspend 回调、PagerState 的实时 offsetFraction、WormDots 的逐帧插值都是 Compose 侧概念,C ABI 只能同步一来一回。
交付链路复杂 .so、CMake、N-API、HAP、签名和 hdc 都要单独验收。

因此,本项目把边界放在:上游 UI 语义(状态机 + 几何 + 规则)的 ohos 编译、根构建替换(cmp_qrose 模式)和 C ABI/N-API 桥接层。像素级渲染属于宿主,ArkTS Demo 用 ArkUI 复刻官方三页引导,但每一个行为决策都来自 Kotlin 侧的库核心。

1.2 库提供的能力

Demo 复刻上游官方示例(Welcome / Auto-generated Quizzes / Daily Progress 三页),对应桥接的全部入口:

  • 会话配置(configure) :三页内容(第一页 #FFFFF5F0 → #FFFFE2D6 垂直渐变、后两页纯色 #FFEDEBFA / #FFE4F5F1)与四个按钮开关(showSkip/showNext/showBack/showDone),镜像 IntroductionScreen(...) 的参数语义;
  • 程序化翻页(next/prev/skip/goto) :镜像 IntroState 的四个方法,边界 clamp 与动画规格(spring500 / tween350)随响应下发;
  • 手势翻页落定(settle) :镜像 PagerState.settledPage,Swiper 手势/动画结束后提交页码;
  • 拖动进度(progress) :镜像 PagerState.currentPageOffsetFraction,驱动指示器逐帧插值(本机 SDK 降级实录见 3.6 / 8.1);
  • 槽位可见性(leftSlot/rightSlot) :镜像 ControlsRow 的两个 when------左槽非末页优先 Skip、currentPage > 0 时 Back、否则 64vp 占位;右槽末页 Done 否则 Next;
  • 指示器几何(dots/dotsWidth) :镜像 WormDots 的插值数学,每个点的宽/高/透明度/颜色/圆角由库核心按当前进度计算,ArkUI 只画"库给的几何";
  • 背景解析(bg) :镜像 IntroductionScreen 的 BGSkin 优先级------页渐变 > 页纯色 > 全局背景 > 透明;
  • 页面视差(fx) :镜像 PageView 的三段 graphicsLayer 增益(标题 1-|off|、正文 1-1.2|off|、图标 -40*off 平移与 0.2 下限透明度);
  • 版本握手(version):返回 fork 版本号,来自真实 Native 调用而非静态文字。

响应是统一的全量快照:

字段 作用
ok / op 操作是否成功(失败时 error 带原文)与本次操作名。
page / pageCount / isFirst / isLast 当前会话的页位置。
move 仅转移类 op 返回:`{target, anim:"spring500"
leftSlot / rightSlot `"skip"
bg {color:"#AARRGGBB", gradient?:[...]},gradient 非空时为渐变。
dots / dotsWidth 每个点的 {i,w,h,alpha,color,radius,active} 与指示器总宽。
fx 仅 op=fx 返回:该页视差的七个数值。
version 1.0.3-ohos.1。

1.3 实现适配

维度 要求
语义复用 翻页状态机、槽位规则、指示器几何、背景解析、视差增益五组语义逐条对齐上游 Compose 实现,重写为纯 Kotlin 核心;渲染不属于核心,归宿主。
平台目标 根构建直接声明 ohosArm64 + ohosX64,保留 jvm() 作交叉验证,库模块出 KLIB,example/nativeApp 出双 ABI 动态库。
构建隔离 根构建替换为极简 ohos 构建(cmp_qrose 模式);上游 composeApp / iosApp 示例与 composeMain 停放区留盘不进构建。
桥接稳定 使用 2 个 C ABI 和 UTF-8 JSON 快照,不传递 Kotlin 对象地址;每次 op 返回全量快照,宿主无状态渲染;异常永远不跨 FFI。
UI 完整 三页 Swiper + 自绘 WormDots + 四按钮控制行 + 渐变/纯色背景 + 视差 + 版本握手 + 关于面板 + 错误状态行。
仓库规范 项目说明、文章、效果图和源码链接统一使用 AtomGit。

本项目的 ArkUI 页面不是 Compose Multiplatform UI 实现。kmp-onboarding 的价值在引导语义(翻页规则、指示器数学、槽位逻辑),这些语义可以原样跨平台;像素渲染天然属于宿主,ArkTS Demo 只是把桥暴露的快照画出来。

二、实现路线图

text 复制代码
第 1 阶段:项目初始化     ── 盘点模块与依赖边界、确认"纯 UI 库无内核可抽"、确定交付物
第 2 阶段:目标与依赖打通 ── 根构建替换、eazytec Maven 置前、Kotlin 2.2.21-1.0.0 钉版、禁用 ohos 测试
第 3 阶段:核心提炼       ── composeMain 隔离 8 个 Compose 文件、commonMain 逐条镜像五组语义
第 4 阶段:桥协议建模     ── 9-op JSON 契约、全量快照、move 动画规格、错误通道
第 5 阶段:原生桥接       ── @CName C ABI、C++ N-API、DisposeString 释放、CMake IMPORTED
第 6 阶段:ArkUI 宿主     ── 三页 Swiper、WormDots 绘制、控制行、视差应用、onContentOffset 降级实录

三、逐步实现过程

第 1 阶段:项目初始化

1.1 工程边界
text 复制代码
kmp-onboarding/                     库模块(commonMain 语义核心 + composeMain 停放区 + commonTest 交叉验证)
kmp-onboarding/src/composeMain/     上游 8 个 Compose 文件原样停放(不注册、不编译、不删除)
example/nativeApp/                  Kotlin/Native 动态库(Bridge + MiniJson)
example/harmonyApp/                 DevEco Stage、CMake N-API 和 ArkTS 引导页
composeApp/ iosApp/                 上游 Android/iOS 示例,留盘不进 settings
README.OpenHarmony.md / _CN.md      鸿蒙适配说明(中英双份)
README.upstream.md                  上游原文保存
doc/                                本文章与效果图

构建拓扑走 cmp_qrose 模式 (根构建即 ohos 构建):settings.gradle.kts 只 include :kmp-onboarding 与 :example:nativeApp,rootProject.name = "ohos_kmp_onboarding"。上游的 android/compose/dokka/vanniktech 发布插件、composeApp、iosApp 全部随根构建替换剥离------本 fork 不发 Maven Central。

1.2 工具链矩阵
项目 配置
Kotlin Multiplatform HarmonyOS Kotlin 2.2.21-1.0.0(eazytec Maven)
JDK 21(org.gradle.java.home 指向 Temurin jdk-21.0.12)
OpenHarmony 目标 ohosArm64 + ohosX64(jvm() 保留作交叉验证)
Gradle 8.14.1(wrapper 与 eazytec 分发对齐)
DevEco product 5.1.0(18) 兼容
DevEco Studio 5.1.0 Release
ABI arm64-v8a + x86_64
Bundle org.terminator.ohos.onboarding
版本 1.0.3-ohos.1(上游 1.0.3 + ohos 后缀)
bash 复制代码
export JAVA_HOME="/path/to/jdk-21"
java -version
1.3 源码依赖度盘点

上游库模块的 8 个源文件(全部在 commonMain,逐文件盘点):

文件 依赖形态 处置
api/IntroductionScreen.kt HorizontalPager/Surface/WindowInsets/semantics,BGSkin 背景解析 停放 composeMain,背景解析规则镜像进核心
api/Types.kt PageViewModel 持有 @Composable 槽位,DotsDecorator/IntroButtonStyle 全是 Dp/Color/Shape 停放;可序列化子集进 OnboardingPageSpec,默认值进 OnboardingDefaults
state/IntroState.kt 封装 PagerState,suspend animateScrollToPage + spring/tween 停放;翻页规则与动画规格镜像进 OnboardingCore
ui/ControlsRow.kt TextButton/Button/FilledTonalButton + 两个 when 槽位分支 停放;槽位规则镜像进 OnboardingCore.leftSlot/rightSlot
ui/Indicators.kt WormDots:animateDpAsState/animateFloatAsState 插值 停放;插值数学镜像进 WormDotsGeometry
ui/PageView.kt Crossfade/AnimatedVisibility/graphicsLayer 视差 停放;视差增益镜像进 OnboardingCore.pageFx
internal/Animations.kt lerpDp/lerpFloat(Dp 运算) 停放;浮点版进 WormDotsGeometry 私有函数
internal/BackGround.kt BGSkin(Color + Brush) 停放;ARGB 版进 BgSpec

expect/actual 声明全库为零,androidMain/iosMain 源集为空壳------整个上游库就是一层 Compose 皮,这正是"语义镜像"路线的判定依据。

1.4 页面边界
text 复制代码
页头小字        kmp-onboarding · <version> · ohosArm64(版本握手,真实 Native 调用)
Swiper 区      三页:中央 emoji 图标(🎓/📝/⭐,160vp)+ 标题(26fp bold)+ 正文(16fp)
背景层         bg.gradient 非空 → 180° 线性渐变;否则 bg.color 纯色,350ms 平滑过渡
控制行         左槽(Skip/Back/64vp 占位)| 居中 WormDots(快照几何逐点绘制)| 右槽(Next/Get Started/占位)
状态行         最近一次操作结果与错误原文
关于面板       半模态:库简介、架构分层、仓库与社区链接、时间戳事件日志

第 2 阶段:目标与依赖打通

2.1 制品验证(本适配的成败手)

先查社区 Maven(https://maven.eazytec-cloud.com)上 HarmonyOS Kotlin 发行版的可达性:org.jetbrains.kotlin:kotlin-gradle-plugin:2.2.21-1.0.0 及其 ohosArm64()/ohosX64() target 支持------qrose / SpotlightOnboarding 等已过审仓库用的正是这套钉法。本库除 Compose 外零第三方依赖 (无 kotlinx、无 serialization),不需要验证任何制品线,工具链钉版直接沿用社区标准:Kotlin 2.2.21-1.0.0、Gradle 8.14.1。上游源码基于 Kotlin 2.2.20,与 2.2.21 同代,核心重写源码零降级成本。

同时确认了一个反方向结论:eazytec 上虽然出现了 org.jetbrains.compose 的 1.9.2-OH.0.1.2-* 制品线,但已过审仓库无一采用(qrose 上游 CMP 1.12 编不过、SpotlightOnboarding 上游 CMP 1.7.3 编不过);本库上游恰好是 CMP 1.9.1、与 1.9.2 只差一个 patch,但 Compose 渲染层在 HAP 内的入口、skiko 与设备能力对齐均无已过审先例------按"路线选择必须基于当天的已验证组合"的社区纪律,本仓同样不启用该制品线,UI 语义走镜像、渲染归宿主。

2.2 根构建替换(cmp_qrose 模式)

settings.gradle.kts 把 eazytec 仓库写在最前:

kotlin 复制代码
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_kmp_onboarding"

include(":kmp-onboarding")
include(":example:nativeApp")

gradle.properties 钉住 JDK 与三个 Kotlin/Native 开关(ignoreDisabledTargets / distribution.downloadFromMaven / allowUsingOpenSourceCompiler),version=1.0.3-ohos.1;根 build.gradle.kts 全局禁用 ohos 测试任务(HarmonyOS Kotlin 的 ohos 测试目标不可用):

kotlin 复制代码
subprojects {
    tasks.matching { it.name.contains("Ohos", ignoreCase = true) && it.name.contains("Test") }
        .configureEach { enabled = false }
}

gradle/libs.versions.toml 收敛为 kotlin = "2.2.21-1.0.0" 单插件。上游 gradle.properties 里的 org.gradle.configuration-cache、android.* 等条目随重写清除。

2.3 编译语义核心

kmp-onboarding/build.gradle.kts 收敛为纯 KMP 模块:

kotlin 复制代码
plugins {
    alias(libs.plugins.kotlin.multiplatform)
}

kotlin {
    applyDefaultHierarchyTemplate()
    jvmToolchain(21)
    jvm()          // 交叉验证用
    ohosArm64()
    ohosX64()

    sourceSets {
        commonMain.dependencies {}
        commonTest.dependencies { implementation(kotlin("test")) }
    }
}

实测:

bash 复制代码
./gradlew :kmp-onboarding:compileKotlinJvm        # BUILD SUCCESSFUL
./gradlew :kmp-onboarding:compileKotlinOhosArm64 :kmp-onboarding:compileKotlinOhosX64   # BUILD SUCCESSFUL

第 3 阶段:核心提炼(语义镜像)

3.1 composeMain 隔离

8 个上游 Compose 文件 git mv 到 kmp-onboarding/src/composeMain/(包路径原样),不注册进任何 target:社区 Compose 工具链(CMP 1.9.2 / Kotlin 2.2.21)编不了上游(CMP 1.9.1 / Kotlin 2.2.20)源集;保留不删,等制品线与上游对齐后原样接回。库模块 build.gradle.kts 尾注释明确"不要加回来、不要 sync"。

3.2 commonMain 四文件逐条镜像

核心包 io.github.yskuem.onboarding.core,每个 KDoc 注明规则的上游出处:

OnboardingCore ------ 五组语义中的四组:

kotlin 复制代码
fun next(): PageMove {                       // IntroState.next(): (page+1).coerceAtMost(last) + spring(500f)
    val target = (currentPage + 1).coerceAtMost(_pages.size - 1)
    return moveTo(target, spring = true)
}

fun leftSlot(page: Int = currentPage): ControlSlot {   // ControlsRow 左槽 when
    val isLast = page == _pages.size - 1
    return when {
        !isLast && showSkipButton && skipContent -> ControlSlot.SKIP
        showBackButton && page > 0 && backContent -> ControlSlot.BACK
        else -> ControlSlot.NONE            // 上游渲染 Spacer(64.dp)
    }
}

fun background(page: Int = currentPage): BgSpec {      // IntroductionScreen BGSkin 优先级
    return when {
        spec.gradientArgb.isNotEmpty() -> BgSpec(TRANSPARENT, spec.gradientArgb)
        spec.pageColorArgb != null -> BgSpec(spec.pageColorArgb)
        global != null -> BgSpec(global)
        else -> BgSpec()                    // 透明
    }
}

fun pageFx(page: Int, fraction: Float = currentOffsetFraction): PageFx {
    val offset = (currentPage - page) + fraction       // PageView: (currentPage - index) + offsetFraction
    val d = abs(offset)
    return PageFx(
        titleAlpha = (1f - d).coerceIn(0f, 1f),        // 标题淡入
        bodyAlpha = (1f - 1.2f * d).coerceIn(0f, 1f),  // 正文更快淡出(1.2 增益)
        imageTranslateX = -40f * offset,               // 图标反向平移(视差)
        imageAlpha = (1f - 0.5f * d).coerceIn(0.2f, 1f), // 图标半速淡出,下限 0.2
        imageScale = 1f - 0.05f * d,                   // 轻微缩小
        titleTranslateY = 8f * d, bodyTranslateY = 10f * d,  // 垂直漂移
    )
}

WormDotsGeometry ------ 指示器插值数学(上游 Indicators.kt 逐行对应):

kotlin 复制代码
val dir = if (progress >= 0f) 1 else -1            // 拖动方向
val target = (current + dir).coerceIn(0, count - 1) // 行进方向的相邻页
val frac = abs(progress).coerceIn(0f, 1f)

val width = when {
    isCurrent -> lerp(m.activeWidth, m.size, frac)   // 当前点从激活宽度缩回
    isTarget  -> lerp(m.size, m.activeWidth, frac)   // 目标点从基础宽度拉伸
    else -> m.size
}
val alpha = when {
    isCurrent -> lerp(1f, m.inactiveAlpha, frac)     // 1.0 → 0.55
    isTarget  -> lerp(m.inactiveAlpha, 1f, frac)     // 0.55 → 1.0
    else -> m.inactiveAlpha
}
// color = (active ? activeColor : color).copy(alpha = alpha)
// radius: 激活态 min(RoundedCornerShape(12), height/2),CircleShape = size/2

"虫形拉伸"完全由这些数值产生,宿主不做任何动画计算。OnboardingDefaults 把 DotsDecorator/DotsContainerStyle/IntroButtonStyle/PageDecoration 的全部默认值 ARGB 化(点 8vp、激活 22×8vp、间距 8vp、0x33000000/0xFF6666FF、基线透明度 0.55、按钮圆角 16vp、槽位占位 64vp、标题 26fp、正文 16fp)。OnboardingPageSpec 是 PageViewModel + PageDecoration 的可序列化子集(@Composable 槽位无桥接等价物,由宿主自绘)。

第 4 阶段:桥协议建模

4.1 JSON 契约
text 复制代码
io.github.yskuem.onboarding.core/**(语义核心,逐条镜像上游)
        ↓ OnboardingCore / WormDotsGeometry / OnboardingDefaults
Bridge.kt(@CName × 2,9-op JSON 进出)
        ↓ UTF-8 JSON 全量快照
N-API onboardingVersion() / onboardingDispatch()
        ↓
ArkUI Index.ets(三页 Swiper 引导)

请求 op 一览(缺省 snapshot):

op 载荷 镜像的上游 API
configure pages 对象数组 + show* 四开关 + globalBackground IntroductionScreen(...) 参数
next / prev --- IntroState.next()/previous()(spring500)
skip / goto index skipToEnd()/animateScrollTo()(tween350)
settle index PagerState.settledPage 提交
progress value(可负) PagerState.currentPageOffsetFraction
fx index PageView 的 graphicsLayer 视差
snapshot --- 全量状态查询

三个设计要点:其一,每次 op 返回全量快照 (页位置 + move + 槽位 + 背景 + 指示器 + 总宽),宿主拿到快照即可整页重绘,不维护第二份状态------UI 库桥的正确形态是"库给几何与决策,宿主只画";其二,move 携带动画规格 (spring500/tween350),宿主把上游 spring(stiffness = 500f) 与 tween(350) 的语义映射到 Swiper 动画时长;其三,错误统一通道 ,configure 的对象数组解析、非法 op、越界索引全部走 ok:false。

4.2 MiniJson 的数组扩展

qrose/SpotlightOnboarding 的 MiniJson 是扁平 map;本协议 configure 需要 "pages":[{...},{...}] 一层对象数组。处置是在手写状态机风格内扩展:顶层按括号深度 0 切分键值对,新增 objArray(对象数组)与 strArray(gradient 字符串数组)两个访问器,仍然零依赖------不引入 kotlinx.serialization,避免桥模块多拉一条制品线。

第 5 阶段:原生桥接(技术难点)

5.1 三层桥接
text 复制代码
ArkTS
  │ JSON string
  ▼
C++ N-API entry(libonboarding_napi.so)
  │ const char*
  ▼
Kotlin/Native C ABI(libohosonboarding.so)
  │ OnboardingDispatch / OnboardingVersion
  ▼
UTF-8 JSON + DisposeString

@Composable 槽位与 PagerState 对象都跨不过 C 签名,只传扁平 JSON。

5.2 Kotlin/Native 导出函数
kotlin 复制代码
@CName("OnboardingDispatch")
fun onboardingDispatch(request: String): String   // 9 op 一口进,全量快照一口出

@CName("OnboardingVersion")
fun onboardingVersion(): String = "1.0.3-ohos.1"  // 版本握手
C ABI 符号 ArkTS 方法 覆盖语义
OnboardingDispatch onboardingDispatch(req) 会话配置 + 翻页 + 槽位 + 指示器几何 + 背景 + 视差
OnboardingVersion onboardingVersion() fork 版本握手(真机版本行的数据来源)
5.3 C++ N-API 与 CMake

napi_init.cpp 注册 nm_modname = "onboarding_napi",导出 onboardingDispatch/onboardingVersion;字符串读取先量长再 std::vector 拷贝(规避新版头文件把缓冲区标非 const);KN 返回串必须经 libohosonboarding_symbols()->DisposeString 归还 Kotlin 运行时,绝不 free()。libohosonboarding_api.h 按社区已审仓库同款手写(只声明 ExportedSymbols 结构与两个业务符号)。

cmake 复制代码
set(KN_SO ${KN_SO_DIR}/libohosonboarding.so)
if(NOT EXISTS ${KN_SO})
    message(FATAL_ERROR "Missing ${KN_SO}. Run :example:nativeApp:linkDebugSharedOhosArm64 / OhosX64 first.")
endif()
add_library(ohosonboarding SHARED IMPORTED)
set_target_properties(ohosonboarding PROPERTIES
  IMPORTED_LOCATION ${KN_SO}
  IMPORTED_NO_SONAME TRUE)          # 防 Windows 绝对路径进 RUNPATH
add_library(onboarding_napi SHARED napi_init.cpp)
target_link_libraries(onboarding_napi PUBLIC
  libace_napi.z.so libhilog_ndk.z.so ohosonboarding)

$ORIGIN rpath + POST_BUILD 三件套拷贝(NAPI so、KN so、libc++)保证 dlopen 互相可见。example/nativeApp/build.gradle.kts 双 ABI binaries.sharedLib { baseName = "ohosonboarding" },link 任务 finalizedBy Copy 任务把 so 与 ~/.konan 对应 triple 的 libc++_shared.so 灌进 harmonyApp/entry/libs/{arm64-v8a,x86_64}。

typescript 复制代码
export const onboardingDispatch: (request: string) => string;
export const onboardingVersion: () => string;

第 6 阶段:ArkUI 宿主

6.1 会话初始化
typescript 复制代码
aboutToAppear(): void {
  this.version = onboardingVersion();   // 版本握手:真实 Native 调用
  this.opConfigure();                   // 三页内容 + 四开关,逐字复刻上游官方 Demo
}
6.2 页面交互
  • Swiper 复刻 HorizontalPager :onChange → settle 提交落定页;move 响应驱动 swiperController.changeIndex(target),tween350 → 350ms、spring500 → 500ms 等时长近似;程序驱动期间抑制手势上报并给起止两页预取 fx;
  • WormDots 自绘 :Row 内逐点 width/height/radius/color(#AARRGGBB) 渲染,间距 8vp,总宽来自 dotsWidth 居中;点击指示器可 goto 对应页;
  • 控制行三槽 :左槽按 leftSlot 渲染 Skip(TextButton 风格)/Back/64vp 占位;右槽按 rightSlot 渲染 Get Started(#FF6666FF 底白字)或 Next(FilledTonal 近似:#3366666FF 底 #FF6666FF 字);行 padding 20vp 对齐上游 Demo 的 DotsContainerStyle(contentPadding = 20.dp);
  • 视差逐字段应用 :标题 opacity=titleAlpha + translateY、正文同理、中央图标 translateX + opacity + scale,对应上游三段 graphicsLayer;
  • 背景跟随快照:gradient 非空 → 180° 线性渐变,否则纯色,350ms 补间过渡;
  • 图标替代 :Material Icons(Icons.Outlined.School/Quiz/AutoAwesome)在 ArkTS 无矢量资源等价物,以 emoji 🎓📝⭐ 替代,160vp 图标区。
6.3 onContentOffset 降级实录

开发期用 Swiper 的 onContentOffset 把拖动偏移换算成 currentPageOffsetFraction 实时上报 progress(带 1%/16ms 双重节流),指示器逐帧插值。真机编译报 ArkTS ERROR 10505001: Property 'onContentOffset' does not exist on type 'SwiperAttribute'------该接口需 API 15+,本机 SDK 未达。处置按预留注释降级:删除该事件与换算方法,settle-only 更新(手势拖动中指示器不逐帧插值,落定后快照一次到位),按钮驱动路径不受影响。

四、完整代码对照

4.1 整体架构

text 复制代码
io.github.yskuem.onboarding.core/**(语义核心:逐条镜像上游 Compose 实现)
    │ OnboardingCore(翻页/槽位/背景/视差) + WormDotsGeometry + OnboardingDefaults
example/nativeApp Bridge.kt + MiniJson
    │ OnboardingDispatch / OnboardingVersion
libonboarding_napi.so
    │ onboardingDispatch / onboardingVersion
Index.ets
    │ JSON parse + @State 快照应用
    ▼
ArkUI 真机引导页(三页 Swiper + WormDots + 控制行)

4.2 文件清单

文件 职责
settings.gradle.kts 极简 ohos 构建、eazytec Maven 置前、ohos_kmp_onboarding、composeApp/iosApp 移出
gradle.properties JDK 21、version=1.0.3-ohos.1、三个 Kotlin/Native 开关
gradle/libs.versions.toml Kotlin 2.2.21-1.0.0 单插件钉版
build.gradle.kts(根) 版本后缀、全局禁用 Ohos*Test* 任务
kmp-onboarding/build.gradle.kts 纯 KMP:jvm() + ohosArm64() + ohosX64(),无 sharedLib
kmp-onboarding/src/composeMain/** 上游 8 个 Compose 文件原样停放(不编译)
kmp-onboarding/src/commonMain/**/core/* 语义核心四文件(全新,逐条镜像)
kmp-onboarding/src/commonTest/** 15 项 JVM 交叉验证(全新,钉住镜像规则)
example/nativeApp/build.gradle.kts 双 ABI sharedLib、cxxShared 定位 libc++、copyKnSo* 回填
example/nativeApp/.../Bridge.kt 2 个 C ABI、9-op 分发、MiniJson(含数组扩展)、错误通道
example/harmonyApp/.../cpp/napi_init.cpp N-API 导出、参数检查、DisposeString
example/harmonyApp/.../cpp/CMakeLists.txt IMPORTED 引入 KN so、$ORIGIN rpath、POST_BUILD 三件套
example/harmonyApp/.../ets/pages/Index.ets ArkUI 引导页(约 770 行,含降级实录)
README.OpenHarmony_CN.md 鸿蒙适配主文档(15 节)

4.3 关键 API 对照

层次 API 作用
核心 OnboardingCore.next()/previous() 边界 clamp + spring(stiffness=500f) 语义
核心 OnboardingCore.skipToEnd()/scrollTo() tween(350) 语义,非法索引 clamp
核心 OnboardingCore.leftSlot()/rightSlot() ControlsRow 两个 when 的镜像
核心 OnboardingCore.background() BGSkin 优先级:页渐变 > 页纯色 > 全局 > 透明
核心 OnboardingCore.pageFx() PageView 三段 graphicsLayer 增益
核心 WormDotsGeometry.dots()/totalWidth() WormDots 插值数学与指示器总宽
核心 OnboardingDefaults DotsDecorator 等默认值 ARGB 化
Native OnboardingDispatch / OnboardingVersion C ABI JSON 导出
N-API onboardingDispatch(req) / onboardingVersion() 向 ArkTS 暴露能力
ArkTS call(req) 触发操作并应用全量快照

4.4 ArkTS 与 Kotlin 的边界

typescript 复制代码
const s = JSON.parse(onboardingDispatch(JSON.stringify({ op: 'next' })));
// s.ok === true; s.move = {target: 1, anim: 'spring500'}
// s.leftSlot = 'skip'; s.rightSlot = 'next'; s.dots = [...]; s.bg = {...}
this.applyMove(s.move);   // swiperController.changeIndex(1), 500ms
this.applySnapshot(s);    // @State: 槽位/指示器/背景/状态行
kotlin 复制代码
"next" -> move = state.next()      // OnboardingCore: clamp + spring 语义
"prev" -> move = state.previous()
...
snapshot(op, move, fx)             // 全量快照 JSON 拼装

两者之间只传输扁平 JSON 字符串,不传输 Kotlin 对象、@Composable 槽位或未校验的动态结构。

五、关键决策说明

决策 1:语义镜像而不是等 Compose 工具链,也不是整库重写

上游是纯 UI 库:价值全在 Compose 渲染 + 行为语义。eazytec 上虽出现 CMP 1.9.2-OH 制品线,但无已过审的"HAP 内跑 Compose"先例,渲染层入口/skiko/设备能力对齐都是未知数;反过来把整库 ArkTS 重写则等于抛弃上游------状态机与几何会随上游演进失联。正确切面是语义:翻页规则、槽位逻辑、指示器数学、背景优先级、视差增益,这些是库的" constitution",跨平台无损;渲染天然属于宿主。核心每个函数的 KDoc 标注上游出处,上游升级时可逐条核对。

决策 2:工具链钉 2.2.21-1.0.0 + Gradle 8.14.1(社区标准线)

本库零第三方依赖,无 kotlinx 制品线牵制,直接沿用 qrose / SpotlightOnboarding 已过审的钉法;上游 Kotlin 2.2.20 与 2.2.21 同代,核心源码零降级。与 KStore 的教训互为印证:工具链版本跟着依赖走------KStore 因 coroutines 制品线被迫升 2.4.20 线留 Gradle 9.x,本库无此约束,选最保守的已验证组合。

决策 3:composeMain 停放不删除

8 个 Compose 文件原样 git mv 进 src/composeMain/(git 识别为 100% rename),不注册任何 target。删除会丢失"上游原件"的对照基准,镜像核心的每条规则都要能对回上游源码;保留的成本为零(不参与编译),等社区 CMP ohos 制品线与上游谱系对齐后原样接回。

决策 4:sharedLib 只在 example,库模块只出 KLIB

库模块声明 target 但不开 binaries.sharedLib:否则每个模块各带一份 Kotlin/Native 运行时,符号与体积失控(qrose 审核模式明文规则)。libohosonboarding.so 只由 example/nativeApp 链接产出,桥是唯一的 so 出口。

决策 5:全量快照桥 + move 携带动画规格

UI 库的桥和存储库不同:KStore 每次只回操作结果,本库每次回整页渲染所需的全部决策 (位置/槽位/指示器/背景/动画规格)。宿主因此完全无状态------拿快照画页面,丢快照丢状态,不存在两侧状态漂移;move.anim 把上游动画语义(spring500/tween350)显式下发给宿主执行,而不是让宿主猜。

决策 6:交叉验证测试全新编写(与 KStore 沿用上游测试相反)

KStore 真编译上游引擎,跑的是上游原测试文件;本库的核心是新写的镜像 ,没有现成测试可跑。处置是 15 项新测试逐条钉住镜像规则------每个断言注释对应上游哪个文件哪个行为(next() 的 clamp、左槽 Skip 优先于 Back、背景四级优先级、WormDots 插值端点、0x8C/0xC5 alpha 通道编码、视差增益与 0.2 下限),JVM 与 ohos 跑同一份 commonMain,jvmTest 全绿即"镜像没漂"。

六、测试与验证

6.1 测试环境

本次验证使用 Windows(x64)、JDK 21(Temurin jdk-21.0.12)、DevEco Studio 5.1.0 Release、鸿蒙真机(arm64)。

6.2 JVM 交叉验证

bash 复制代码
./gradlew :kmp-onboarding:jvmTest

15 项用例全部通过:

测试组 用例数 钉住什么
翻页状态机 5 next/previous 边界 clamp、spring/tween 语义标记、scrollTo 非法索引、settle 同步与分数清零
槽位规则 2 左槽 Skip 优先 → Back(currentPage > 0)→ 占位;右槽末页 Done ↔ Next 切换、开关关闭退化
背景解析 1 页渐变 > 页纯色 > 全局 > 透明四级优先级
指示器几何 4 frac 0/1 插值端点交接、负进度反向目标、alpha 通道编码(0xC5 = 0.775×255)、总宽含间距
视差 3 居中页全显、上游增益逐字段(1.2/40/0.5/0.05/8/10)、图标透明度 0.2 下限

6.3 链路与 HAP 验证

text 复制代码
:kmp-onboarding:compileKotlinJvm / compileKotlinOhosArm64 / OhosX64: BUILD SUCCESSFUL
:kmp-onboarding:jvmTest: 15 tests completed, 0 failed
:example:nativeApp:linkDebugSharedOhosArm64 / OhosX64 + copyKnSo*: BUILD SUCCESSFUL
符号层: libohosonboarding.so 动态符号含 OnboardingDispatch / OnboardingVersion / libohosonboarding_symbols
CompileArkTS: finished(onContentOffset 降级后)
PackageHap / 签名 / hdc install: 真机安装启动成功

产物(debugShared 未 strip):libohosonboarding.so arm64 约 3.7MB、x86_64 约 3.4MB(含 Kotlin/Native 运行时与语义核心),随 libc++_shared.so(1.85MB/1.73MB)双 ABI 回填 entry/libs/。

6.4 功能用例

  1. 启动后页头出现 kmp-onboarding · 1.0.3-ohos.1 · ohosArm64(来自 onboardingVersion() 真实 Native 调用);
  2. 三页 Swiper 手势滑动,落定后 settle 快照更新页码/槽位/指示器/背景;
  3. 第一页背景为 #FFFFF5F0 → #FFFFE2D6 垂直渐变,第二/三页纯色 #FFEDEBFA/#FFE4F5F1,翻页时 350ms 过渡;
  4. 非末页右槽为 Next(FilledTonal 风格),末页切换为 Get Started(#FF6666FF 底白字);
  5. 第一页左槽为 Skip,点按直达末页(tween350 语义);第二页起 Skip 让位给 Back;
  6. 指示器三 dots 按快照几何渲染,当前页激活(22×8vp 胶囊 #FF6666FF),其余 8vp 圆点基线透明度 0.55;点击指示器 goto 对应页;
  7. 按钮驱动翻页(Next/Back/Skip)带 move 动画规格,Swiper 按目标页动画滚动;
  8. 视差随翻页应用:离中心页标题/正文透明度下降、图标反向平移并轻微缩小;
  9. 末页点 Get Started 弹出完成对话框(对应上游 onDone → onFinished()),状态行留痕;
  10. 关于面板展示架构分层、仓库与社区链接、时间戳事件日志。

6.5 验证结论

语义核心(翻页状态机、槽位规则、指示器几何、背景解析、视差增益)、Native 动态库、C ABI、N-API、ArkUI 引导页和 HAP 已完成链路验证。应用在鸿蒙真机上成功安装进入前台,页面展示了真实的引导会话:版本握手、三页翻页、槽位切换、WormDots 几何与背景过渡全部由 Kotlin 侧库核心驱动(见下节效果图)。

七、运行效果

7.1 真机截图

7.2 命令速查

bash 复制代码
git clone https://atomgit.com/oh-tpc/kmp-onboarding.git
cd kmp-onboarding
export JAVA_HOME="/path/to/jdk-21"
./gradlew :kmp-onboarding:compileKotlinOhosArm64 :kmp-onboarding:compileKotlinOhosX64   # klib
./gradlew :kmp-onboarding:jvmTest                                                       # 15 项交叉验证
./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.onboarding -m entry
hdc shell hilog | grep OnboardingDemo

八、遗留问题与改进方向

8.1 踩坑复盘

  1. 只改 ArkTS 页面不算 KMP 适配,必须让 :kmp-onboarding 真正编译成 ohosArm64/ohosX64,且语义核心镜像自上游而不是页面手写;
  2. settings.gradle.kts 里 eazytec 双仓必须排在 pluginManagement 与 dependencyResolutionManagement 最前,否则配置期直接 Unresolved reference: ohosArm64;
  3. 可变属性的 smart cast :background() 里 globalBackgroundArgb != null -> BgSpec(colorArgb = globalBackgroundArgb) 编译失败------var 构造属性判空后不能智能转换,先 val global = globalBackgroundArgb 再判空使用;
  4. gradlew 与 wrapper jar 必须配套更换 :本仓上游 gradlew 被改成 java -jar 启动方式,与标准 wrapper jar(无 Main-Class manifest)不兼容,报"没有主清单属性"------只换 jar 不换脚本必然失败;
  5. IEEE 负零陷阱 :视差 imageTranslateX = -40f * offset 在 offset=0 时产生 -0.0,assertEquals(0f, -0.0f) 失败(Float.equals 对负零判不等)------浮点断言要么用 abs() 归一,要么用带容差的重载;上游 Compose 同样产生 -0.0,渲染无差异;
  6. 测试断言必须按上游语义核对而不是直觉:previous() 从第 1 页的目标是第 0 页(不是停在 1),pageFx 对非当前页的 offset 是 (currentPage - page) + fraction;
  7. onContentOffset 是 API 15+ 接口 :真机编译报 10505001 Property 'onContentOffset' does not exist on type 'SwiperAttribute'------开发期就应在代码里预留降级注释(本仓按注释一键降级 settle-only),SDK 相关 API 先查 compatibleSdkVersion 的可用矩阵;
  8. KN 返回字符串必须 DisposeString 归还运行时,N-API 层不能 free();
  9. HAP 缺当前 ABI 的 so 会报安装失败 9568347,abiFilters 必须双 ABI,每个 ABI 三件套(NAPI so / KN so / libc++)缺一不可;
  10. CMake 引入 KN so 必须 IMPORTED_NO_SONAME TRUE + $ORIGIN rpath,否则 Windows 下链接会把绝对路径写进 RUNPATH,装机即 dlopen 失败;
  11. 根构建禁用 Ohos*Test* 任务 + kotlin.native.ignoreDisabledTargets=true,否则 ohos 测试目标配置期报错;
  12. 签名由 DevEco 自动生成写回 build-profile.json5,提交前保护(git update-index --skip-worktree 或还原该文件),证书绝不进入 AtomGit;仓库内 signingConfigs 保持占位。

8.2 已知问题

  • 上游 Compose UI(IntroductionScreen/PageViewModel 等)不在鸿蒙编译范围,src/composeMain 停放待社区 CMP ohos 制品线与上游谱系对齐后接回;
  • Material Icons 以 emoji 替代(ArkTS 无 Compose 矢量图标资源);spring(stiffness = 500f) 以 500ms 等时长近似(Swiper 不支持自定义弹簧曲线);渐变背景切换不做逐色插值(仅纯色路径补间);
  • onContentOffset 降级后手势拖动中指示器不逐帧插值、视差不实时跟随,落定后快照一次到位(升级 SDK 后可恢复);
  • rawPages / globalHeader / globalFooter / 容器外观(DotsContainerStyle 的 shadowElevation 等)是纯 Compose 槽位,无桥接等价物,Demo 未覆盖;
  • 上游 onSkip/onDone 的 suspend 回调语义 = ArkTS 按钮回调,不跨协程;桥内会话为单例(Demo 只持有一个引导会话,同上游示例只持有一个 rememberIntroState);
  • ohos 测试目标禁用(工具链不支持),验证走 JVM jvmTest + 真机 Demo;
  • android / ios 目标剥离(源文件留在树里),发布、dokka 一并剥离,本 fork 不发 Maven Central。

8.3 未来优化方向

  • SDK 升级后恢复 onContentOffset 实时上报,指示器/视差逐帧插值(字段与节流逻辑已保留);
  • 跟踪社区 CMP ohos 制品线(1.9.2-OH.*),与上游 CMP 谱系对齐后评估把 composeMain 原样接回,桥层退役;
  • 评估 .har 形态发布,让 ArkTS 工程直接依赖而不是走 so 三件套;
  • 在 CI 中加入 ohos 编译、ELF 符号检查与 HAP 构建;
  • 补充 uitest 自动化截图与多设备验收矩阵。

九、总结

9.1 核心难点回顾

text 复制代码
上游 kmp-onboarding(8 文件 100% Compose,无内核可抽)
    → 判定路线:语义镜像(SpotlightOnboarding / ktoast 同款),渲染归宿主
    → 根构建替换(cmp_qrose 模式,Kotlin 2.2.21-1.0.0 / Gradle 8.14.1)
    → composeMain 停放 8 文件 + commonMain 镜像五组语义(翻页/槽位/几何/背景/视差)
    → ohosArm64 / ohosX64 KLIB + jvm() 交叉验证
    → C ABI(@CName × 2)+ 9-op JSON 全量快照(move 携带动画规格)
    → C++ N-API(DisposeString 纪律)+ CMake IMPORTED 双 ABI
    → ArkTS 三页引导 + WormDots 自绘 + onContentOffset 降级实录
    → 15 项 jvmTest 全绿 + HAP 签名真机验收

9.2 封装层次

  • 核心层:OnboardingCore(翻页状态机/槽位规则/背景解析/视差增益)+ WormDotsGeometry(指示器插值)+ OnboardingDefaults(上游默认值 ARGB 化);
  • Native 层:双 ABI 动态库并通过 2 个 C ABI 输出 JSON 快照;
  • N-API 层:完成参数检查、字符串转换和 Native 内存释放;
  • ArkTS 层:会话配置、快照应用、Swiper/指示器/控制行渲染、错误状态行与日志;
  • DevEco 层:完成 CMake、HAP、签名、安装和真机运行。

9.3 三条经验

  1. UI 库的适配面是语义不是像素:翻页规则、槽位逻辑、指示器数学、背景优先级、视差增益是引导库的"constitution",可以逐条镜像跨平台;像素渲染天然属于宿主。切面切对了,ArkTS Demo 只需 770 行就能完整复刻上游行为,且每条行为可追溯到 Kotlin 侧库代码;
  2. 全量快照是 UI 库桥的正确形态 :每次 op 返回渲染所需的全部决策(含动画规格),宿主无状态化,从根上消灭两侧状态漂移;move.anim 把上游动画语义显式下发,而不是让宿主自行猜时长;
  3. 交叉验证的形态跟着路线走 :真编译路线(KStore)跑上游原测试零改动;镜像路线(本仓)必须新写测试钉住镜像规则------断言注释逐一对应上游文件与行为,测试即镜像契约的文档。

9.4 适配成果

当前适配已完成:

  • kmp-onboarding 1.0.3 引导语义的五组规则(翻页状态机、槽位可见性、WormDots 插值几何、背景解析、视差增益)逐条镜像为纯 Kotlin 核心并完成 ohosArm64/ohosX64 双 ABI Kotlin/Native 编译(上游 Compose 源码零改动停放 composeMain);
  • 2 个 C ABI 符号和 C++ N-API 包装层(符号层验证通过);
  • DevEco Stage ArkUI 三页引导验收页面(版本握手、三页翻页、槽位切换、指示器几何、背景过渡、视差、关于面板、错误状态行);
  • 15 项 JVM 交叉验证全绿 + HAP 构建(双 ABI so 三件套)、签名、设备安装和真机验证;
  • 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。

参考文档

相关推荐
miofly1 小时前
GitHub 日榜趋势速报 | 2026-10-28
开源·github
孙琦Ray1 小时前
Nature子刊发布MyGPT开源平台:本地检索防泄密
开源·大模型幻觉·数据隐私·检索增强生成·mygpt
SelectDB技术团队2 小时前
ClickHouse SQL 好学吗?从 MySQL 迁移 Doris 的完整兼容验证
人工智能·sql·mysql·clickhouse·开源·apache doris·增量计算
网络豆2 小时前
开源鸿蒙平台 CMP 三方库「dotsindicator」适配全流程
华为·开源·harmonyos
resh_people2 小时前
开源鸿蒙平台 CMP 三方库「kmp-wheel-picker」适配全流程
华为·开源·harmonyos
HelloGitHub2 小时前
《HelloGitHub》第 127 期
开源·github
2501_944521592 小时前
开源鸿蒙平台 KMP 三方库「BottomSheetNavigator3KMP」适配全流程
华为·开源·harmonyos
Agent世界2 小时前
百度OCR 2开源:KV缓存从47GB压到1.77GB
百度·缓存·开源·大模型·ocr
2501_944521592 小时前
开源鸿蒙平台 CMP 三方库「scroll-effects」适配全流程
华为·开源·harmonyos