本文记录 haze 接入 OpenHarmony 的完整过程,覆盖 CMP 视觉效果库盘点、ohosArm64/ohosX64 双目标、Kotlin/Native 动态库、C ABI/N-API 桥接、ArkUI 真机页面、HAP 构建、签名和真机验收。

haze 是 Compose Multiplatform 生态的毛玻璃效果库,对外 API 是 Modifier.hazeSource() / Modifier.hazeEffect(...),全部实现在 Compose Modifier.Node + GraphicsLayer 体系里。本次适配把它的参数模型内核(HazeBlurDefaults 默认值、HazeMaterials 五档材质表、HazeProgressive 渐进渐变、EaseIn 缓动、亮度判定、ResolvedHazeBlurStyle 合成顺序)复刻进 OpenHarmony ARM64/x64 运行时,配一条 Kotlin/Native 软件模糊管线,ArkUI 页面作为真机验收宿主——鸿蒙侧解析出的材质 alpha、渐进强度与 Android/iOS/Desktop 完全同源。

项目地址: AtomGit/oh-tpc/haze

上游仓库: chrisbanes/haze

开发工具: 华为云码道

环境安装: 环境安装指南

一、背景

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

haze 为 Compose 应用提供毛玻璃 / 渐进模糊 / 材质着色能力:开发者用 hazeSource 标记内容层、用 hazeBlur(以及 Haze 2.x 的 haze-glass 效果工厂)标记效果层,即可获得背景模糊、线性/径向渐进模糊、SwiftUI 风格材质预设和噪点质感。KMP 项目把这些效果的参数语义放在 commonMain,由 Android、iOS、Desktop 等宿主共享渲染。OpenHarmony 应用同样需要这套能力,但默认构建不会生成 OpenHarmony 的 Kotlin/Native 变体。

如果只把示例页面改成 ArkTS 调系统 backgroundBlurStyle,页面能糊,却无法证明公共 Kotlin 实现已经进入 OpenHarmony ARM64 设备完成编译、链接和执行,也无法保证跨端材质参数一致。适配需要同时验证 KMP 模型内核、Native 动态库、C ABI、N-API、ArkUI 页面和 HAP 交付链路。

障碍具体问题
目标缺失必须增加 ohosArm64() / ohosX64() 才能生成 OpenHarmony KLIB 与动态库。
工具链不一致上游 Compose 1.12.1 / Kotlin 2.4.21 / AGP 9 / Gradle 9.8.1;社区 CMP 1.9.2 / HarmonyOS Kotlin 2.2.21-1.0.0。两条插件线不能共存于同一个 Gradle 构建。
源码面 100% Compose通读 haze / haze-blur / haze-materials 的 commonMain:完全无 Compose 依赖的源文件只有 4 个(约 128 行),其余全部锚定 androidx.compose.ui。
渲染原语不同上游模糊走平台 RenderEffect / Skia;Kotlin/Native 共享库里没有 Skia,也没有 RenderEffect。
语言边界不同ArkTS 不能直接持有 Kotlin 对象,需要 C ABI、C++ N-API 和 JSON。
交付链路复杂.so、CMake、N-API、HAP、签名和 hdc 都要单独验收。

因此,本项目把边界放在 KMP 目标配置、参数模型内核和 C ABI/N-API 桥接层。效果语义仍由公共 Kotlin 实现负责,ArkTS 只负责调用 Native、解析 JSON 和展示结果。

1.2 库提供的能力

真机 Demo 提供三个可观察场景(Tab):

  • 毛玻璃材质:HazeMaterials 五档预设(ultraThin / thin / regular / thick / ultraThick)× 明暗容器切换,默认半径 24dp 或手动 0–80dp,noiseFactor 0–1;
  • 渐进模糊:HazeProgressive 纵向线性 / 横向线性 / 径向三种模式,双端强度(start/end 或 center/radius)实时可调,EaseIn 缓动;
  • 自定义参数:blurEnabled 开关、整体 alpha、纯色 tint(HazeColorEffect.tint 语义)、噪点、蒙版形状(圆角矩形 / 圆形)与面板位置尺寸。

