开源鸿蒙平台 CMP 三方库「ComposeTheme」适配全流程
本文记录
ComposeTheme接入 OpenHarmony 的完整过程,覆盖设计系统 API 库盘点、鸿蒙 Compose 制品线选型、真实引擎编译、ohosArm64/ohosX64目标、Kotlin/Native 动态库、C ABI/N-API 桥接、ArkUI 真机页面、HAP 构建和真机验收。
ComposeTheme是 Compose Multiplatform 的"设计系统 API"库(上游 composablehorizons/ComposeTheme,MIT):buildComposeTheme {}返回一个主题@Composable,ComposeTheme对象在任意后代里按DesignToken取值,ThemeBuilderDSL 支持colors/textStyles/shapes三组默认属性(10 色系 × 10 明度共 100 个颜色令牌、9 档文字样式、6 档形状)、任意类型的自定义DesignProperty、extend {}包装既有设计系统,以及composetheme-material3/composetheme-material两个桥模块(extendMaterial3/extendMaterial把 Material 3 / Material 2 的 colorScheme、typography、shapes 映射进同一套令牌体系)。本次适配与社区早期路线(qrose / haze / PreCompose / voyager 的"零依赖内核提取")有本质区别:社区 Maven 上已经出现完整的 HarmonyOS Compose 制品线(org.jetbrains.compose.*1.12.0-0.1.0-07,runtime / ui / foundation / material / material3 / animation 全部带ohosArm64/ohosX64klib 变体),而本库上游基于 Compose 1.8.1 构建、全库零 expect/actual、零平台源码集,因此不需要提取内核也不需要剥离 Compose——三个模块的全部commonMain(buildComposeTheme主题解析、ThemeBuilderDSL、默认令牌表、DefaultDebugIndication、Material 2 / Material 3 桥)被原样编译成 OpenHarmony KLIB。OHOS 目前没有 Compose composition 宿主,@Composable 取值访问器无法在 ArkTS 侧挂载,适配按照 compose-shimmer 先例为库内可无头驱动的真实对象(ThemeBuilder、令牌合并、ResolvedTheme 属性存储、M2/M3 桥)开出 JSON 门面,ArkUI 页面作为验收宿主。
项目地址: AtomGit/oh-tpc/ComposeTheme.git
上游仓库: composablehorizons/composetheme
开发工具: 华为云码道
环境安装: 环境安装指南
一、背景
1.1 为什么做 OpenHarmony 平台 KMP/CMP 适配
ComposeTheme 为 Compose Multiplatform 应用提供"设计系统 API"这一件事的完整答案:buildComposeTheme { } 一行建主题,返回的 @Composable 包住 App 后,任意后代用 ComposeTheme.colors.red500 / ComposeTheme.textStyles.base / ComposeTheme.shapes.round 这样的扩展属性取默认令牌,用 ComposeTheme.colors[token] / ComposeTheme[property] 取自定义令牌与设计属性,extend { } 挂接任意既有设计系统,extendMaterial3 { colorScheme = lightColorScheme(primary = Color.Red) } 一段配置把 Material 3 的整套 colorScheme / typography / shapes 收编进同一套 API。KMP 项目把这些能力放在 commonMain,由 Android、iOS、桌面、Web 共享。OpenHarmony 应用同样需要这套主题基础设施,但上游构建(Compose 1.8.1 / Kotlin 2.1.21 / AGP 8.2 / Gradle 8.7)不会生成 OpenHarmony 变体。
如果只把示例页面改成 ArkTS,页面可以显示色块,却无法证明公共主题引擎在 OpenHarmony ARM64 设备上完成编译、链接和执行。适配需要同时验证主题解析引擎、Compose 运行时、Native 动态库、C ABI、N-API、ArkUI 页面和 HAP 交付链路。
| 障碍 | 具体问题 |
|---|---|
| 目标缺失 | 必须增加 ohosArm64() / ohosX64() 才能生成 OpenHarmony KLIB。 |
| 工具链不一致 | 上游 Compose 1.8.1 / Kotlin 2.1.21 / AGP 8.2(androidTarget + ios framework + js/wasmJs)与鸿蒙工具链无法共存于一个 Gradle 构建;根构建还有 Sonatype 发布与 dokka。 |
| 依赖矩阵 | 库依赖 compose.foundation,桥模块依赖 compose.material / compose.material3,每一个都要有 ohos klib 变体才能解析。 |
| 语言边界不同 | ArkTS 不能直接持有 Kotlin 对象,需要 C ABI、C++ N-API 和 JSON。 |
| @Composable 跨不了桥 | ComposeTheme.colors[token] 等取值入口都是 @Composable,OHOS 没有 composition 宿主,无法在 ArkTS 侧直接调用。 |
| 交付链路复杂 | .so、CMake、N-API、HAP、签名和 hdc 都要单独验收。 |
因此,本项目把边界放在:三个库模块全部 commonMain 的 ohos 编译、根构建替换(cmp_qrose 模式)和无头门面 + C ABI/N-API 桥接层。令牌语义完全由上游原版实现负责,ArkTS 只负责调用 Native、解析 JSON 和展示结果。
1.2 库提供的能力
Demo 提供一个真实的主题解析会话,对应桥接的全部入口:
- 默认令牌快照(defaults):读取真实
DefaultComposeTheme——100 个颜色令牌(10 色系 × 10 明度)、9 档文字样式(xs 12sp 到 xl5 48sp)、6 档形状(round 4dp 到 full 100%)的计数与抽样值; - DSL 驱动(builder):真实构造
ThemeBuilder,设置名称、经真实DefaultDebugIndication工厂创建指示器节点、用真实DesignTokens工厂装自定义令牌、注册自定义DesignProperty,并调用真实buildComposeTheme工厂入口; - 合并语义(merge):执行
buildComposeTheme内部那三行DesignTokens(name, builder.X.entries + DefaultComposeTheme.X.entries)合并表达式——上游 README 的 WARNING(默认令牌无法被覆盖)在设备上肉眼可验证:覆盖red500解析回默认值#FFFF4C4C,自定义primary正常通过;自定义属性经真实ResolvedTheme.getProperty属性存储回读; - 错误路径(missing):复现
DesignTokens.get@Composable 访问器抛出的缺失令牌报错原文(含主题名); - Material 桥(extend3 / extend):真实执行
extendMaterial3/extendMaterial(两者本身是普通函数,无需 composition),读回映射后的 M3 五档形状(4/8/12/16/28dp)、真实lightColorScheme色板、Typography()的 bodyLarge/titleLarge/labelSmall,以及 M2 的十二色令牌与 small/medium/large 形状; - 版本握手(version):
themeVersion()返回 fork 版本号,来自真实 Native 调用而非静态文字。
响应包含:
| 字段 | 作用 |
|---|---|
ok | 操作是否成功,失败时 error 带原文。 |
op | 回显本次操作。 |
| 各 op 专属字段 | defaults 的计数与抽样、builder 的指示器与属性、merge 的 resolved/missing、extend3 / extend 的色板与形状等。 |
引擎侧完整能力(buildComposeTheme 返回的 composable、全部 @Composable 扩展属性、MutableThemeProperties、DefaultDebugIndication 的 Modifier.Node 实现、M2/M3 桥注册的 MaterialTheme 包装)全部在上游 commonMain 里原样随编译进入 .so,JVM 侧保留三个模块对上游制品线(compose 1.8.1)的编译交叉验证,证明 ohos .so 里执行的解析逻辑没有漂移。
1.3 实现适配
| 维度 | 要求 |
|---|---|
| 代码复用 | composetheme / composetheme-material / composetheme-material3 的全部 commonMain 文件原样编译,不提取、不重写、零 expect/actual 需要补;新增源码只有三个 ohosMain 无头门面。 |
| 平台目标 | 根构建直接声明 ohosArm64 + ohosX64,保留 jvm 作交叉验证,库模块出 KLIB,example/nativeApp 出双 ABI 动态库。 |
| 构建隔离 | 根构建替换为极简 ohos 构建(cmp_qrose 模式);samples(desktop 专用样例)留盘不进构建。 |
| 桥接稳定 | 使用 2 个 C ABI 和 UTF-8 JSON,不传递 Kotlin 对象地址;异常永远不跨 FFI。 |
| 依赖纪律 | jvm 交叉验证线版本主体与上游完全一致(compose 1.8.1),ohos 线走社区 Compose 制品线;上游源码零改动。 |
| UI 完整 | 四 Tab 覆盖全部桥接入口:令牌 / 构建合并 / Material / 关于。 |
| 仓库规范 | 项目说明、文章、效果图和源码链接统一使用 AtomGit。 |
本项目的 ArkUI 页面不是 Compose Multiplatform UI 实现。OHOS 尚无 Compose composition 宿主,@Composable API 已编入 so 但无挂载点;ArkTS Demo 把桥暴露的主题解析闭环画出来,数值全部来自 Kotlin 侧真实对象。
二、实现路线图
第 1 阶段:项目初始化 ── 盘点模块与依赖边界、确认社区 Compose ohos 制品线、确定交付物
第 2 阶段:目标与依赖打通 ── 根构建替换、eazytec Maven 置前、Kotlin 2.4.20-0.1.0-07 钉版、分线依赖
第 3 阶段:桥协议建模 ── 7 个 op 的 JSON 契约、key=value 列表字段、错误通道
第 4 阶段:原生桥接 ── @CName C ABI、C++ N-API、参数校验和内存释放
第 5 阶段:ArkUI 宿主 ── 四 Tab 页面、真机数值呈现、错误边界
第 6 阶段:示例与验证 ── HAP、签名、设备安装、真机运行和效果图
三、逐步实现过程
第 1 阶段:项目初始化
1.1 工程边界
composetheme/ 上游核心引擎(进 ohos 构建,jvm + ohosArm64 + ohosX64)
composetheme/src/ohosMain/ 新增:ComposeThemeHeadless 门面 + 跨模块辅助函数
composetheme-material/ 上游 Material 2 桥(进 ohos 构建,ohosMain 加门面)
composetheme-material3/ 上游 Material 3 桥(进 ohos 构建,ohosMain 加门面)
samples/ mkdocs.yml scripts/ art/ 上游 desktop 样例与文档站,留盘不进 settings
example/nativeApp/ Kotlin/Native 动态库(Bridge + MiniJson)
example/harmonyApp/ DevEco Stage、CMake N-API 和 ArkTS 页面
README.OpenHarmony.md / _CN.md 鸿蒙适配说明(中英双份)
README.upstream.md 上游原文保存
构建拓扑走 cmp_qrose 模式(根构建即 ohos 构建):settings.gradle.kts 只 include :composetheme、:composetheme-material、:composetheme-material3 与 :example:nativeApp,rootProject.name = "ohos_composetheme"。上游的 AGP / dokka / nexus 发布插件、androidTarget、js/wasmJs、iOS framework 全部随根构建替换剥离——本 fork 不发 Maven Central。上游构建文件原样保存为 *.upstream(settings / 根与模块 build / catalog / properties / wrapper 七份)。
1.2 工具链矩阵
| 项目 | 配置 |
|---|---|
| Kotlin Multiplatform | HarmonyOS Kotlin 2.4.20-0.1.0-07(eazytec Maven) |
| JDK | 21(org.gradle.java.home 钉死本机路径) |
| OpenHarmony 目标 | ohosArm64 + ohosX64(jvm 保留作交叉验证) |
| Gradle | 9.6.1(上游 wrapper 8.7 升级;2.4.20 线支持 Gradle 9.x,社区先例 compose-shimmer / KStore 同款) |
| Compose 制品线 | 社区 org.jetbrains.compose.* 1.12.0-0.1.0-07(ohos klib:runtime / ui / foundation / material / material3 / animation) |
| jvm 交叉验证线 | 上游自洽线 compose 1.8.1(Maven Central,即上游 1.4.0 实际编译所用的版本体) |
| DevEco product | 5.1.0(18) 兼容 |
| DevEco Studio | 5.1.0 Release |
| ABI | arm64-v8a + x86_64 |
| Bundle | org.terminator.ohos.composetheme |
export JAVA_HOME="/path/to/jdk-21"
java -version
1.3 源码依赖度盘点
| 模块 | commonMain 文件 | 依赖形态 | 处置 |
|---|---|---|---|
composetheme | 3 | compose.foundation(runtime/ui 传递),零平台 API | 整体编译(jvm + 双 ohos) |
composetheme-material | 1 | foundation + compose.material + project(:composetheme) | 整体编译(零平台源码集) |
composetheme-material3 | 1 | foundation + compose.material3 + project(:composetheme) | 整体编译(零平台源码集) |
samples | 7(desktopMain) | compose.desktop.currentOs 等 *-desktop 子制品 | 留盘不进构建 |
expect 声明全库零处:三个模块只有 commonMain,没有 androidMain / iosMain / jvmMain,没有 import android.* / import platform.*,唯一的非 Compose 依赖是 kotlinx.coroutines.launch(经 foundation 传递、common 通用 API)。这意味着 ohos 侧不需要补任何 actual——适配面从"补平台缝"变成"给可无头驱动的内部对象开门面"。唯一的新增源码是三个 ohosMain 门面(见第 3 阶段)。
值得单独说明的是取值入口的形态:DesignTokens.get / ComposeTheme.get 都是 @Composable @ReadOnlyComposable,但它们底层调用的对象全部可以在 composition 外构造和驱动——ThemeBuilder(DSL 状态)、DesignTokens(令牌映射)、DefaultComposeTheme(默认表)、ResolvedTheme.getProperty(ComposeTheme.get 实际调用的非 composable 属性存储)、DefaultDebugIndication(真实 IndicationNodeFactory,create(MutableInteractionSource()) 不需要 composition)、extendMaterial3 / extendMaterial(本来就是普通函数)。这是无头门面路线成立的前提。
1.4 页面边界
Tab 切换 令牌 / 构建合并 / Material / 关于
令牌 Tab 真实 DefaultComposeTheme 快照:三组计数、6 个抽样色块、9 档文字样式表、6 档形状预览
构建合并 Tab 主题名 / 自定义 token / token 值输入区;builder op 结果行;
merge op 结果(resolved 来源标注 + 被默认覆盖的尝试值 + missing);
缺失 token 报错文案按钮与展示
Material Tab M3:primary 输入 + 六色板 + 五档形状 + 三个文字样式;M2:五色板 + 三档形状 + body1/h1
关于 Tab 版本握手(真实 Native 调用)、工具链表、调用链分层、已知限制、仓库与社区链接
页面启动时先调 themeVersion() 做握手,再依次拉取 defaults / builder / merge / extend3 / extend 快照填充各 Tab。
第 2 阶段:目标与依赖打通
2.1 制品线验证(本适配的成败手)
先查社区 Maven(https://maven.eazytec-cloud.com)上有没有 ohos 的 Compose 制品。用 .module 元数据逐个验证:
org.jetbrains.compose.foundation:foundation:1.12.0-0.1.0-07
variants: ohosArm64ApiElements-published / ohosArm64SourcesElements-published / ohosX64…
org.jetbrains.compose.material3:material3:1.12.0-0.1.0-07
variants: 同上,依赖闭包含 material / foundation / runtime / ui / animation
org.jetbrains.compose.material:material:1.12.0-0.1.0-07
variants: 同上
三个关键结论:其一,库需要的四件(foundation / material / material3 + 传递的 runtime / ui)在社区线上全部存在 ohosArm64 / ohosX64 变体——这是纯 Compose UI 库能走"真编译整库"路线的前提,qrose 时代(上游 Compose 1.12 / Kotlin 2.4 与社区线对不齐)只能把 Painter 剥进不编译的 composeMain;其二,社区线不发布 *-desktop 子制品,jvm 交叉验证目标不能从这条线解析,必须像 compose-shimmer / eungabi / AdaptiveStack 一样按目标分线;其三,上游 1.4.0 基于 Compose 1.8.1 构建,社区线版本体 1.12 更新,但库只用 foundation/material[3] 的稳定 API(IndicationNodeFactory、Modifier.Node、staticCompositionLocalOf 等),两线编译都绿即 API 面兼容的证据。
2.2 工具链钉版(为什么是 2.4.20-0.1.0-07)
社区此前批准的钉法是 HarmonyOS Kotlin 2.2.21-1.0.0(qrose / haze 用的)。但社区 Compose 线 1.12.0-0.1.0-07 的依赖闭包钉在 stdlib 2.3.20 / Kotlin 工具链 2.4.20-0.1.0-07(material3 的 .module 里 requires 字段可见),旧线读不了新元数据。工具链版本跟着依赖走:Kotlin 钉 2.4.20-0.1.0-07。额外好处:上游源码本来就是 Kotlin 2.1.21 编的,2.4.20 向后兼容;Gradle 从上游 8.7 升到 9.6.1(compose-shimmer / KStore 先例,2.4.20 线支持 Gradle 9.x),不像 2.2.21 旧线必须降 8.14.1。
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_composetheme"
include(":composetheme")
include(":composetheme-material")
include(":composetheme-material3")
include(":example:nativeApp")
2.3 编译真实引擎
composetheme/build.gradle.kts 收敛为纯 KMP 模块,Compose 依赖按目标线声明:
plugins {
alias(libs.plugins.kotlin.multiplatform)
alias(libs.plugins.kotlin.compose.compiler) // commonMain 有 @Composable,必须挂编译器插件
}
kotlin {
applyDefaultHierarchyTemplate()
jvm { compilerOptions { jvmTarget.set(JvmTarget.JVM_21) } } // 交叉验证线
ohosArm64() // the adaptation
ohosX64()
sourceSets {
ohosMain.dependencies {
implementation(libs.compose.runtime) // 社区线 1.12.0-0.1.0-07
implementation(libs.compose.foundation)
}
jvmMain.dependencies {
implementation(libs.compose.runtime.jvm) // 上游线 1.8.1
implementation(libs.compose.foundation.jvm)
}
}
}
composetheme-material / composetheme-material3 同构,分别加 compose.material / compose.material3 与 project(":composetheme")。依赖放 ohosMain / jvmMain 而不是 commonMain,是因为同一坐标在两条线上版本不同——“线切换,不是版本切换”。实测:
./gradlew :composetheme:compileKotlinOhosArm64 :composetheme:compileKotlinOhosX64 # BUILD SUCCESSFUL
./gradlew :composetheme-material3:compileKotlinOhosArm64 :composetheme-material:compileKotlinOhosArm64 # BUILD SUCCESSFUL
三个模块的全部 commonMain 文件一个没动。调试期踩过的三个编译错误都出在新增的 ohosMain 门面上(社区线 foundation 没有 shape.corner 子包、跨模块构造 internal 类、顶层函数引用 object 嵌套类),处置见 8.1。
2.4 样例与测试的处置:samples 需要 *-desktop 制品
上游没有 commonTest(样例在 :samples,desktop-only),交叉验证采用编译级:三个模块的 commonMain(上游原样)对上游制品线执行 compileKotlinJvm。:samples 需要 compose.desktop.currentOs(*-desktop 子制品),社区 ohos 线不发布,留盘不进 settings;根构建同时全局禁用 ohos 测试任务(本库无测试,防御性保留社区纪律):
subprojects {
tasks.matching { it.name.contains("Ohos", ignoreCase = true) && it.name.contains("Test") }
.configureEach { enabled = false }
}
第 3 阶段:桥协议建模
3.1 JSON 契约
com.composables.composetheme/**(上游引擎 + M2/M3 桥,原样编译)
↓ ThemeBuilder / DesignTokens 合并 / ResolvedTheme / DefaultDebugIndication
门面层(ComposeThemeHeadless / ExtendMaterial3Headless / ExtendMaterialHeadless)
↓ 无头快照
Bridge.kt(@CName × 2,扁平 JSON 进出)
↓ UTF-8 JSON
N-API themeVersion() / themeDispatch()
↓
ArkUI Index.ets(四 Tab)
请求与响应:
请求 : {"op":"version"}
{"op":"defaults"}
{"op":"builder","name":"LightTheme","overrides":"primary=FF42A5F5","props":"transitions=fast_200|faster_300"}
{"op":"merge","overrides":"red500=FF00C853,primary=FF42A5F5","textStyles":"customBody=18:26",
"shapes":"customRound=22","req":"red500,primary,nosuchtoken","propName":"transitions"}
{"op":"missing","token":"primary","theme":"DarkTheme"}
{"op":"extend3","primary":"FF6750A4"}
{"op":"extend"}
响应 : {"ok":true,"op":"...","<op 专属字段>"}
{"ok":false,"op":"...","error":"..."}
一个关键设计:列表型参数走逗号分隔的 key=value 列表字段(overrides / textStyles / shapes / props),而不是 ov.primary 这类点号前缀键——ArkTS 严格模式禁止对象字面量使用非标识符属性名(arkts-identifiers-as-prop-names),也禁止无接口对应的字面量(arkts-no-untyped-obj-literals),列表字段让请求对象能完整接口化(DispatchRequest),Kotlin 侧 pairs() 一行解析。
3.2 合并语义为什么是门面的核心
buildComposeTheme 返回的 composable 体内做三件事:构造 ThemeBuilder 并执行 builder lambda;用 DesignTokens(name, builder.X.entries + DefaultComposeTheme.X.entries) 把三组令牌与默认表合并(Kotlin 的 Map + Map,右侧默认值覆盖左侧 builder 值);把合并结果连同 builder 的自定义属性装进 ResolvedTheme,经 CompositionLocalProvider 提供并按需 extend 包装。前两步不需要 composition——门面用同一批真实对象执行同样的表达式,ArkTS 拿到的 resolved 值与将来 composition 宿主挂上后 ComposeTheme.colors[token] 读到的完全一致。这也让上游 README 的 WARNING 第一次可以在设备上直接验证:
{"name":"red500","argb":"#FFFF4C4C","source":"default","attemptedArgb":"#FF00C853"}
{"name":"primary","argb":"#FF42A5F5","source":"custom","attemptedArgb":"#FF42A5F5"}
source=default 证明 builder 对默认令牌的覆盖被合并丢弃(默认值 #FFFF4C4C 原样胜出),source=custom 证明新增令牌正常通过——两个 JSON 行就是上游合并语义的真机证据。自定义属性则经真实 ResolvedTheme.getProperty(ComposeTheme.get 的非 composable 底层)回读,验证属性存储链路。
suspend 与重组在门面里都不存在:令牌解析是纯同步数据操作,桥全程无协程、无帧时钟——这是它比动画类库(compose-shimmer 需要 HeadlessFrameClock)简单的地方。
3.3 错误通道
每个导出整体 try/catch,Kotlin 异常永远不跨 FFI:
@CName("ThemeDispatch")
fun themeDispatch(request: String): String = try {
/* op 分发 */
} catch (t: Throwable) {
"""{"ok":false,"op":"dispatch","error":${jsonString(t.message ?: t.toString())}}"""
}
非法 JSON、不支持的 op、颜色 hex 格式错误,全部走同一个 ok:false 通道,ArkTS 侧统一渲染。缺失令牌的报错文案由门面按 DesignTokens.get 的原文复现(该异常只在 composition 内抛出):
Tried to access the value of the token called primary, but no tokens with that name are
defined within the colors property. You probably forgot to set a primary token in your
theme definition. The resolved theme was DarkTheme
第 4 阶段:原生桥接(技术难点)
4.1 三层桥接
ArkTS
│ JSON string
▼
C++ N-API entry(libcomposetheme_napi.so)
│ const char*
▼
Kotlin/Native C ABI(libohoscomposetheme.so)
│ ThemeDispatch / ThemeVersion
▼
UTF-8 JSON + DisposeString
直接导出 Kotlin 对象会暴露 ABI 和生命周期问题;只导出整数又会让页面复制合并语义,所以选择 C ABI + JSON。
4.2 Kotlin/Native 导出函数
@CName("ThemeDispatch")
fun themeDispatch(request: String): String // 7 个 op 一口进
@CName("ThemeVersion")
fun themeVersion(): String = "1.4.0-ohos.1" // 版本握手
| C ABI 符号 | ArkTS 方法 | 覆盖内核 |
|---|---|---|
ThemeDispatch | themeDispatch(req) | defaults 快照 + ThemeBuilder DSL + 令牌合并 + ResolvedTheme 回读 + M2/M3 桥 |
ThemeVersion | themeVersion() | fork 版本握手(真机版本行的数据来源) |
4.3 C++ N-API 方法分发
napi_property_descriptor desc[] = {
{"themeDispatch", nullptr, NapiThemeDispatch, nullptr, nullptr, nullptr,
static_cast<napi_property_attributes>(napi_default | napi_enumerable), nullptr},
{"themeVersion", nullptr, NapiThemeVersion, nullptr, nullptr, nullptr,
static_cast<napi_property_attributes>(napi_default | napi_enumerable), nullptr},
};
每个回调统一三步:napi_typeof 校验必须是 string、OH_LOG_INFO 打字节数、调 KN 函数并包装返回。KN 返回的字符串必须经 libohoscomposetheme_symbols()->DisposeString 归还 Kotlin 运行时,绝不 free()。libohoscomposetheme_api.h 按社区已审仓库同款手写(只声明 ExportedSymbols 结构与两个业务符号),不拖整份生成头。
4.4 CMake 和类型声明
add_library(ohoscomposetheme SHARED IMPORTED)
set_target_properties(ohoscomposetheme PROPERTIES
IMPORTED_LOCATION ${KN_SO}
IMPORTED_NO_SONAME TRUE)
add_library(composetheme_napi SHARED napi_init.cpp)
target_link_libraries(composetheme_napi PUBLIC
libace_napi.z.so
libhilog_ndk.z.so
ohoscomposetheme)
OHOS_ARCH 为空或 libohoscomposetheme.so 缺失直接 FATAL_ERROR(提示先跑 link 任务);$ORIGIN rpath 加 POST_BUILD 拷贝保证 NAPI so、KN so、libc++ 三件套同目录,dlopen 时互相可见。
export const themeDispatch: (request: string) => string;
export const themeVersion: () => string;
第 5 阶段:ArkUI 宿主
5.1 启动握手与快照拉取
aboutToAppear(): void {
this.versionText = themeVersion(); // 版本握手:来自真实 Native 调用
this.refreshDefaults(); // 令牌 Tab 数据
this.runBuilder(); // 构建合并 Tab:builder op
this.runMerge(); // 构建合并 Tab:merge op(覆盖 red500 + 自定义 primary)
this.refreshMaterial(); // Material Tab:extend3 + extend
}
private dispatch(req: DispatchRequest, onOk: (res: DispatchResponse) => void): boolean {
const res = JSON.parse(themeDispatch(JSON.stringify(req))) as DispatchResponse;
if (!res.ok) { this.lastError = `op=${res.op ?? '?'} ${res.error ?? 'unknown error'}`; return false; }
onOk(res);
return true;
}
merge 的请求把"尝试覆盖默认 red500 + 新增自定义 primary + 一个不存在的 token"三件事一次发给引擎,响应的 resolved 数组按 source / attemptedArgb 标注来源与被丢弃的尝试值。
5.2 页面交互
令牌 Tab 自上而下:三组令牌计数行(100 / 9 / 6)、6 个抽样色块(名称 + #AARRGGBB 文本,底色即该色值)、9 档文字样式表(名称 + 字号sp / 行高sp,按字号升序)、6 档形状(34vp 蓝色方块 + 实测圆角预览,full 显示 100%)。构建合并 Tab:主题名 / 自定义 token 名 / token 值 / red500 覆盖值四个输入框 + 两个触发按钮,builder 结果区展示指示器工厂名、真实创建的节点类名、buildComposeTheme 工厂已调用、属性注册表;merge 结果区逐条显示 resolved(来源标注)与被默认覆盖的尝试值,missing 单独成行;"模拟读取缺失 token"按钮展开真实报错文案。Material Tab:primary 十六进制输入 + 重新求值按钮,M3 六色板 / 五档形状 / 三个文字样式、M2 五色板 / 三档形状 / body1/h1 字号。关于 Tab:版本握手行、工具链表、ArkTS → NAPI → C ABI → Kotlin 引擎 → 社区 Compose 分层说明与仓库 / 社区链接。
5.3 对比度和错误边界
M3 / M2 色板行用色值本身作底色、按明度自动切换黑白前景文字;ok:false 时错误原文进页面顶部红色横幅(ErrorBanner)。Native 调用本身抛错(理论不该发生)同样被 try/catch 收进横幅。
第 6 阶段:示例与验证
6.1 工程结构
example/
├── nativeApp/
│ ├── build.gradle.kts 双 ABI sharedLib(baseName=ohoscomposetheme)+ cxxShared + copyKnSo*
│ └── src/ohosMain/kotlin/example/nativeapp/
│ └── Bridge.kt @CName × 2 + MiniJson(扁平 JSON,继承自 cmp_qrose 审核模式)
└── harmonyApp/
├── build-profile.json5 signingConfigs 留空(签名由 DevEco 自动生成回填)
├── AppScope/app.json5 bundleName org.terminator.ohos.composetheme、versionName 1.4.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/libohoscomposetheme_api.h
├── src/main/cpp/types/libcomposetheme_napi/{index.d.ts, oh-package.json5}
└── src/main/ets/pages/Index.ets ArkUI 四 Tab 验收页面
6.2 构建、签名和安装
./gradlew :composetheme:compileKotlinJvm :composetheme-material:compileKotlinJvm \
:composetheme-material3:compileKotlinJvm # 交叉验证
./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 保持空数组)。
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.composetheme -m entry
四、完整代码对照
4.1 整体架构
com.composables.composetheme/**(上游引擎原样编译)
│ buildComposeTheme / ThemeBuilder / DesignTokens / DefaultComposeTheme / ResolvedTheme
com.composables.composetheme.material[3]/**(上游 M2/M3 桥原样编译)
│ extendMaterial / extendMaterial3 + ComposeTheme.colorScheme 等扩展
ohosMain 门面 × 3
│ ComposeThemeHeadless / ExtendMaterial3Headless / ExtendMaterialHeadless
Bridge.kt + MiniJson
│ ThemeDispatch / ThemeVersion
libcomposetheme_napi.so
│ themeDispatch / themeVersion
Index.ets
│ JSON parse + @State 更新
▼
ArkUI 真机页面(四 Tab)
4.2 文件清单
| 文件 | 职责 |
|---|---|
settings.gradle.kts | 极简 ohos 构建、eazytec Maven 置前、ohos_composetheme、samples 移出 |
gradle/libs.versions.toml | Kotlin 2.4.20-0.1.0-07 + 双 Compose 线(ohos 1.12.0-0.1.0-07 / jvm 1.8.1) |
build.gradle.kts(根) | 版本 1.4.0-ohos.1、全局禁用 Ohos*Test* 任务 |
composetheme*/build.gradle.kts × 3 | 纯 KMP:jvm + ohosArm64 + ohosX64,依赖按目标线声明 |
composetheme/src/ohosMain/.../ComposeThemeHeadless.ohos.kt | 核心门面 + 跨模块辅助(headlessThemeBuilder / headlessLookup / registeredPropertyValues) |
composetheme-material3/src/ohosMain/.../ExtendMaterial3Headless.ohos.kt | M3 桥门面 |
composetheme-material/src/ohosMain/.../ExtendMaterialHeadless.ohos.kt | M2 桥门面 |
example/nativeApp/build.gradle.kts | 双 ABI sharedLib、cxxShared 定位 libc++、copyKnSo* 回填 |
nativeApp/.../Bridge.kt | 2 个 C ABI、7 个 op、key=value 列表解析、MiniJson、错误通道 |
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 | 鸿蒙适配主文档(18 节) |
4.3 关键 API 对照
| 层次 | API | 作用 |
|---|---|---|
| 引擎 | buildComposeTheme { } | 主题工厂:builder 执行 + 三组令牌合并 + ResolvedTheme 装配 |
| 引擎 | ThemeBuilder | DSL 状态:name / indication / colors / textStyles / shapes / properties / extend |
| 引擎 | DesignTokens(entries + defaults) | 令牌合并(Map + Map,默认值胜出——上游 WARNING 的来源) |
| 引擎 | ResolvedTheme.getProperty | ComposeTheme[property] 的非 composable 属性存储 |
| 引擎 | DefaultComposeTheme | 100 色 / 9 文字 / 6 形状默认令牌表 |
| 引擎 | DefaultDebugIndication | 默认指示器(IndicationNodeFactory + Modifier.Node) |
| M3 桥 | extendMaterial3 { } | colorScheme / typography / shapes 映射 + MaterialTheme 包装注册 |
| M2 桥 | extendMaterial { } | lightColors / Typography / Shapes 映射(十二色令牌) |
| 门面 | ComposeThemeHeadless 等 × 3 | defaults / builder / merge / missing / extend3 / extend 快照 |
| Native | ThemeDispatch / ThemeVersion | C ABI JSON 导出 |
| N-API | themeDispatch(req) / themeVersion() | 向 ArkTS 暴露能力 |
| ArkTS | dispatch(req, onOk) | 触发操作并刷新 UI |
4.4 ArkTS 与 Kotlin 的边界
const res = JSON.parse(themeDispatch(JSON.stringify({
op: 'merge',
overrides: `red500=FF00C853,${this.customTokenText}=${this.customValueText}`,
textStyles: 'customBody=18:26', shapes: 'customRound=22',
req: `red500,${this.customTokenText},nosuchtoken`,
propName: 'transitions'
})));
// res.resolved[0] = {name:'red500', argb:'#FFFF4C4C', source:'default', attemptedArgb:'#FF00C853'}
// res.resolved[1] = {name:'primary', argb:'#FF42A5F5', source:'custom', ...}
val mergedColors = DesignTokens(
_colors.name,
builder.colors.entries + DefaultComposeTheme.colors.entries, // buildComposeTheme 同款表达式
)
val theme = ResolvedTheme(builder.name, builder.indication, allProperties, builder.themeWrapper)
val propertyValue = theme.getProperty(DesignProperty<String>("transitions"))
两者之间只传输扁平 JSON 字符串,不传输 Kotlin 对象或未校验的动态结构。
五、关键决策说明
决策 1:真编译整库而不是提取零依赖内核
早期仓库抽内核或剥离 Compose(qrose 把 Painter 移进不编译的 composeMain),是因为当时的社区线与上游版本对不齐。本次适配先验证制品线:foundation / material / material3 的 ohos klib 全部存在于社区 Compose 1.12.0-0.1.0-07,且本库上游基于更旧的 Compose 1.8.1、只用稳定 API——障碍已消失,再剥离等于把主题引擎的手脚拆掉。真编译让 buildComposeTheme、令牌合并、属性存储、M2/M3 桥全部是上游原件。
决策 2:工具链升到 Kotlin 2.4.20-0.1.0-07,Gradle 升到 9.6.1
社区 Compose 线的依赖闭包钉在 stdlib 2.3.20 / 工具链 2.4.20,旧钉法 2.2.21 读不了新元数据。Gradle 方面 2.4.20 线支持 9.x(compose-shimmer / KStore 先例),上游 wrapper 8.7 直接升 9.6.1——只有 2.2.21 旧线才必须降 8.14.1,两条工具链线的经验不能互相套用。
决策 3:根构建替换(cmp_qrose 模式),samples 留盘
上游根构建(AGP / compose multiplatform 插件别名 / dokka / nexus 发布 + PGP 签名)对鸿蒙没有保留价值,整个替换为极简 ohos 构建,上游文件保存为 *.upstream。samples 是 desktop-only 样例(compose.desktop.currentOs),社区 ohos 线不发布对应制品,留盘不进 settings.gradle.kts——交叉验证降为三个库模块的 compileKotlinJvm 编译级验证。
决策 4:无头门面而不是运行 composition
OHOS 没有 Compose composition 宿主,理论上可以在 KN so 里手搓 Recomposer + 自定义帧时钟跑一个无 UI 的 composition,但那要把渲染管线的运行时假设(主线程调度、帧时钟)全部带进无头环境,风险高且没有先例。门面路线(compose-shimmer 已审先例)只调用不需要 composition 的真实对象——ThemeBuilder、DesignTokens 合并、ResolvedTheme.getProperty、DefaultDebugIndication 工厂、extendMaterial3——合并语义与 composition 内完全一致(同一批对象、同一批表达式),宿主出现后删除门面即可,commonMain 零影响。
决策 5:编译级交叉验证 + 上游源码零改动
本库上游没有 commonTest,交叉验证的有效形态是"同一份 commonMain、两条制品线都编译通过":jvm 对上游线(compose 1.8.1,Maven Central)证明源码没被适配改动,ohos 对社区线(1.12.0-0.1.0-07)证明新线 API 兼容。上游源码一个字节不动(含那个上游自带的 data class copy 可见性警告),是这套证据链成立的前提。
六、测试与验证
6.1 测试环境
本次验证使用 Windows(x64)、JDK 21、DevEco Studio 5.1.0 Release、OpenHarmony 命令行工具(hvigor / hdc)与鸿蒙真机(arm64)。
6.2 JVM 交叉验证
./gradlew :composetheme:compileKotlinJvm :composetheme-material:compileKotlinJvm \
:composetheme-material3:compileKotlinJvm # BUILD SUCCESSFUL
三个模块的上游 commonMain(ComposeTheme.kt / ComposeTheme.Defaults.kt / DefaultIndication.kt / ExtendMaterial.kt / ExtendMaterial3.kt)对上游制品线编译全绿,零改动(唯一告警是上游自带的 data class copy() 可见性提示,非适配引入)。ohos 侧同一批源码对社区线编译同样全绿——"同一份源码、两条制品线"的证据链闭合。
6.3 HAP 验证
:composetheme:compileKotlinOhosArm64 / OhosX64: BUILD SUCCESSFUL
:composetheme-material / -material3:compileKotlinOhosArm64 / OhosX64: BUILD SUCCESSFUL
linkDebugSharedOhosArm64 / linkDebugSharedOhosX64: BUILD SUCCESSFUL
llvm-nm -D: ThemeDispatch / ThemeVersion / libohoscomposetheme_symbols 均为全局 T 符号
BuildNativeWithCmake: finished(双 ABI 编出 libcomposetheme_napi.so)
CompileArkTS: finished
PackageHap: finished
BUILD SUCCESSFUL
install bundle successfully
start ability successfully
KN so 因全量静态链接 Compose 运行时显著大于存储类库:debugShared 未 strip 时 arm64 约 38.5MB、x86_64 约 37.1MB(KStore 同口径约 14.8MB——差的正是 Compose runtime / foundation / material[3]);每个 ABI 三件套(libcomposetheme_napi.so / libohoscomposetheme.so / libc++_shared.so)由 Copy 任务与 CMake POST_BUILD 双重保证同目录。
6.4 功能用例
- 启动后版本握手行出现
themeVersion() → 1.4.0-ohos.1(来自真实 Native 调用,非静态文字); - 令牌 Tab 计数行为
colors 令牌数 100、textStyles 令牌数 9、shapes 令牌数 6——与上游ComposeTheme.Defaults.kt的令牌定义逐一对账(10 色系 × 10 明度); - 抽样色块
red500 #FFFF4C4C/blue500 #FF6699FF/gray900 #FF474747等与上游默认表常量一致; - 文字样式表 xs 12sp/16sp 到 xl5 48sp/48sp 单调递增,形状表 round 4dp 到 full 100%;
- 构建合并 Tab:
red500行显示来源=default且"builder 尝试值(被默认覆盖) #FF00C853"——上游 WARNING 真机成立;primary行显示来源=custom;nosuchtoken进 missing; - 自定义属性回读行显示
set-via-headless-builder(经真实ResolvedTheme.getProperty); - "模拟读取缺失 token"展开的报错文案含
The resolved theme was DarkTheme(主题名参与报错); - Material Tab:把 primary 改为
FF6750A4重新求值,M3 色板 primary 同步变化、onPrimary 保持白;五档形状 4/8/12/16/28dp、bodyLarge 16sp/24sp;M2 区 primary#FF6200EE、三档形状 4/8/16dp、body1 16sp、h1 96sp; - 每个 Tab 的数据在页面切回时保持(@State 持有真实快照,非静态演示数据)。
6.5 验证结论
主题解析引擎、Compose 运行时、Native 动态库、C ABI、N-API、ArkUI 页面和 HAP 已完成链路验证。应用在鸿蒙真机上成功安装并进入前台,页面展示了真实的主题会话:版本握手、默认令牌快照、DSL 驱动、合并语义(默认不可覆盖 + 自定义通过)、缺失令牌报错与 M2/M3 桥(见下节效果图)。
七、运行效果
7.1 真机截图

