本文记录 compose-shadow-alternative 接入 OpenHarmony 的完整过程,覆盖阴影库盘点、鸿蒙工具链选线、语义核心编译、ohosArm64 / ohosX64 目标、Kotlin/Native 动态库、C ABI/N-API 桥接、ArkUI 真机页面、HAP 构建和真机验收。

compose-shadow-alternative 是 kubode 开发的 Compose Multiplatform 阴影库,含两个制品:compose-boxshadow(Modifier.boxShadow:任意 Shape 的盒阴影,Android 走 BlurMaskFilter、其余平台走 Skia MaskFilter 模糊画笔)与 compose-dropshadow(DropShadow 组合件:离屏捕获内容位图,Modifier.blur + ColorFilter.tint 生成按内容透明度投影的阴影层)。本次适配与 KStore 的"真编译整库"路线有本质区别:社区 Compose-for-OHOS 制品线(CMP 1.9.2 / HarmonyOS Kotlin 2.2.21)与上游栈(Kotlin 1.9.22 / CMP 1.5.12)版本对不齐,而这两个库的每一行实现都 import androidx.compose.*——真编译整库在当前制品线下不可行。因此走社区已审核的"停车隔离"路线(cmp_qrose / Korner / day-and-night-switch / Cloudy 同一条路):上游 Compose 面 git mv 停车到 src/composeMain 字节不动,阴影语义(形状几何、模糊换算、两层画布布局、边界处理)复述为纯 Kotlin 编进 ohos 目标,经 libohosshadow.so(@CName ShadowDispatch / ShadowVersion)+ CMake N-API libshadow_napi.so 桥接给 ArkTS,CPU 光栅化(扫描线填充 + 3-pass 盒式模糊 + 零依赖 PNG)复刻渲染语义,ArkUI 三 Tab 页面作为真机验收宿主。

项目地址: AtomGit/oh-tpc/compose-shadow-alternative.git

上游仓库: kubode/compose-shadow-alternative

开发工具: 华为云码道

环境安装: 环境安装指南

一、背景

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

Compose 原生的阴影表达有限:Modifier.shadow 是 elevation 驱动的 Material 阴影,不能任意控制颜色、偏移与模糊半径,也不能按内容透明度投影。上游库补上这两块:boxShadow 让任意 Shape 得到 CSS 盒阴影式的软阴影,DropShadow 让图标、文字、镂空内容按自身 alpha 投影,动画态经 drawInvalidationTrigger 实时跟随。这些能力在 KMP 项目里由 Android / iOS / 桌面 / Web 共享,OpenHarmony 应用同样需要,但上游构建(Kotlin 1.9.22 / CMP 1.5.12 / Gradle 8.10.2)不会生成 OpenHarmony 变体。

如果只把示例页面改成 ArkTS,页面可以显示滑杆,却无法证明阴影语义引擎在 OpenHarmony ARM64 设备上完成编译、链接和执行。适配需要同时验证语义核心、光栅化管线、Native 动态库、C ABI、N-API、ArkUI 页面和 HAP 交付链路。

障碍具体问题
目标缺失必须增加 ohosArm64() / ohosX64() 才能产出 OpenHarmony 产物。
Compose 无 ohos 线两个库的每个源文件都 import androidx.compose.*(Modifier、drawWithCache、Paint、ImageBitmap、BlurredEdgeTreatment),社区 CMP 1.9.2 与上游 CMP 1.5.12 互不兼容。
无引擎可提取与 qrose(条码编码器)、Cloudy(着色器编译器)不同,本库 100% 是 Compose 胶水,没有现成的纯 Kotlin 引擎目录可以"换个源集就编"。
工具链不一致上游 K1.9 / Gradle 8.10.2 / AGP 8.2.2 / compose-compiler 与鸿蒙 Kotlin 2.2.21 线无法共存于一个 Gradle 构建。
语言边界不同ArkTS 不能直接持有 Kotlin 对象;阴影位图要跨 FFI,只能走 JSON + Base64 PNG。
交付链路复杂.so、CMake、N-API、HAP、签名和 hdc 都要单独验收。

因此,本项目把边界放在:语义复述层的 ohos 编译(ShadowShape / BoxShadowSpec / DropShadowSpec + CPU 光栅化器)、根构建替换(cmp_qrose 模式)和 C ABI/N-API 桥接层。阴影语义的正确性由上游实现的第一性原理与 26 项 JVM 钉死测试共同保证,ArkTS 只负责调用 Native、解析 JSON 和展示结果。

1.2 库提供的能力

Demo 提供一个真实的阴影渲染会话,对应桥接的全部入口:

  • 盒阴影(boxshadow):矩形 / 圆角矩形 / 圆形三种形状(镜像上游 RectangleShape / RoundedCornerShape / CircleShape),任意 #AARRGGBB 颜色、X/Y 偏移、模糊半径实时可调;返回模糊形状阴影层 PNG 与 margin / contentX / contentY,宿主内容按层序叠放在阴影之上——与上游「阴影画在内容之后」一致;
  • 内容投影(dropshadow):按内容 alpha 投影,镜像上游两层结构;内置圆角边框 / 实心圆角 / 五角星三种内容,支持 0-360° 旋转实时跟随(对应上游 drawInvalidationTrigger = { degrees } 旋转示例);content=mask 支持宿主传入 base64 RGBA 任意内容;
  • 边界处理(edge):Unbounded 允许阴影溢出内容边界(上游默认),Rectangle 裁剪到内容矩形——镜像 BlurredEdgeTreatment 语义;
  • 版本握手:shadowVersion() 返回 fork 版本号,来自真实 Native 调用而非静态文字。

响应包含:

字段作用
ok操作是否成功,失败时 error 带原文。
op / kind回显本次操作与产物类型(boxshadow / dropshadow)。
width / height / margin输出画布尺寸与内容外边距(radius + max|offset|)。
contentX / contentY宿主内容应叠加的位置(层序约定的落点)。
sigma / rasterUs / blurUs / pngUs / bytes模糊换算结果与三段耗时实测。
pngBase64boxshadow 为阴影层 PNG;dropshadow 为阴影+内容合成图。
shadowPngBase64仅 dropshadow:单独的阴影层(对应上游 DropShadowEffect)。

引擎侧完整能力(形状几何、参数校验、画布布局、边缘裁剪、扫描线光栅化、3-pass 高斯近似、直 alpha 合成、零依赖 PNG)全部在 Kotlin commonMain 里编译进入 .so,JVM 侧保留 26 项交叉验证用例,证明 ohos .so 里执行的渲染逻辑没有漂移。

1.3 实现适配

维度要求
代码复用上游 Compose 面 4 个源文件 git mv 停车到 src/composeMain(字节不动、rename 相似度 100%);语义核心新写 3 个纯 Kotlin 文件并由测试钉死。
平台目标根构建直接声明 ohosArm64 + ohosX64,保留 jvm() 作交叉验证,库模块出 KLIB,example/nativeApp 出双 ABI 动态库。
构建隔离根构建替换为极简 ohos 构建(cmp_qrose 模式);build-logic、sample/ 留盘不进构建。
桥接稳定使用 2 个 C ABI 和 UTF-8 JSON,不传递 Kotlin 对象地址;异常永远不跨 FFI。
依赖纪律引擎零第三方依赖;桥 JSON 与 PNG 手写,不引 kotlinx.serialization 与压缩库。
UI 完整三 Tab 覆盖全部桥接入口:盒阴影调参、内容投影调参、旋转跟随、边界开关、双色板和版本握手。
仓库规范项目说明、文章、效果图和源码链接统一使用 AtomGit。

本项目的 ArkUI 页面不是 Compose Multiplatform UI 实现。停车路线下鸿蒙侧没有 Compose 运行时,ArkTS Demo 是阴影语义的验收宿主;等社区 Compose-ohos 制品线对齐后按 README 第 20 节路线接回。

二、实现路线图

第 1 阶段:项目初始化     ── 盘点模块与依赖边界、确认工具链线、确定交付物
第 2 阶段:目标与依赖打通 ── 根构建替换、eazytec Maven 置前、Kotlin 2.2.21-1.0.0 钉版、Gradle 8.14.1
第 3 阶段:桥协议建模     ── 停车隔离、语义核心复述、boxshadow/dropshadow JSON 契约、错误通道
第 4 阶段:原生桥接       ── @CName C ABI、C++ N-API、参数校验和内存释放
第 5 阶段:ArkUI 宿主     ── 三 Tab、滑杆实时调参、双图层展示和版本握手
第 6 阶段:示例与验证     ── HAP、签名、设备安装、真机运行和效果图

三、逐步实现过程

第 1 阶段:项目初始化

1.1 工程边界
compose-boxshadow/                     上游盒阴影库(进 ohos 构建,jvm + ohosArm64 + ohosX64)
compose-boxshadow/src/commonMain/      新增:ShadowShape + BoxShadowSpec(形状几何与参数模型)
compose-boxshadow/src/composeMain/     停车:上游 BoxShadow.kt + blur expect/actual(字节不动)
compose-dropshadow/                    上游投影库(进 ohos 构建,结构同上)
compose-dropshadow/src/commonMain/     新增:DropShadowSpec + ShadowEdgeTreatment
compose-dropshadow/src/composeMain/    停车:上游 DropShadow.kt
example/nativeApp/                     Kotlin/Native 动态库(ShadowOps + Rasterizer + PngEncoder + Bridge)
example/harmonyApp/                    DevEco Stage、CMake N-API 和 ArkTS 页面
README.OpenHarmony.md / _CN.md         鸿蒙适配说明(中英双份)
README.upstream.md                     上游原文保存
build-logic/ sample/ .github/          上游构建逻辑与示例,留盘不进 settings

构建拓扑走 cmp_qrose 模式(根构建即 ohos 构建):settings.gradle.kts 只 include :compose-boxshadow、:compose-dropshadow 与 :example:nativeApp,rootProject.name = "ohos_compose_shadow_alternative"。上游的 AGP / Compose / vanniktech 发布插件、build-logic 组合构建、foojay 工具链解析全部随根构建替换剥离——本 fork 不发 Maven Central。

1.2 工具链矩阵
项目配置
Kotlin MultiplatformHarmonyOS Kotlin 2.2.21-1.0.0(eazytec Maven,社区批准的老线)
JDK21(org.gradle.java.home 指向 Temurin 21)
OpenHarmony 目标ohosArm64 + ohosX64(jvm() 保留作交叉验证)
Gradle8.14.1(上游 8.10.2 升级;2.2.21 线不支持 Gradle 9.x)
konanohos sysroot 经 kotlin.native.distribution.downloadFromMaven=true 从社区 Maven 拉取
DevEco product5.1.0(18) 兼容
DevEco Studio5.1.0 Release
ABIarm64-v8a + x86_64
Bundleorg.terminator.ohos.shadow
export JAVA_HOME="/path/to/jdk-21"
java -version
1.3 源码依赖度盘点
文件依赖形态处置
BoxShadow.ktandroidx.compose.ui 全家(draw / graphics / unit)停车 composeMain,commonMain 腾位装语义核心
blur.android.ktandroid.graphics.BlurMaskFilter + compose Paint停车 composeMain(expect 的 actual,属 Compose 面)
blur.nonAndroid.ktcompose Paint + org.jetbrains.skia.MaskFilter停车 composeMain(上游自定义 nonAndroidMain 源集不再接线)
DropShadow.ktfoundation / runtime / ui + DrawScope.draw(Compose 1.6 API)停车 composeMain(Android 单平台模块改 KMP 后 src/main 不再是源集)