Native 侧返回的 RenderResult 包含:

字段作用
ok / error调用结果;Kotlin 异常永不跨语言边界,统一转 JSON 错误。
width / height / bytes画布尺寸与 PNG 字节数,供状态行对账。
blurRadius / material / progressive解析后的生效参数(材质 → 24dp、默认 → 20dp 等规则可验证)。
tint / tintAlpha生效 tint 颜色与解析后浮点 alpha(材质按容器亮度选 light/dark 档)。
luminance容器色 W3C 相对亮度,验证选档依据。
pngBase64完整渲染帧(内容 + 玻璃面板)PNG。

共享层还提供三类交叉验证:模型钉值(默认值、材质十档 alpha、亮度、缓动)、管线性质(常数图不变、脉冲对称、PNG 结构)、协议回显(alpha=0 时输出与原始场景逐字节一致)。

1.3 实现适配

维度要求
代码复用Haze 2.x 参数模型以纯 Kotlin 复刻进 ohosArm64/ohosX64,每个值注明上游出处并由 JVM 测试钉住。
平台目标example/nativeApp 独立消费工程启用 ohosArm64 + ohosX64,生成双 ABI 动态库。
桥接稳定两个 C ABI 符号 + 扁平 UTF-8 JSON,不传递 Kotlin 对象地址,返回字符串由 N-API 侧即刻释放。
CMP 兼容上游 haze 模块源码零改动;社区 CMP 工具链对齐后可直接原生编 ohos,本内核退役为对照实现。
UI 完整三 Tab 覆盖桥全部入口字段,每个字段至少一个可见控件与状态行回显。
仓库规范项目说明、文章、效果图和源码链接统一使用 AtomGit。

本项目的 ArkUI 页面不是 Compose Multiplatform UI 实现。haze 的 hazeSource / hazeBlur 修饰符仍在仓库 commonMain,继续由 Android/iOS/Desktop/Web 消费;鸿蒙 Demo 验证的是同一份参数模型内核的 OpenHarmony Native 链路。

二、实现路线图

第 1 阶段:项目初始化     ── 盘点源码集、效果框架边界和 OpenHarmony 交付物
第 2 阶段:目标与依赖打通 ── example/ 独立 Gradle 工程、ohosArm64/ohosX64、双 ABI so
第 3 阶段:参数模型建模   ── HazeModel 复刻 HazeBlurDefaults/Materials/Progressive + 钉值测试
第 4 阶段:原生桥接       ── C ABI、C++ N-API、参数校验和内存释放
第 5 阶段:ArkUI 宿主     ── 三 Tab 玻璃工作台、状态行回显、错误边界
第 6 阶段:示例与验证     ── HAP、签名、设备安装、真机运行和效果图

三、逐步实现过程

第 1 阶段:项目初始化

1.1 工程边界
haze/ haze-blur/ haze-glass/ haze-materials/   上游库模块,源码零改动
example/settings.gradle.kts                    独立 OHOS 构建 rootProject "ohos_haze"
example/gradle/ + gradlew                      Gradle 8.14.1 独立 wrapper
example/nativeApp/src/commonMain/              HazeModel / BlurKernel / SceneRenderer / HazeEngine / PngEncoder / MiniJson
example/nativeApp/src/ohosMain/                Bridge.kt(@CName C ABI)
example/nativeApp/src/jvmTest/                 模型钉值 + 管线 + 协议共 23 项测试
example/harmonyApp/                            DevEco Stage:CMake NAPI + ArkTS 三 Tab

example 是独立 Gradle 工程:上游根构建(build-logic 复合构建、发布流、截图/基准测试)一字不动,Gradle 与 Hvigor 分开执行,互不影响。这是 miuix 先例确立、本仓沿用的边界——上游构建复杂时,替换式改写会砸坏整条流水线。