7.2 命令速查
git clone https://atomgit.com/oh-tpc/ComposeTheme.git
cd ComposeTheme
export JAVA_HOME="/path/to/jdk-21"
./gradlew :composetheme:compileKotlinOhosArm64 :composetheme-material:compileKotlinOhosArm64 \
:composetheme-material3:compileKotlinOhosArm64 # klib
./gradlew :composetheme:compileKotlinJvm :composetheme-material:compileKotlinJvm \
:composetheme-material3:compileKotlinJvm # 交叉验证
./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.composetheme -m entry
hdc shell hilog | grep ComposeTheme
八、遗留问题与改进方向
8.1 踩坑复盘
- 只改 ArkTS 页面不算 KMP 适配,必须让三个库模块真正编译成
ohosArm64/ohosX64动态库; settings.gradle.kts里 eazytec 双仓必须排在pluginManagement与dependencyResolutionManagement最前,否则配置期直接Unresolved reference: ohosArm64;- 社区线 foundation klib 没有
androidx.compose.foundation.shape.corner子包(DpCornerSize/PercentCornerSize解析不到)——形状数值改用稳定的公共 APIShape.createOutline(Size(100/200), Ltr, Density(1f))求值,dp 角在两种尺寸下半径不变、百分比角随尺寸翻倍,一次调用同时拿到数值与类型; ThemeBuilder构造器是核心模块 internal,M2/M3 门面(跨模块)不能直接构造——核心门面提供公开headlessThemeBuilder()工厂与headlessLookup/registeredPropertyValues等辅助,跨模块驱动同一批真实对象;- 顶层辅助函数引用 object 内嵌套类必须写全限定(
ComposeThemeHeadless.ShapeSnapshot),否则 ohos 编译报 Unresolved; - Kotlin infix 优先级陷阱:
name to x and MASK实际解析为(name to x) and MASK,二元位运算要显式加括号; - Compose 依赖必须按目标线声明(
ohosMain社区线 /jvmMain上游线):社区线不发布*-desktop子制品,坐标放commonMain会让 jvm 目标解析失败——线切换,不是版本切换; - ArkTS 严格模式两条硬规则:对象字面量必须对应显式接口(
arkts-no-untyped-obj-literals),属性名必须是合法标识符(arkts-identifiers-as-prop-names)——ov.primary这类点号键与Record字面量都过不了编译,列表参数改用逗号分隔的key=value列表字段,请求对象全部接口化; - 根构建的
/build/是根锚定模式,盖不住嵌套目录——DevEco 打开工程后生成的example/harmonyApp/.hvigor/、entry/build/、entry/libs/(so 三件套)要单独追加 gitignore,否则混进提交; - HAP 缺当前 ABI 的 so 会报安装失败
9568347,abiFilters必须双 ABI,每个 ABI 三件套缺一不可; - KN 返回字符串必须
DisposeString归还运行时,N-API 层不能free(); - 签名材料由 DevEco 自动写入
build-profile.json5,仓库内保持signingConfigs空数组 + product 引用"default",证书绝不进入 AtomGit。
8.2 已知问题
- OHOS 无 Compose composition 宿主:
buildComposeTheme返回的主题 composable 与全部 @Composable 取值访问器已编入 so 但无 ArkUI 侧挂载点,Demo 经无头门面驱动同样的底层对象; - 缺失令牌的报错文案由门面按 @Composable 访问器原文复现(真实异常只会在 composition 内抛出);
extendMaterial/extendMaterial3注册的MaterialTheme包装 lambda 是核心模块 internal,无头侧不反查——门面以"桥已执行 + 令牌/属性已映射"为验收面;- 上游 1.4.0 的
buildComposeThemebuilder lambda 是 @Composable(1.3→1.4 行为变化),门面绕过该入口直接驱动ThemeBuilder,合并语义与之一致; - android / js / wasmJs / iOS 目标与 Sonatype 发布、dokka 一并剥离(源文件留在树里),本 fork 不发 Maven Central;
samples(desktop 样例)未接入 ohos 构建,mkdocs.yml文档站与scripts/同为上游遗留。
8.3 未来优化方向
- 跟踪社区 Compose composition 宿主:出现后删除三个 ohosMain 门面与 nativeApp 桥,
commonMain的 @Composable API 直接接入,example/harmonyApp换成真实 Compose 页面; - 把门面快照改为带状态的主题会话(同一 builder 生命周期内多次 resolve),更贴近真实 App 用法;
- 评估
.har形态发布,让 ArkTS 工程直接依赖而不是走 so 三件套; - 在 CI 中加入 ohos 编译、ELF 符号检查和 HAP 构建;
- 补充模拟器(x86_64)与其他设备 ABI 的验收矩阵和自动化截图脚本。
九、总结
9.1 核心难点回顾
上游 composetheme + -material + -material3(全部 commonMain,零 expect/actual)
→ 验证社区 Compose ohos 制品线(foundation / material / material3)
→ 工具链对齐 Kotlin 2.4.20-0.1.0-07(Gradle 8.7 → 9.6.1)
→ 根构建替换(cmp_qrose 模式,依赖按目标分线:ohos 社区线 / jvm 上游线)
→ ohosArm64 / ohosX64 KLIB + 三个无头门面
→ C ABI(@CName × 2)+ key=value 列表字段载荷
→ C++ N-API(DisposeString 纪律)
→ ArkTS JSON 解析(接口化请求,绕开严格模式两条硬规则)
→ ArkUI 真机四 Tab 验收(令牌 / 合并语义 / M2/M3 桥 / 版本握手)
9.2 封装层次
- 引擎层:上游原版
buildComposeTheme、ThemeBuilderDSL、DesignTokens合并、ResolvedTheme属性存储; - 默认表层:上游原版
DefaultComposeTheme(100 色 / 9 文字 / 6 形状)与DefaultDebugIndication; - 桥层:上游原版
extendMaterial3/extendMaterial与ComposeTheme.colorScheme等扩展; - 门面层:三个 ohosMain 无头门面把上述对象提升为 JSON 快照;
- Native 层:生成双 ABI 动态库并通过 2 个 C ABI 输出 JSON;
- N-API 层:完成参数检查、字符串转换和 Native 内存释放;
- ArkTS 层:管理四 Tab 页面状态、请求分发、数值渲染与错误文本;
- DevEco 层:完成 CMake、HAP、签名、安装和真机运行。
9.3 三条经验
- 先验证生态制品线再定适配路线:社区 Compose 线(
1.12.0-0.1.0-07)补齐 foundation / material / material3 的 ohos klib 后,纯 Compose 库的正确答案从"剥离 Compose"(qrose 时代)变成"真编译整库 + 无头门面"(compose-shimmer 先例)——路线选择必须基于当天的制品可达性,而不是历史结论; - @Composable 不是铁板一块:把"取值入口是 @Composable"拆开看,入口底层的对象(builder、令牌映射、属性存储、桥函数)大多不需要 composition——无头门面的边界画在"真实对象 + 同款表达式"上,既不复制语义也不虚构运行时;上游 README 里"默认令牌无法覆盖"这条 WARNING,正是靠门面第一次在真机上被肉眼验证;
- ArkTS 严格模式是桥协议的隐性设计约束:
arkts-no-untyped-obj-literals/arkts-identifiers-as-prop-names决定了列表参数必须走key=value列表字段而不是前缀键——跨语言协议设计要把最严的消费方(ArkTS 编译器)当作第一公民,否则 Kotlin 侧再优雅也要返工。
9.4 适配成果
当前适配已完成:
- ComposeTheme
1.4.0真实引擎的双 ABI Kotlin/Native 编译(上游三个模块commonMain文件零改动,零 expect/actual,新增源码仅三个 ohosMain 无头门面); - 社区 Compose 制品线
1.12.0-0.1.0-07在主题类库上的完整落地(runtime / foundation / material / material3,ohosArm64 + ohosX64 双变体); - 2 个 C ABI 符号和 C++ N-API 包装层(
llvm-nm验证全局 T 符号); - DevEco Stage ArkUI 四 Tab 验收页面(默认令牌、DSL 驱动、合并语义、缺失令牌报错、M2/M3 桥、版本握手);
- 三个模块 JVM 编译交叉验证全绿(上游源码对上游制品线)+ HAP 构建(双 ABI 六件套 so)、签名、设备安装和真机验证;
- AtomGit 项目文档、OpenHarmony README(中英双份,中文主文档 18 节)和本地效果图。
适配过程 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)