expect 声明全库只有 1 处:internal expect fun Paint.blur(radius: Float)。上游给了 2 个 actual——Android 是 BlurMaskFilter,其余平台是 Skia MaskFilter.makeBlur。两个 actual 都长在 Compose Paint 上,所以没有"补第 3 个 ohos actual"的选项,expect/actual 随整个 Compose 面一起停车;ohos 侧的模糊等价物是软件光栅化器里的 gaussianBlur(sigma = radius × 0.5),语义换算被写进 BoxShadowSpec 并由测试钉死。

1.4 页面边界
标题区          COMPOSE-SHADOW-ALTERNATIVE · OPENHARMONY / 副标题行(Compose 阴影库 · Kotlin/Native ohosArm64/ohosX64)
Tab 切换        BoxShadow 盒阴影 / DropShadow 投影 / 关于
盒阴影 Tab      形状切换(矩形/圆角/圆形)、圆角/尺寸/偏移/模糊滑杆、阴影色板、
                阴影层 PNG + 宿主红色内容叠放(Stack 层序)、画布与耗时元信息行
投影 Tab        内容切换(圆角边框/实心圆角/五角星)、旋转 0-360° 滑杆、偏移/模糊滑杆、
                裁剪开关(Unbounded/Rectangle)、阴影色与内容色双板、合成图与仅阴影层并排
关于区          版本握手(真实 Native 调用)、桥协议摘要、三件套 so 说明、仓库与社区链接

页面启动时先调 shadowVersion() 做握手,随后并行渲染两个阴影 Tab 的首帧。

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

2.1 制品线验证(选线的依据)

本库引擎零第三方依赖,选线只看两件事:eazytec Maven 上 HarmonyOS Kotlin 2.2.21-1.0.0 的 Gradle 插件与 konan ohos sysroot(sysroot-ohos-aarch64 / sysroot-ohos-x86_64)当日可达。社区两条已批准线——老线(Kotlin 2.2.21-1.0.0 + Gradle 8.14.1,qrose / haze / Cloudy / day-and-night-switch 在用)与新线(Kotlin 2.4.20-0.1.0-07 + Gradle 9.6.1,KStore 在用)——选择标准是"依赖矩阵要什么",不是"哪条新"。本 fork 不被 kotlinx ohos 制品牵引,按规则取老线,零依赖纯引擎不需要升线。

2.2 工具链钉版(为什么是 2.2.21-1.0.0 + Gradle 8.14.1)

上游 Gradle 8.10.2 在 2.2.21 线下必须升到 8.14.1(社区批准的搭配,且不支持 9.x——两条工具链线的经验不能互相套用)。JDK 从上游的 toolchain 19 + foojay 自动解析改为 org.gradle.java.home 显式钉 Temurin 21,Windows 无系统 java 时构建仍可复现。

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_compose_shadow_alternative"

include(":compose-boxshadow")
include(":compose-dropshadow")
include(":example:nativeApp")

gradle.properties 钉 version=0.3.0-ohos.1(上游版本 + -ohos.N 后缀)、kotlin.native.ignoreDisabledTargets=true、kotlin.native.distribution.downloadFromMaven=true。libs.versions.toml 只剩 kotlin-multiplatform 一个插件条目。

2.3 编译语义核心

两个库模块收敛为纯 KMP 模块:

plugins {
    alias(libs.plugins.kotlin.multiplatform)
}