1.2 工具链矩阵
项目配置
Kotlin MultiplatformOpenHarmony 构建使用 HarmonyOS Kotlin 2.2.21-1.0.0(eazytec Maven)
Gradle8.14.1(example/ 独立 wrapper;上游根构建 9.8.1 保留)
JDK21(Temurin;org.gradle.java.home 写入 example/gradle.properties)
OpenHarmony 目标ohosArm64 + ohosX64
DevEco product5.1.0(18),runtimeOS: HarmonyOS
DevEco Studio5.1.0 Release
ABIarm64-v8a + x86_64
Bundleorg.terminator.ohos.haze
export JAVA_HOME="/path/to/jdk-21"
java -version
1.3 页面边界
标题区          haze · OpenHarmony / 版本握手行(桥 2.0.1-ohos.1 · 上游 2.0.1 · Kotlin 2.2.21-1.0.0)
Tab 区          毛玻璃材质 / 渐进模糊 / 自定义参数
场景区          湖景 / 卡片 / 渐变(程序化内容,替代 hazeSource 捕获的真实 UI)
参数区          材质五档、明暗容器、半径/噪点/强度滑条、蒙版形状与位置
结果区          Image(PixelMap) 全宽展示 + 状态行(尺寸 / png 字节 / tintα)

页面启动时先做 hazeVersion() 版本握手并展示,再自动渲染默认帧:湖景 + regular 材质 + 浅色容器。

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

2.1 加入 ohosArm64 / ohosX64

example/settings.gradle.kts 的 pluginManagement 与 dependencyResolutionManagement 都把两个 eazytec Maven URL 排在最前,否则 ohosArm64() 在官方 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_haze"
include(":nativeApp")

nativeApp 只声明三个目标,sharedLib 只放这里,避免每个模块各吐一份带运行时的 so:

kotlin {
    applyDefaultHierarchyTemplate()
    jvm()                       // JVM 交叉验证目标
    ohosArm64 { binaries.sharedLib { baseName = "ohoshaze" } }
    ohosX64   { binaries.sharedLib { baseName = "ohoshaze" } }
}
2.2 构建核心库和 Native 产物
cd example
./gradlew :nativeApp:jvmTest \
  :nativeApp:linkDebugSharedOhosArm64 \
  :nativeApp:linkDebugSharedOhosX64

copyKnSoArm64 / copyKnSoX64 用 finalizedBy 挂在链接任务上,准备:

example/harmonyApp/entry/libs/arm64-v8a/libohoshaze.so      ≈8.51 MB
example/harmonyApp/entry/libs/arm64-v8a/libc++_shared.so    ≈1.85 MB(同 triple)
example/harmonyApp/entry/libs/x86_64/libohoshaze.so         ≈7.71 MB
example/harmonyApp/entry/libs/x86_64/libc++_shared.so       ≈1.73 MB

nm -gU libohoshaze.so 可见 HazeRender / HazeVersion 均为全局 T 符号。

2.3 独立消费工程

example 不依赖上游任何 Gradle 模块,内核(模型 + 管线)就在 nativeApp/src/commonMain 里,同时被 jvm() 与两个 ohos 目标编译——同一份源码,JVM 上跑测试钉值,ohos 上跑真机。

第 3 阶段:参数模型建模(技术难点)

3.1 JSON 契约
Index.ets generate()
        ↓ HazeRequest(扁平 JSON)
N-API hazeRender(string)
        ↓ const char*
@CName("HazeRender")
        ↓ HazeEngine.render
场景渲染 → 盒式模糊 → 材质/tint/噪点 → alpha×progressive×蒙版
        ↓ PNG Base64
ArkTS Image(PixelMap)

页面不复制任何材质判定或强度插值逻辑,每个回显字段都由公共 Kotlin 代码生成。

3.2 复刻的数据模型

HazeModel.kt 把四个上游源文件的取值语义 1:1 镜像成纯 Kotlin,文件头逐条注明出处:

object HazeModel {
  const val VERSION = "2.0.1-ohos.1"        // <上游>-ohos.1 约定
  const val UPSTREAM_VERSION = "2.0.1"

  object Defaults {                          // HazeBlurDefaults.kt
    const val BLUR_RADIUS_DP = 20f           // blurRadius = 20.dp
    const val NOISE_FACTOR = 0.15f           // noiseFactor = 0.15f
  }

