开源鸿蒙平台 CMP 三方库「compose-shadow-alternative」适配全流程
本文记录
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、其余平台走 SkiaMaskFilter模糊画笔)与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)版本对不齐,而这两个库的每一行实现都 importandroidx.compose.*——真编译整库在当前制品线下不可行。因此走社区已审核的"停车隔离"路线(cmp_qrose / Korner / day-and-night-switch / Cloudy 同一条路):上游 Compose 面git mv停车到src/composeMain字节不动,阴影语义(形状几何、模糊换算、两层画布布局、边界处理)复述为纯 Kotlin 编进 ohos 目标,经libohosshadow.so(@CName ShadowDispatch / ShadowVersion)+ CMake N-APIlibshadow_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 | 模糊换算结果与三段耗时实测。 |
pngBase64 | boxshadow 为阴影层 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 Multiplatform | HarmonyOS Kotlin 2.2.21-1.0.0(eazytec Maven,社区批准的老线) |
| JDK | 21(org.gradle.java.home 指向 Temurin 21) |
| OpenHarmony 目标 | ohosArm64 + ohosX64(jvm() 保留作交叉验证) |
| Gradle | 8.14.1(上游 8.10.2 升级;2.2.21 线不支持 Gradle 9.x) |
| konan | ohos sysroot 经 kotlin.native.distribution.downloadFromMaven=true 从社区 Maven 拉取 |
| DevEco product | 5.1.0(18) 兼容 |
| DevEco Studio | 5.1.0 Release |
| ABI | arm64-v8a + x86_64 |
| Bundle | org.terminator.ohos.shadow |
export JAVA_HOME="/path/to/jdk-21"
java -version
1.3 源码依赖度盘点
| 文件 | 依赖形态 | 处置 |
|---|---|---|
BoxShadow.kt | androidx.compose.ui 全家(draw / graphics / unit) | 停车 composeMain,commonMain 腾位装语义核心 |
blur.android.kt | android.graphics.BlurMaskFilter + compose Paint | 停车 composeMain(expect 的 actual,属 Compose 面) |
blur.nonAndroid.kt | compose Paint + org.jetbrains.skia.MaskFilter | 停车 composeMain(上游自定义 nonAndroidMain 源集不再接线) |
DropShadow.kt | foundation / 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 方法 | 覆盖内核 |
|---|---|---|
ShadowDispatch | shadowDispatch(req) | 形状几何 + 光栅化 + 模糊 + 合成 + PNG |
ShadowVersion | shadowVersion() | 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.toml | Kotlin 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.kt | 2 个 op 的 dispatch 核心(JVM 与 ohos 同一份) |
nativeApp/.../Bridge.kt | 2 个 C ABI、错误通道 |
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 三 Tab 验收页面 |
README.OpenHarmony_CN.md | 鸿蒙适配主文档(24 节) |
4.3 关键 API 对照
| 层次 | API | 作用 |
|---|---|---|
| 引擎 | ShadowShape.outline(w, h) | Rectangle / RoundedRect / Circle → 轮廓多边形(Circle 是内切椭圆) |
| 引擎 | BoxShadowSpec.blurSigma / canvasOutset | radius×0.5 换算与 margin 公式 |
| 引擎 | DropShadowSpec.outputSize / shadowOrigin / contentOrigin | Unbounded / Rectangle 两种画布布局 |
| 光栅化 | Rasterizer.polygonMask | 偶奇扫描线 + 超采样抗锯齿(ring 双回路天然成洞) |
| 光栅化 | Rasterizer.gaussianBlur | 3-pass 盒式模糊高斯近似(boxesForGauss 盒宽转半径) |
| 光栅化 | Rasterizer.compositeOver | 直 alpha「内容 over 阴影」合成 |
| Native | ShadowDispatch / ShadowVersion | C ABI JSON 导出 |
| N-API | shadowDispatch(req) / shadowVersion() | 向 ArkTS 暴露能力 |
| ArkTS | renderBox() / 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 项用例全部通过:
| 测试类 | 用例数 | 钉住什么 |
|---|---|---|
ShadowShapeTest | 7 | 轮廓几何:矩形四角、圆角每角 12 段弧共 52 点、界内钳位、0 圆角退化矩形、Circle 内切椭圆等距性、负圆角拒绝 |
BoxShadowSpecTest | 5 | sigma = radius×0.5、margin 公式、负半径 / 非有限偏移拒绝、0 半径合法 |
DropShadowSpecTest | 4 | Unbounded 画布留白与图层原点、Rectangle 不留白、参数校验 |
ShadowOpsTest | 10 | 桥全链路:版本握手、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 功能用例
- 启动后关于 Tab 显示
shadowVersion() → {"ok":true,"version":"0.3.0-ohos.1",...}(来自真实 Native 调用,非静态文字); - 盒阴影 Tab 切换矩形 / 圆角 / 圆形,阴影轮廓随形状变化,圆角滑杆仅对 rounded 生效;
- 拖动 X / Y 偏移滑杆,阴影与红色内容块的相对错位随之变化,内容块自身位置(contentX / contentY)不变——验证偏移只作用于阴影层;
- 模糊半径 0 ↔ 40 渐变,从锐利轮廓到光晕,无"阴影比内容清楚"的倒置;
- 投影 Tab 五角星旋转 0-360°,阴影实时跟随内容轮廓(对应上游
drawInvalidationTrigger动画语义); - 裁剪开关切换
Unbounded/Rectangle,合成图画布尺寸立即在元信息行反映差异; - 阴影色 / 内容色双板即时生效;
- 构造
shape=hexagon/radius=-5/ 坏 JSON 请求,返回ok:false原文,页面不崩; - 真机元信息行实时显示三段耗时(见 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 踩坑复盘
- 只改 ArkTS 页面不算 KMP 适配,必须让
:compose-boxshadow/:compose-dropshadow/:example:nativeApp真正编译成ohosArm64/ohosX64产物; settings.gradle.kts里 eazytec 双仓必须排在pluginManagement与dependencyResolutionManagement最前,否则配置期直接Unresolved reference: ohosArm64;- JVM 测试绿 ≠ 多平台绿:
java.lang.Math/Character.digit/String.format混进 common 代码时 JVM 全绿,ohosArm64 编译直接Unresolved reference——common 代码只能用kotlin.math与手写工具函数; boxesForGauss返回的是盒宽不是半径,直接当半径用等效 sigma 放大一倍(10px 方块核心 alpha 掉到 0.31),修正为(w-1)/2并用"质量守恒 ±2% / 能量外溢 / 核心保持 >0.5"三测试钉死;- 负 radius 被
coerceIn(0, MAX)静默钳 0 返回ok:true,违反"非法输入走 ok:false"的桥纪律——改为显式拒绝; - JavaExec 只挂
files(jvmJar)会缺 kotlin-stdlib(NoClassDefFoundError: kotlin/io/FilesKt),要补jvmRuntimeClasspath;Kotlin 2.2 的KotlinCompilation.output.classesDirectories不可用,脚本编译直接失败; TimeMark之间的减法在部分版本缺操作符,统一改measureTimedValue(扩展函数需显式import kotlin.time.measureTimedValue);- ohos 目标没有
kotlin.jvm.JvmInline,小值类型(ShadowPoint)一律普通 data class; @CName在 2.2.21 线不在kotlinx.cinterop包,删掉 import 靠默认导入解析;- 根构建的
/build/与.idea/*盖不住嵌套目录——DevEco 打开工程后生成的example/harmonyApp/.idea/、entry/build/、entry/libs/(so 三件套)要单独追加 gitignore,so 由 Gradle 回填不入库; build-profile.json5的signingConfigs条目缺material字段会被 hvigor schema 拒绝——仓库内留空数组,勾选自动签名后由 IDE 回填;- HAP 缺当前 ABI 的 so 会报安装失败
9568347,abiFilters必须双 ABI,每个 ABI 三件套(libshadow_napi.so/libohosshadow.so/libc++_shared.so)缺一不可;KN 返回字符串必须DisposeString归还运行时。
8.2 已知问题
composeMain停车面等社区compose.uiohos 制品线与上游 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 三条经验
- 没有引擎的库,适配对象是"语义"而不是"代码":qrose 提取编码器、KStore 真编译整库,前提都是上游有 UI-free 的部分;纯 Compose 胶水库的正确答案是把实现逆向成语义模型(几何、换算、层序、边界)再正射为纯 Kotlin,并用测试钉死——"复述"的保真度靠第一性原理推导 + JVM 同源执行双重保证;
- 工具链版本跟着依赖走,两条线的经验不能互相套用:本库零第三方依赖,按"依赖矩阵要什么"取老线(2.2.21 + Gradle 8.14.1);KStore 被 kotlinx ohos 制品牵引到新线(2.4.20 + Gradle 9.6.1)——选线依据是制品可达性,不是版本新旧;
- 交叉验证的强度取决于"同源程度":把 dispatch 核心放 commonMain、让 JVM 测试与 ohos
.so执行同一份代码,比"JVM 参考实现"强一个量级;模糊这类视觉正确性没有客观断言,要转成物理性质(质量守恒、能量外溢、核心保持)才可测,再由真机元信息行的实测耗时闭环。
9.4 适配成果
当前适配已完成:
compose-shadow-alternative0.3.0语义核心的双 ABI Kotlin/Native 编译(上游 Compose 面零改动停车,3 个纯 Kotlin 语义文件新写并测试钉死);- 2 个 C ABI 符号和 C++ N-API 包装层(
llvm-nm验证全局 T 符号,libohosshadow.soarm64 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。
参考文档
更多推荐



所有评论(0)