kotlin {
    applyDefaultHierarchyTemplate()
    jvmToolchain(21)

    jvm()          // 交叉验证用
    ohosArm64()
    ohosX64()

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

compose-dropshadow 同构。实测:

./gradlew :compose-boxshadow:compileKotlinOhosArm64 :compose-boxshadow:compileKotlinOhosX64   # BUILD SUCCESSFUL
./gradlew :compose-dropshadow:compileKotlinOhosArm64 :compose-dropshadow:compileKotlinOhosX64 # BUILD SUCCESSFUL

commonMain 装的是三个新写的语义文件:ShadowShape(Rectangle / RoundedRect / Circle → 轮廓多边形,镜像 Compose Shape 语义——CircleShape 实为内切椭圆、圆角钳半短边、每角 12 段弧)、BoxShadowSpec(参数模型 + blurSigma = radius × 0.5 + canvasOutset = radius + max|offset|)、DropShadowSpec + ShadowEdgeTreatment(上游两层 Box 的画布布局公式:Unbounded 留 margin、Rectangle 不留)。

2.4 测试依赖的处置

commonTest 只依赖 kotlin("test")(多平台制品齐全),没有 turbine 式的"无 ohos 制品"问题。但根构建仍全局禁用 ohos 测试任务——鸿蒙线跑 JVM 测试要拉设备 runner,收益为零:

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

测试统一在 JVM 跑(jvmTest),ohos 侧只承担编译与链接。

第 3 阶段:桥协议建模

3.1 JSON 契约
io.github.kubode.compose.boxshadow / dropshadow/**(语义核心,纯 Kotlin)
        ↓ ShadowShape.outline / BoxShadowSpec / DropShadowSpec
ShadowOps.dispatch(nativeApp commonMain,JVM 与 ohos 同一份)
        ↓ 扁平 JSON 进出 + PNG Base64
N-API shadowVersion() / shadowDispatch()
        ↓
ArkUI Index.ets(三 Tab)

请求与响应:

请求 : {"op":"boxshadow","width":140,"height":140,"shape":"rounded","corner":16,
        "offsetX":6,"offsetY":8,"radius":14,"color":"#66000000","ss":3}
       {"op":"dropshadow","width":120,"height":120,"content":"star","rotate":24,
        "offsetX":6,"offsetY":6,"radius":10,"color":"#803DDC84",
        "contentColor":"#FF3DDC84","edge":"unbounded"}
响应 : {"ok":true,"kind":"boxshadow","width":172,"height":172,"margin":16,
        "contentX":16,"contentY":16,"sigma":7,"rasterUs":...,"blurUs":...,
        "pngUs":...,"bytes":...,"pngBase64":"<PNG>"}
       {"ok":false,"error":"unknown shape 'hexagon' (expected rect|rounded|circle)"}

两个设计要点:其一,boxshadow 只返回阴影层,内容由宿主叠加在 (contentX, contentY)——层序与上游"阴影画在内容之后"严格一致,Demo 里是 ArkUI 红色圆角 Box 叠在 PNG 上;dropshadow 返回合成图 + 单独阴影层双图,分别对应上游的最终效果与 DropShadowEffect 组件。其二,dropshadow 的 content=mask 接受宿主 base64 RGBA,把"按任意内容透明度投影"做成通用通道。

3.2 载荷语义为什么是"复述"而不是"提取"

桥内阴影这样画:

// op=boxshadow:模糊形状层,偏移只作用于阴影
val spec = BoxShadowSpec(argb, offsetX, offsetY, radius, shape)
val margin = ceil(spec.canvasOutset()).toInt()
val contour = shape.outline(width, height).map { ShadowPoint(it.x + margin + offsetX, it.y + margin + offsetY) }
val mask = Rasterizer.polygonMask(w, h, listOf(contour), ss)
val shadowAlpha = Rasterizer.gaussianBlur(mask, w, h, spec.blurSigma)
val png = PngEncoder.encodeArgb(Rasterizer.tint(shadowAlpha, argb), w, h)

// op=dropshadow:阴影 = tint(blur(内容 alpha)),垫底合成
val shadowAlpha = Rasterizer.gaussianBlur(contentCanvas, w, h, spec.blurSigma)
val composite = Rasterizer.compositeOver(Rasterizer.tint(shadowAlpha, argb), contentPixels)

复述的每一条都来自上游源码的第一性原理:boxShadow 的 drawWithCache 按 shape.createOutline 建 Path、withTransform { clipPath(Difference); translate(offset) } 后用模糊画笔画 Path——所以偏移只作用于阴影层、模糊单位是 radius;DropShadow 的两层结构里阴影层对离屏 bitmap 做 blur + tint 并按 offset 偏移——所以阴影形状 = 内容 alpha、着色 = tint、层序 = 阴影在内容后。模糊换算钉 sigma = radius × 0.5:上游本就双轨(Android 把 radius 直喂 BlurMaskFilter,其余平台直喂 Skia makeBlur 的 sigma 位),Skia 官方换算是 radius/√3 ≈ 0.577,fork 钉 0.5 并写进测试——fork 内部自洽且可测优先于跨平台精确。

3.3 错误通道

每个导出整体 try/catch,Kotlin 异常永远不跨 FFI:

@CName("ShadowDispatch")
fun shadowDispatch(request: String): String = try {
    ShadowOps.dispatch(request)
} catch (t: Throwable) {
    """{"ok":false,"error":"${MiniJson.string(t.message ?: t.toString())}"}"""
}

非法 JSON、未知 op、未知 shape / content、负 radius、mask 尺寸越界、base64 损坏,全部走同一个 ok:false 通道,ArkTS 侧统一渲染。

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

4.1 三层桥接
ArkTS
  │ JSON string
  ▼
C++ N-API entry(libshadow_napi.so)
  │ const char*
  ▼
Kotlin/Native C ABI(libohosshadow.so)
  │ ShadowDispatch / ShadowVersion
  ▼
UTF-8 JSON + DisposeString

直接导出 Kotlin 对象会暴露 ABI 和生命周期问题;只导出整数又会让页面复制渲染语义,所以选择 C ABI + JSON。

4.2 Kotlin/Native 导出函数
@CName("ShadowDispatch")
fun shadowDispatch(request: String): String   // boxshadow / dropshadow 一口进

@CName("ShadowVersion")
fun shadowVersion(): String                   // 版本握手
C ABI 符号ArkTS 方法覆盖内核
ShadowDispatchshadowDispatch(req)形状几何 + 光栅化 + 模糊 + 合成 + PNG
ShadowVersionshadowVersion()fork 版本握手(真机版本行的数据来源)
4.3 C++ N-API 方法分发
napi_property_descriptor desc[] = {
    {"shadowVersion", nullptr, NapiShadowVersion, nullptr, nullptr, nullptr,
     static_cast<napi_property_attributes>(napi_default | napi_enumerable), nullptr},
    {"shadowDispatch", nullptr, NapiShadowDispatch, nullptr, nullptr, nullptr,
     static_cast<napi_property_attributes>(napi_default | napi_enumerable), nullptr},
};

每个回调统一三步:napi_typeof 校验必须是 string、OH_LOG_INFO 打字节数、调 KN 函数并包装返回。KN 返回的字符串必须经 libohosshadow_symbols()->DisposeString 归还 Kotlin 运行时,绝不 free()。libohosshadow_api.h 按社区已审仓库同款手写(只声明 ExportedSymbols 结构与两个业务符号),不拖整份生成头。

4.4 CMake 和类型声明
add_library(ohosshadow SHARED IMPORTED)
set_target_properties(ohosshadow PROPERTIES
  IMPORTED_LOCATION ${KN_SO}
  IMPORTED_NO_SONAME TRUE)

add_library(shadow_napi SHARED napi_init.cpp)
target_link_libraries(shadow_napi PUBLIC
  libace_napi.z.so
  libhilog_ndk.z.so
  ohosshadow)

OHOS_ARCH 为空或 libohosshadow.so 缺失直接 FATAL_ERROR(提示先跑 link 任务);$ORIGIN rpath 加 POST_BUILD 拷贝保证 NAPI so、KN so、libc++ 三件套同目录,dlopen 时互相可见。

export const shadowDispatch: (request: string) => string;
export const shadowVersion: () => string;

第 5 阶段:ArkUI 宿主

5.1 启动握手和触发操作
aboutToAppear(): void {
  this.versionInfo = shadowVersion();   // 版本握手:来自真实 Native 调用
  this.renderBox();
  this.renderDrop();
}

private renderBox(): void {
  const request: string = `{"op":"boxshadow","width":${this.bsSize},"height":${this.bsSize}` +
    `,"shape":"${this.bsShape}","corner":${this.bsCorner}` +
    `,"offsetX":${this.bsOffX},"offsetY":${this.bsOffY},"radius":${this.bsRadius}` +
    `,"color":"${this.shadowPalette[this.bsColorIdx]}","ss":3}`;
  const raw: string = shadowDispatch(request);
  const parsed: RenderOk = JSON.parse(raw) as RenderOk;
  if (!parsed.ok || parsed.pngBase64 === undefined) {
    this.bsMeta = `失败: ${parsed.error ?? 'empty png'}`;
    return;
  }
  this.bsMeta = `${parsed.width}×${parsed.height} margin=${parsed.margin} sigma=${parsed.sigma}` +
    ` · 光栅 ${this.usToMs(parsed.rasterUs)}ms 模糊 ${this.usToMs(parsed.blurUs)}ms` +
    ` PNG ${this.usToMs(parsed.pngUs)}ms (${parsed.bytes}B)`;
  this.decodePng(parsed.pngBase64, (pm: image.PixelMap) => { this.bsMap = pm; });
}

滑杆 onChange 里同步更新状态后立即 renderBox() / renderDrop()——每次拖动都是一次真实的 Native dispatch + PNG 解码,实时性本身就是对桥的压测。

5.2 页面交互

盒阴影 Tab 自上而下:形状切换(矩形 / 圆角 / 圆形)、条件显示的圆角滑杆、尺寸 / X 偏移 / Y 偏移 / 模糊半径四滑杆、五色阴影色板、预览区(阴影层 PNG 垫底,红色内容按 contentX/contentY 叠放,Stack 层序复刻上游)、画布与三段耗时元信息行。投影 Tab:内容切换(圆角边框 / 实心圆角 / 五角星)、旋转 0-360° 滑杆、偏移 / 模糊滑杆、裁剪开关(对应 BlurredEdgeTreatment)、阴影色与内容色双板、合成图与仅阴影层并排展示。关于 Tab:版本握手 JSON 原文、桥协议摘要、三件套 so 说明与仓库 / 社区链接。

5.3 对比度和错误边界

阴影色板选中态加 3px 深色描边;ok:false 时错误原文进元信息行并被 hilog 记录;Native 调用本身抛错(理论不该发生)同样被 try/catch 收进状态行,页面不崩。

第 6 阶段:示例与验证

6.1 工程结构
example/
├── nativeApp/
│   ├── build.gradle.kts                      双 ABI sharedLib(baseName=ohosshadow)+ cxxShared + copyKnSo*
│   └── src/
│       ├── commonMain/kotlin/org/terminator/ohos/shadow/
│       │   ├── ShadowOps.kt                  dispatch 核心(JVM 与 ohos 同一份)
│       │   ├── Rasterizer.kt / PngEncoder.kt / MiniJson.kt / ColorJson.kt
│       ├── ohosMain/.../Bridge.kt            @CName × 2(异常不跨 FFI)
│       ├── jvmMain/.../DocImages.kt          generateDocImages 任务:JVM 出预览图
│       └── commonTest/.../ShadowOpsTest.kt   桥全链路 10 用例
└── harmonyApp/
    ├── build-profile.json5                   signingConfigs 留空(签名由 DevEco 自动生成回填)
    ├── AppScope/app.json5                    bundleName org.terminator.ohos.shadow、versionName 0.3.0-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/libohosshadow_api.h
        ├── src/main/cpp/types/libshadow_napi/{index.d.ts, oh-package.json5}
        └── src/main/ets/pages/Index.ets      ArkUI 三 Tab 验收页面
6.2 构建、签名和安装
./gradlew :compose-boxshadow:jvmTest :compose-dropshadow:jvmTest :example:nativeApp:jvmTest  # 26 项交叉验证
./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 中勾选自动生成,证书写入本机 ~/.ohos/config,不进入仓库(仓库内 build-profile.json5 的 signingConfigs 保持空数组,勾选自动签名后由 IDE 回填带 material 的完整配置)。

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.shadow -m entry

四、完整代码对照

4.1 整体架构

io.github.kubode.compose.boxshadow/**(语义核心:ShadowShape + BoxShadowSpec)
io.github.kubode.compose.dropshadow/**(语义核心:DropShadowSpec + EdgeTreatment)
    │ 轮廓多边形 / 模糊换算 / 画布布局公式
ShadowOps + Rasterizer + PngEncoder(nativeApp commonMain,JVM/ohos 同源)
    │ ShadowDispatch / ShadowVersion
libshadow_napi.so
    │ shadowDispatch / shadowVersion
Index.ets
    │ JSON parse + Base64 → PixelMap + @State 更新
    ▼
ArkUI 真机页面(三 Tab)

4.2 文件清单

文件职责
settings.gradle.kts极简 ohos 构建、eazytec Maven 置前、ohos_compose_shadow_alternative
gradle/libs.versions.tomlKotlin 2.2.21-1.0.0 单插件条目(AGP/Compose/vanniktech 剥离)
build.gradle.kts(根)版本 0.3.0-ohos.1、全局禁用 Ohos*Test* 任务
compose-boxshadow/build.gradle.kts 等纯 KMP:jvm + ohosArm64 + ohosX64
compose-boxshadow/src/commonMain/.../ShadowShape.kt形状 → 轮廓多边形(镜像 Compose Shape 语义)
compose-boxshadow/src/commonMain/.../BoxShadowSpec.kt参数模型 + sigma = radius×0.5 + canvasOutset
compose-dropshadow/src/commonMain/.../DropShadowSpec.kt两层画布布局 + ShadowEdgeTreatment
example/nativeApp/build.gradle.kts双 ABI sharedLib、cxxShared 定位 libc++、copyKnSo* 回填、generateDocImages
nativeApp/.../ShadowOps.kt2 个 op 的 dispatch 核心(JVM 与 ohos 同一份)
nativeApp/.../Bridge.kt2 个 C ABI、错误通道
harmonyApp/.../cpp/napi_init.cppN-API 导出、参数检查、DisposeString
harmonyApp/.../cpp/CMakeLists.txtIMPORTED 引入 KN so、$ORIGIN rpath、POST_BUILD 三件套
harmonyApp/.../ets/pages/Index.etsArkUI 三 Tab 验收页面
README.OpenHarmony_CN.md鸿蒙适配主文档(24 节)

4.3 关键 API 对照

层次API作用
引擎ShadowShape.outline(w, h)Rectangle / RoundedRect / Circle → 轮廓多边形(Circle 是内切椭圆)
引擎BoxShadowSpec.blurSigma / canvasOutsetradius×0.5 换算与 margin 公式
引擎DropShadowSpec.outputSize / shadowOrigin / contentOriginUnbounded / Rectangle 两种画布布局
光栅化Rasterizer.polygonMask偶奇扫描线 + 超采样抗锯齿(ring 双回路天然成洞)
光栅化Rasterizer.gaussianBlur3-pass 盒式模糊高斯近似(boxesForGauss 盒宽转半径)
光栅化Rasterizer.compositeOver直 alpha「内容 over 阴影」合成
NativeShadowDispatch / ShadowVersionC ABI JSON 导出
N-APIshadowDispatch(req) / shadowVersion()向 ArkTS 暴露能力
ArkTSrenderBox() / renderDrop()触发渲染并刷新 UI

4.4 ArkTS 与 Kotlin 的边界

const raw = shadowDispatch(JSON.stringify({
  op: 'boxshadow', width: 140, height: 140, shape: 'rounded', corner: 16,
  offsetX: 6, offsetY: 8, radius: 14, color: '#66000000', ss: 3
}));
const res = JSON.parse(raw);
// res.ok === true;res.pngBase64 解码为阴影层,res.contentX/contentY 处叠宿主内容
val spec = BoxShadowSpec(argb, offsetX, offsetY, radius, shape)
val contour = shape.outline(width, height)
    .map { ShadowPoint(it.x + margin + offsetX, it.y + margin + offsetY) }
val png = PngEncoder.encodeArgb(
    Rasterizer.tint(Rasterizer.gaussianBlur(mask, w, h, spec.blurSigma), argb), w, h)

两者之间只传输扁平 JSON 字符串与 Base64 PNG,不传输 Kotlin 对象或未校验的动态结构。

五、关键决策说明

决策 1:停车隔离而不是真编译整库

KStore 能真编译,是因为社区 Maven 补齐了 kotlinx ohos 制品线,障碍消失。本库的障碍在 Compose 本身:社区 CMP 1.9.2 与上游 CMP 1.5.12 互不兼容,而库的每行实现都长在 androidx.compose.* 上——升上游 CMP 再适配等于先做一次大版本迁移。停车(git mv 到 composeMain,rename 相似度 100%)让上游文件字节不动、历史可追溯,接回时是最小反向操作。

决策 2:语义放库模块,光栅化放 demo 桥

形状几何与参数模型是库能力(对齐 Korner 的 cornerPath 在 shared 模块),随库出 KLIB 并被 16 个测试钉死;软件光栅化器是 demo 桥的一部分(对齐 Korner 的 Rasterizer 在 nativeApp),等 Compose 接回后自然废弃,不污染库的公共 API。

决策 3:模糊换算钉 sigma = radius × 0.5

上游本就双轨:Android 把 radius 直喂 BlurMaskFilter(mask 半径语义),其余平台直喂 Skia MaskFilter.makeBlur(sigma 语义)——同一参数在两个上游平台产生不同强度的模糊。Skia 官方的 mask→sigma 换算是 radius/√3 ≈ 0.577。fork 钉 0.5 并写进测试:fork 内部自洽且可测,优先于对任一上游平台的精确拟合。

决策 4:nativeApp 加 jvm() 目标,dispatch 双端同源

ShadowOps.dispatch 放在 nativeApp 的 commonMain,ohos 的 @CName 壳只有 8 行,JVM 侧同一入口被 10 个桥测试与 generateDocImages 任务调用——交叉验证不是"模拟桥",而是"桥本体在 JVM 上执行"。这比"JVM 写一套参考实现"强一个量级:不存在两份逻辑漂移的可能。

决策 5:PNG 用 zlib stored 未压缩块

零依赖(不引压缩库进 KN so,工具链矩阵不变)换体积:148px 级画布约 60-140 KB,预览场景可接受;真机实测 172×172 画布 69,842 B。压缩位图反而会模糊掉阴影边缘的 alpha 渐变精度,对调参 Demo 是负收益。

六、测试与验证

6.1 测试环境

本次验证使用 Windows 10(x64)、JDK 21(Temurin)、Gradle 8.14.1、HarmonyOS Kotlin 2.2.21-1.0.0(eazytec Maven)、DevEco Studio 5.1.0 Release、OpenHarmony 命令行工具(hvigor / hdc)与鸿蒙真机(arm64)。

6.2 JVM 交叉验证

./gradlew :compose-boxshadow:jvmTest :compose-dropshadow:jvmTest :example:nativeApp:jvmTest

26 项用例全部通过:

测试类用例数钉住什么
ShadowShapeTest7轮廓几何:矩形四角、圆角每角 12 段弧共 52 点、界内钳位、0 圆角退化矩形、Circle 内切椭圆等距性、负圆角拒绝
BoxShadowSpecTest5sigma = radius×0.5、margin 公式、负半径 / 非有限偏移拒绝、0 半径合法
DropShadowSpecTest4Unbounded 画布留白与图层原点、Rectangle 不留白、参数校验
ShadowOpsTest10桥全链路:版本握手、PNG 签名(0x89 PNG)、画布尺寸推导、ring 偶奇成洞、mask 按 alpha 投影、缓冲长度校验、坏 JSON / 未知 op / 负 radius 全部 ok:false、模糊质量守恒 ±2%、能量外溢与核心保持

上游 Compose 面(4 个停车文件)零改动、零参与编译,接回时无暗改风险。

6.3 HAP 验证

:compose-boxshadow:compileKotlinOhosArm64 / OhosX64: BUILD SUCCESSFUL
:compose-dropshadow:compileKotlinOhosArm64 / OhosX64: BUILD SUCCESSFUL
linkDebugSharedOhosArm64 / linkDebugSharedOhosX64: BUILD SUCCESSFUL
llvm-nm -D: ShadowDispatch / ShadowVersion 均为全局 T 符号(双 ABI)
BuildNativeWithCmake: finished(双 ABI 编出 libshadow_napi.so)
CompileArkTS: finished
PackageHap: finished
BUILD SUCCESSFUL
install bundle successfully
start ability successfully

libohosshadow.so debugShared 实测 arm64 约 3.83 MB、x86_64 约 3.54 MB(含 Kotlin/Native 运行时),另附 konan 的 libc++_shared.so(1.85 MB / 1.73 MB);每个 ABI 三件套由 copyKnSo* 自动回填 entry/libs。

6.4 功能用例

  1. 启动后关于 Tab 显示 shadowVersion() → {"ok":true,"version":"0.3.0-ohos.1",...}(来自真实 Native 调用,非静态文字);
  2. 盒阴影 Tab 切换矩形 / 圆角 / 圆形,阴影轮廓随形状变化,圆角滑杆仅对 rounded 生效;
  3. 拖动 X / Y 偏移滑杆,阴影与红色内容块的相对错位随之变化,内容块自身位置(contentX / contentY)不变——验证偏移只作用于阴影层;
  4. 模糊半径 0 ↔ 40 渐变,从锐利轮廓到光晕,无"阴影比内容清楚"的倒置;
  5. 投影 Tab 五角星旋转 0-360°,阴影实时跟随内容轮廓(对应上游 drawInvalidationTrigger 动画语义);
  6. 裁剪开关切换 Unbounded / Rectangle,合成图画布尺寸立即在元信息行反映差异;
  7. 阴影色 / 内容色双板即时生效;
  8. 构造 shape=hexagon / radius=-5 / 坏 JSON 请求,返回 ok:false 原文,页面不崩;
  9. 真机元信息行实时显示三段耗时(见 7.1 截图:光栅 8.8ms / 模糊 14.4ms / PNG 19.5ms),每次滑杆拖动即一次完整 Native 往返。

6.5 验证结论

语义核心、光栅化管线、Native 动态库、C ABI、N-API、ArkUI 页面和 HAP 已完成链路验证。应用在鸿蒙真机上成功安装并进入前台,页面展示了真实的阴影渲染会话:版本握手、三形状盒阴影、偏移 / 模糊实时调参、旋转跟随投影、边界裁剪切换与三段耗时实测(见下节效果图)。

七、运行效果

7.1 真机截图

在这里插入图片描述

7.2 命令速查

git clone https://atomgit.com/oh-tpc/compose-shadow-alternative.git
cd compose-shadow-alternative
export JAVA_HOME="/path/to/jdk-21"
./gradlew :compose-boxshadow:compileKotlinOhosArm64 :compose-dropshadow:compileKotlinOhosArm64   # klib
./gradlew :compose-boxshadow:jvmTest :compose-dropshadow:jvmTest :example:nativeApp:jvmTest      # 26 项交叉验证
./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.shadow -m entry
hdc shell hilog | grep ShadowNapi

八、遗留问题与改进方向

8.1 踩坑复盘

  1. 只改 ArkTS 页面不算 KMP 适配,必须让 :compose-boxshadow / :compose-dropshadow / :example:nativeApp 真正编译成 ohosArm64 / ohosX64 产物;
  2. settings.gradle.kts 里 eazytec 双仓必须排在 pluginManagement 与 dependencyResolutionManagement 最前,否则配置期直接 Unresolved reference: ohosArm64;
  3. JVM 测试绿 ≠ 多平台绿:java.lang.Math / Character.digit / String.format 混进 common 代码时 JVM 全绿,ohosArm64 编译直接 Unresolved reference——common 代码只能用 kotlin.math 与手写工具函数;
  4. boxesForGauss 返回的是盒宽不是半径,直接当半径用等效 sigma 放大一倍(10px 方块核心 alpha 掉到 0.31),修正为 (w-1)/2 并用"质量守恒 ±2% / 能量外溢 / 核心保持 >0.5"三测试钉死;
  5. 负 radius 被 coerceIn(0, MAX) 静默钳 0 返回 ok:true,违反"非法输入走 ok:false"的桥纪律——改为显式拒绝;
  6. JavaExec 只挂 files(jvmJar) 会缺 kotlin-stdlib(NoClassDefFoundError: kotlin/io/FilesKt),要补 jvmRuntimeClasspath;Kotlin 2.2 的 KotlinCompilation.output.classesDirectories 不可用,脚本编译直接失败;
  7. TimeMark 之间的减法在部分版本缺操作符,统一改 measureTimedValue(扩展函数需显式 import kotlin.time.measureTimedValue);
  8. ohos 目标没有 kotlin.jvm.JvmInline,小值类型(ShadowPoint)一律普通 data class;
  9. @CName 在 2.2.21 线不在 kotlinx.cinterop 包,删掉 import 靠默认导入解析;
  10. 根构建的 /build/ 与 .idea/* 盖不住嵌套目录——DevEco 打开工程后生成的 example/harmonyApp/.idea/、entry/build/、entry/libs/(so 三件套)要单独追加 gitignore,so 由 Gradle 回填不入库;
  11. build-profile.json5 的 signingConfigs 条目缺 material 字段会被 hvigor schema 拒绝——仓库内留空数组,勾选自动签名后由 IDE 回填;
  12. HAP 缺当前 ABI 的 so 会报安装失败 9568347,abiFilters 必须双 ABI,每个 ABI 三件套(libshadow_napi.so / libohosshadow.so / libc++_shared.so)缺一不可;KN 返回字符串必须 DisposeString 归还运行时。

8.2 已知问题

  • composeMain 停车面等社区 compose.ui ohos 制品线与上游 CMP 版本对齐后接回,届时软件光栅化降级为 fallback(README.OpenHarmony_CN.md 第 20 节四步接回路线);
  • PNG 用 zlib stored 未压缩块,体积随画布像素数线性增长(172×172 实测 69,842 B),大画布建议降 ss 或减尺寸;
  • content=mask(宿主传 RGBA)不支持 rotate(旋转只作用于内置几何内容;宿主可先自行旋转位图再传入);
  • 上游 Modifier.boxShadow(shape = ...) 接受任意 Shape,桥内置矩形 / 圆角 / 圆形三种,任意形状经 content=mask 走 alpha 通道(等价能力,不同入口);
  • RTL(layoutDirection)未镜像,RoundedCornerShape 的 start/end 随布局方向翻转按 LTR 处理;
  • 上游 DropShadow 的 drawInvalidationTrigger 对应"重新 dispatch",桥是同步一次一调用,无推送语义;
  • android / ios / macos / js 目标剥离(源文件留在树里),发布、CI 一并剥离,本 fork 不发 Maven Central。

8.3 未来优化方向

  • 补 content=mask + componentSnapshot 的文字投影官方示例页(对应上游 sample 三色文字场景);
  • 增加 op=perf 基准入口(批量迭代计时,对齐 Korner 性能 Tab);
  • release linkReleaseShared 瘦身(debugShared 含运行时 3.83 MB,release 可减半);
  • 在 CI 中加入 ohos 编译、ELF 符号检查和 HAP 构建;
  • 补充模拟器(x86_64)与其他设备 ABI 的验收矩阵和自动化截图脚本;
  • 跟踪社区 Compose-ohos 制品线,准备 composeMain 接回。

九、总结

9.1 核心难点回顾

上游 compose-boxshadow + compose-dropshadow(100% Compose 胶水,无可提取引擎)
    → 验证 Compose-ohos 制品线对不齐 → 定停车隔离路线(qrose/Cloudy 先例)
    → 工具链对齐 Kotlin 2.2.21-1.0.0(Gradle 8.14.1)
    → 根构建替换(cmp_qrose 模式)+ Compose 面 git mv 停车
    → 语义核心复述(ShadowShape / BoxShadowSpec / DropShadowSpec)+ 16 项钉死测试
    → ohosArm64 / ohosX64 KLIB + CPU 光栅化(扫描线 + 3-pass 盒式模糊)
    → C ABI(@CName × 2)+ PNG Base64 载荷
    → C++ N-API(DisposeString 纪律)
    → ArkTS JSON 解析 + Base64 → PixelMap
    → ArkUI 真机三 Tab 验收(盒阴影 / 投影 / 版本握手)

9.2 封装层次

  • 引擎层:形状几何(轮廓多边形)、参数模型与校验、模糊换算、画布布局公式;
  • 光栅化层:偶奇扫描线填充(超采样抗锯齿)、3-pass 高斯近似、直 alpha 合成、零依赖 PNG;
  • Native 层:生成双 ABI 动态库并通过 2 个 C ABI 输出 JSON;
  • N-API 层:完成参数检查、字符串转换和 Native 内存释放;
  • ArkTS 层:管理页面状态、滑杆实时调参、Base64 解码 PixelMap、元信息与错误文本;
  • DevEco 层:完成 CMake、HAP、签名、安装和真机运行。

9.3 三条经验

  1. 没有引擎的库,适配对象是"语义"而不是"代码":qrose 提取编码器、KStore 真编译整库,前提都是上游有 UI-free 的部分;纯 Compose 胶水库的正确答案是把实现逆向成语义模型(几何、换算、层序、边界)再正射为纯 Kotlin,并用测试钉死——"复述"的保真度靠第一性原理推导 + JVM 同源执行双重保证;
  2. 工具链版本跟着依赖走,两条线的经验不能互相套用:本库零第三方依赖,按"依赖矩阵要什么"取老线(2.2.21 + Gradle 8.14.1);KStore 被 kotlinx ohos 制品牵引到新线(2.4.20 + Gradle 9.6.1)——选线依据是制品可达性,不是版本新旧;
  3. 交叉验证的强度取决于"同源程度":把 dispatch 核心放 commonMain、让 JVM 测试与 ohos .so 执行同一份代码,比"JVM 参考实现"强一个量级;模糊这类视觉正确性没有客观断言,要转成物理性质(质量守恒、能量外溢、核心保持)才可测,再由真机元信息行的实测耗时闭环。

9.4 适配成果

当前适配已完成:

  • compose-shadow-alternative 0.3.0 语义核心的双 ABI Kotlin/Native 编译(上游 Compose 面零改动停车,3 个纯 Kotlin 语义文件新写并测试钉死);
  • 2 个 C ABI 符号和 C++ N-API 包装层(llvm-nm 验证全局 T 符号,libohosshadow.so arm64 3.83 MB / x86_64 3.54 MB);
  • DevEco Stage ArkUI 三 Tab 验收页面(盒阴影实时调参、投影旋转跟随、边界裁剪、双色板、版本握手、三段耗时实测);
  • 26 项 JVM 交叉验证全绿 + HAP 构建(双 ABI 六件套 so)、签名、设备安装和真机验证;
  • AtomGit 项目文档、OpenHarmony README(中英双份、中文主文档 5015 字)和真机效果图。

适配过程 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

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