  object Materials {                         // HazeMaterials.kt
    const val BLUR_RADIUS_DP = 24f           // 材质统一写 blurRadius(24.dp)
    const val LUMINANCE_THRESHOLD = 0.5f     // luminance >= 0.5 → lightAlpha
    val ultraThin  = Material("ultraThin",  0.35f, 0.55f)
    val thin       = Material("thin",       0.60f, 0.65f)
    val regular    = Material("regular",    0.73f, 0.80f)
    val thick      = Material("thick",      0.83f, 0.90f)
    val ultraThick = Material("ultraThick", 0.92f, 0.97f)
  }

  fun easeIn(t: Float): Float = cubicBezier(0.42f, 0f, 1f, 1f, t)  // EaseIn
  fun luminance(argb: Int): Float // W3C sRGB 相对亮度(0.04045 阈值)
}
3.3 渲染逻辑

管线严格按上游 ResolvedHazeBlurStyle 的合成顺序:

// HazeEngine.render 主循环(节选)
val blurred = BlurKernel.blur(content, width, height, blurRadiusDp.toInt())
// 三趟盒式模糊逼近高斯,边缘 clamp 对应 BlurredEdgeTreatment.Rectangle

for (pixel in panelMask) {
    val intensity = alpha * progressive(x, y)   // EaseIn 插值的双端强度
    var effect = blurred[idx]
    effect = srcOver(effect, backgroundColor)   // 材质写 backgroundColor(container)
    effect = srcOver(effect, tint)              // HazeColorEffect.tint(container.copy(alpha))
    effect = jitter(effect, grain(noiseFactor)) // 确定性噪点
    out[idx] = lerp(content[idx], effect, mask * intensity)
}
// PNG(stored zlib)→ Base64 → JSON 回显

渐进强度是 HazeProgressive.asBrush 的连续形式:lerp(startIntensity, endIntensity, easing.transform(x)),径向模式的半径取「能放进绘制区域的最大半径」(面板对角线一半),与上游默认一致。

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

4.1 三层桥接
ArkTS
  │ 扁平 JSON string
  ▼
C++ N-API entry(libhaze_napi.so)
  │ const char*
  ▼
Kotlin/Native C ABI(libohoshaze.so)
  │ HazeEngine
  ▼
UTF-8 JSON + 显式 DisposeString

HarmonyOS Kotlin 的 CAdapter 不会把 @CName 业务函数挂到 ArkTS 模块,所以 ArkTS 永远只 import NAPI 包装层,不 import KN so。

4.2 Kotlin/Native 导出函数
@CName("HazeVersion")
fun hazeVersion(): String = HazeEngine.versionJson()

@CName("HazeRender")
fun hazeRender(request: String): String = try {
    HazeEngine.render(request)
} catch (t: Throwable) {
    """{"ok":false,"error":${jsonString(t.message ?: t.toString())}}"""
}

Kotlin 异常永不跨语言边界——跨过去是无信息崩溃,统一转 ok:false。

4.3 C++ N-API 方法分发
napi_property_descriptor desc[] = {
    {"hazeRender",  nullptr, NapiHazeRender,  nullptr, nullptr, nullptr,
     static_cast<napi_property_attributes>(napi_default | napi_enumerable), nullptr},
    {"hazeVersion", nullptr, NapiHazeVersion, nullptr, nullptr, nullptr,
     static_cast<napi_property_attributes>(napi_default | napi_enumerable), nullptr},
};

API 26 的 NAPI 头把 napi_get_value_string_utf8 缓冲区标成非 const,std::string::data() 是 const char* 直接传会编不过:先量长、std::vector<char> 拷贝、再构造 std::string。KN 返回的字符串先拷进 JS 字符串,再调 libohoshaze_symbols()->DisposeString(raw) 释放,不 free,不让 ArkTS 管。

4.4 CMake 和类型声明
add_library(ohoshaze SHARED IMPORTED)
set_target_properties(ohoshaze PROPERTIES
    IMPORTED_LOCATION ${KN_SO_DIR}/libohoshaze.so
    IMPORTED_NO_SONAME TRUE)

add_library(haze_napi SHARED napi_init.cpp)
target_link_options(haze_napi PRIVATE "-Wl,--no-undefined" "-Wl,-rpath,\$ORIGIN")
target_link_libraries(haze_napi PUBLIC libace_napi.z.so libhilog_ndk.z.so ohoshaze)

add_custom_command(TARGET haze_napi POST_BUILD  # KN so + libc++ 保持在 NAPI so 旁边
    COMMAND ${CMAKE_COMMAND} -E copy_if_different ${KN_SO} $<TARGET_FILE_DIR:haze_napi>
    ...)
export const hazeRender: (request: string) => string;
export const hazeVersion: () => string;

第 5 阶段:ArkUI 宿主

5.1 版本握手和默认帧
import { hazeRender, hazeVersion } from 'libhaze_napi.so';

const v = JSON.parse(hazeVersion()) as VersionOk;
this.versionLine = `桥 ${v.version} · 上游 ${v.upstream} · Kotlin ${v.kotlin} · ${v.engine}`;
this.generate();   // aboutToAppear 自动渲染默认帧
5.2 页面交互

毛玻璃材质 Tab 提供五档材质、明暗容器开关、「材质默认半径 24dp」开关(打开时请求省略 blurRadius,由 Native 按材质规则解析为 24)、噪点滑条;渐进模糊 Tab 提供三种模式切换与双端强度滑条(线性显示 start/end,径向显示 center/radius);自定义参数 Tab 提供 blurEnabled、整体 alpha、六色 tint、蒙版形状(圆角矩形 / 圆形)、panelRadius 与面板位置尺寸。滑条拖动中只刷新数值标签,松手(SliderChangeMode.End/Click)才进 Native 重渲染。

5.3 状态回显和错误边界

状态行随每次渲染更新:生成成功 360x640 png=…B tintα=…,其中 tintAlpha 是 Native 解析后的浮点值(材质按亮度选档 / 显式值),与上游表格逐位对账。ok:false 时显示 失败: ${error},HAP 不崩溃。

第 6 阶段:示例与验证

6.1 工程结构
example/
├── settings.gradle.kts / build.gradle.kts / gradle.properties / gradlew
├── nativeApp/
│   ├── build.gradle.kts                       # jvm + ohos 双 ABI + copyKnSo*
│   └── src/
│       ├── commonMain/kotlin/example/nativeapp/  # HazeModel / BlurKernel / SceneRenderer / HazeEngine / PngEncoder / MiniJson
│       ├── ohosMain/kotlin/example/nativeapp/Bridge.kt
│       └── jvmTest/kotlin/example/nativeapp/     # 23 项交叉验证
└── harmonyApp/
    ├── AppScope/app.json5                     # bundle org.terminator.ohos.haze
    └── entry/src/main/
        ├── cpp/{CMakeLists.txt, napi_init.cpp, include/, types/libhaze_napi/}
        ├── ets/{entryability/EntryAbility.ets, pages/Index.ets}
        └── module.json5 / resources/
6.2 构建、签名和安装
export JAVA_HOME="/path/to/jdk-21"
cd example
./gradlew :nativeApp:jvmTest \
  :nativeApp:linkDebugSharedOhosArm64 \
  :nativeApp:linkDebugSharedOhosX64

# DevEco Studio 打开 example/harmonyApp(不是仓库根目录!)
# File → Project Structure → Signing Configs 手动自动签名(证书不入库)
hvigorw assembleHap -p product=default -p buildMode=debug --no-daemon
hdc list targets -v
hdc -t <设备序列号> install -r \
  example/harmonyApp/entry/build/default/outputs/default/entry-default-signed.hap
hdc -t <设备序列号> shell aa start \
  -a EntryAbility -b org.terminator.ohos.haze -m entry

四、完整代码对照

4.1 整体架构

HazeModel.kt / BlurKernel.kt / SceneRenderer.kt
    │ Haze 2.x 参数模型 + 软件模糊 + 程序化场景
    ▼
HazeEngine.kt
    │ ResolvedHazeBlurStyle 合成顺序 → PNG Base64 JSON
    ▼
Bridge.kt
    │ HazeRender / HazeVersion(@CName C ABI)
    ▼
libhaze_napi.so
    │ hazeRender / hazeVersion
    ▼
Index.ets
    │ JSON parse + PixelMap 刷新
    ▼
ArkUI 真机页面

4.2 文件清单

文件职责
example/settings.gradle.kts独立 OHOS 构建、eazytec 插件仓库、ohos_haze
example/gradle.propertiesJDK 21、native 三开关、version=2.0.1-ohos.1
example/nativeApp/build.gradle.ktsjvm + ohosArm64/ohosX64 sharedLib + copyKnSo*
nativeApp/.../HazeModel.ktHaze 2.x 参数模型复刻(出处注释 + 钉值)
nativeApp/.../BlurKernel.kt三趟盒式模糊 + 确定性噪点
nativeApp/.../SceneRenderer.kt湖景 / 卡片 / 渐变三个程序化场景
nativeApp/.../HazeEngine.ktJSON 协议解析 + 渲染管线 + 回显
nativeApp/.../PngEncoder.kt / MiniJson.kt零依赖 PNG(stored zlib)/ 扁平 JSON 解析
nativeApp/.../Bridge.kt@CName C ABI,catch-all JSON 错误
nativeApp/src/jvmTest/23 项:钉值 / 管线 / 协议
harmonyApp/.../napi_init.cppN-API 导出、字符串转换与释放
harmonyApp/.../Index.etsArkUI 三 Tab 玻璃工作台
README.OpenHarmony.md / _CN.md适配说明(中文版为主文档)

4.3 关键 API 对照

层次API作用
KMPHazeModel.DefaultsHazeBlurDefaults:20dp / 0.15 / 默认开启
KMPHazeModel.Materials五档材质 alpha 表 + 24dp + 亮度选档
KMPHazeModel.easeInCubicBezierEasing(0.42, 0, 1, 1)(Compose EaseIn)
KMPHazeEngine.render按 ResolvedHazeBlurStyle 顺序渲染整帧
NativeHazeRenderJSON 请求 → JSON(含 pngBase64)响应
NativeHazeVersionfork / 上游 / Kotlin / 引擎握手
N-APIhazeRender / hazeVersion向 ArkTS 暴露的两个函数
ArkTSgenerate组装请求、调桥、刷新 PixelMap 与状态行

4.4 ArkTS 与 Kotlin 的边界

const raw = hazeRender(JSON.stringify({
  scene: 'landscape', material: 'regular', containerColor: '#F7F2FA',
  progressive: 'linear-v', startIntensity: 0, endIntensity: 1,
}));
const res = JSON.parse(raw) as RenderOk;
val tintAlpha = material.tintAlpha(containerColor)   // luminance >= 0.5 → 0.73f
val tint = Colors.withAlpha(containerColor, tintAlpha)

两者之间只传输扁平 JSON,不传输 Kotlin 对象或未校验的动态结构;材质选档、强度插值、亮度计算全部在 Kotlin 侧,ArkTS 不重写任何判定。

五、关键决策说明

决策 1:独立 example 构建,而不是整仓切换鸿蒙 Kotlin

上游根构建包含 build-logic 复合构建、发布流、截图与基准测试;替换式改写(简单上游可接受)会砸坏整条流水线。独立构建零侵入、与上游可并行维护。

决策 2:复刻参数模型内核,而不是硬接 Compose 修饰符

haze 的 commonMain 没有可整体搬走的「纯 Kotlin 算法文件」(仅 4 个约 128 行的枚举/开关),有价值的正是参数语义。1:1 复刻 + 上游出处注释 + JVM 钉值,是对「代码复用」的诚实实现;硬接 Modifier.Node/GraphicsLayer 等于再打一套 Compose 发行版。

决策 3:软件模糊管线,而不是等平台 RenderEffect

共享库里没有 Skia/RenderEffect。三趟盒式模糊(boxes-for-gaussians)自包含、可测、语义可对齐;实时毛玻璃场景交给系统 backgroundBlurStyle,本桥定位是跨端一致的材质规范源与静态渲染。

决策 4:JSON 作为跨语言数据契约

扁平键值对 + 手写 MiniJson,不引 kotlinx.serialization / coroutines:少一个需要与 HarmonyOS Kotlin 对齐的 KN 依赖,C 签名 const char* → const char* 永不改变。

决策 5:ArkUI 作为验收宿主,参数同源是底线

回显字段(tintAlpha、blurRadius、progressive)必须与上游表格逐位一致;「设备上看起来对」不构成验收,jvmTest 钉值 + 状态行对账才构成。

六、测试与验证

6.1 测试环境

本次验证使用 macOS、JDK 21(Temurin)、HarmonyOS Kotlin 2.2.21-1.0.0、Gradle 8.14.1、DevEco Studio 5.1.0 Release 与鸿蒙真机(arm64-v8a,签名安装)。

6.2 共享样例测试

cd example && ./gradlew :nativeApp:jvmTest

23 项测试分三类全部通过:

  • 模型钉值(8 项):默认 20dp/0.15、材质十档 alpha、24dp 材质半径、亮度白 1.0/黑 0.0/中灰 0.2159、EaseIn 端点与单调、版本约定;
  • 管线性质(5 项):常数图模糊不变、脉冲扩散对称、零半径原样拷贝、PNG 签名与 IHDR 尺寸、噪点确定性;
  • 协议回显(10 项):默认请求回显 20.0/0.15/none、材质回显 24.0 与亮度选档、显式 tint 优先、alpha=0 输出与原始场景 PNG 逐字节一致、画布钳制、非法色值转 ok:false。

6.3 HAP 验证

CompileArkTS: finished
PackageHap: finished
SignHap: finished
BUILD SUCCESSFUL
install bundle successfully
start ability successfully

6.4 功能用例

  1. 启动后版本握手行显示 桥 2.0.1-ohos.1 · 上游 2.0.1 · Kotlin 2.2.21-1.0.0 · haze-kn-rasterizer,并自动渲染默认帧;
  2. 毛玻璃材质 Tab:五档材质 tintα 依次 0.35/0.60/0.73/0.83/0.92,开「深色容器」跳到 0.55/0.65/0.80/0.90/0.97;
  3. 渐进模糊 Tab:纵向线性顶部清晰底部最糊(EaseIn 中段偏清晰),径向中心/边缘强度对调后效果反转;
  4. 自定义参数 Tab:关 blurEnabled 模糊消失 tint 仍生效;alpha 拉到 0 整个效果淡出;蒙版切圆形变椭圆窗;
  5. 三个场景(湖景/卡片/渐变)在每个 Tab 切换一遍,卡片场景高频边缘最能暴露模糊质量;
  6. 任意参数组合下状态行不出现 失败:,Native 异常显示在错误区域。

6.5 验证结论

Native 动态库(双 ABI + libc++)、C ABI、N-API、ArkUI 页面和签名 HAP 已完成链路验证。应用在鸿蒙真机上成功安装并进入前台,页面展示了真实 Native 渲染的毛玻璃材质、渐进模糊与自定义参数效果,状态行回显与上游参数表逐位一致。

七、运行效果

7.1 真机截图

在这里插入图片描述

7.2 命令速查

git clone https://atomgit.com/oh-tpc/haze.git
cd haze
export JAVA_HOME="/path/to/jdk-21"
cd example
./gradlew :nativeApp:jvmTest \
  :nativeApp:linkDebugSharedOhosArm64 \
  :nativeApp:linkDebugSharedOhosX64

# DevEco Studio 打开 example/harmonyApp,自动签名后:
hvigorw assembleHap --mode module -p product=default -p buildMode=debug --no-daemon
hdc -t <设备序列号> install -r \
  example/harmonyApp/entry/build/default/outputs/default/entry-default-signed.hap
hdc -t <设备序列号> shell aa start \
  -a EntryAbility -b org.terminator.ohos.haze -m entry

八、遗留问题与改进方向

8.1 踩坑复盘

  1. 只改 ArkTS 页面不算 KMP 适配,必须真正编译 ohosArm64/ohosX64 动态库;
  2. N-API 不负责业务判断,C++ 只做类型检查、字符串转换和释放;
  3. Native 返回值交给 N-API 后必须调用 DisposeString;
  4. 回显 alpha 要用解析后的浮点值,不能取 8-bit 量化值,否则和上游表格对不上账;
  5. 签名工程必须与源码分离,证书不能进入 AtomGit;
  6. 模拟器是 x86_64,只编 arm64 会报安装错误 9568347,双 ABI 都要出。

8.2 已知问题

  • 软件模糊是高斯的逼近,大半径下与 Skia/RenderEffect 存在可见但不影响语义演示的差异,性能非 GPU 量级;
  • 共享库拿不到 ArkUI 真实内容,hazeSource 的「捕获任意 UI」在 ohos 桥里暂无对应物,用程序化场景替代;
  • HazeProgressive.Brush(位图蒙版)与 HazeColorEffect.colorFilter 未透出,扁平 JSON 暂无合理表达;
  • 噪点为确定性哈希抖动的逼近实现,不做逐平台一致性承诺;
  • HAP 安装必须使用与 bundle name 匹配的签名。

8.3 未来优化方向

  • 增加 pixelData 字段接入真实图片(相册 PixelMap → N-API 指针),协议加字段不动 C 签名;
  • 透出 HazeProgressive.Brush 与 colorFilter;
  • 等社区 CMP 1.12+ / compose.ui ohos 制品出现,评估用上游 haze 模块直接编 ohos,内核退役为对照实现;
  • 在 CI 中加入 Native 链接、ELF 检查和 HAP 构建;
  • 补充不同设备 ABI、API 版本与性能模式的验收矩阵。

九、总结

9.1 核心难点回顾

Haze 2.x 参数模型(commonMain 语义)
    → 纯 Kotlin 复刻 + JVM 钉值
    → ohosArm64/ohosX64 Kotlin/Native
    → C ABI(HazeRender/HazeVersion)
    → C++ N-API(libhaze_napi.so)
    → ArkTS JSON 解析 + PixelMap
    → ArkUI 真机毛玻璃工作台

9.2 封装层次

  • KMP 层:提供 Haze 2.x 默认值、材质表、渐进渐变、缓动与亮度的同源语义;
  • Native 层:生成双 ABI 动态库并通过两个 C ABI 符号输出 JSON + PNG;
  • N-API 层:完成参数检查、字符串转换和 Native 内存释放;
  • ArkTS 层:管理三 Tab 状态、请求组装、结果展示和错误文本;
  • DevEco 层:完成 CMake、HAP、签名、安装和真机运行。

9.3 三条经验

  1. 先让参数模型在 JVM 与 ohos 双侧编译通过并钉值,再接 ArkUI;回显字段与上游表格逐位一致是验收底线;
  2. Compose 重度库的可复用资产往往是「取值语义」而不是某个文件——复刻 + 出处注释 + 测试钉值,好过硬接工具链;
  3. 用扁平 JSON 和少量 C ABI 代替跨语言对象传递,并明确字符串释放责任;分别记录自动测试、Native 链接、HAP 构建和真机运行,避免把编译成功误认为设备链路可用。

9.4 适配成果

当前适配已完成:

  • example/ 独立 OpenHarmony Gradle 构建(上游根构建零改动);
  • Haze 2.x 参数模型内核的双 ABI Kotlin/Native 编译(libohoshaze.so arm64/x64 + 匹配 libc++);
  • 两个 C ABI 符号和 C++ N-API 包装(libhaze_napi.so);
  • DevEco Stage ArkUI 三 Tab 验收页面;
  • 23 项 JVM 交叉验证测试全部通过;
  • HAP 构建、签名、真机安装与运行验证;
  • AtomGit 项目文档(README.OpenHarmony.md / README.OpenHarmony_CN.md)与真机效果图。

适配过程 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开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