本文记录
Jewel接入 OpenHarmony 的完整过程,覆盖主题模型盘点、Compose 源码集隔离、ohosArm64/ohosX64目标、Kotlin/Native 动态库、C ABI/N-API 桥接、ArkUI 真机页面、HAP 构建和真机验收。
Jewel是 JetBrains 孵化的 Compose for Desktop 主题库:用 Compose 复刻 IntelliJ Platform 的 New UI Swing Look and Feel,对外提供ThemeDefinition主题模型与一整套 jewel 风格 Composable(Button/Checkbox/Tab/TextField......)。本次适配与 KStore 一类"纯引擎库"(社区 Maven 已有 kotlinx ohos 制品线,整库真编译)有本质区别:Jewel上游没有 可整库编译的无 Compose 依赖引擎------它的全部价值落在主题模型上(8 组索引色阶、全局色、度量、字体样式、组件样式索引表)。因此走的是社区早期已审核路线(qrose / haze / PreCompose / voyager 的"模型内核提取"):主题模型以纯 Kotlin 内核提取进commonMain原样编译,Compose UI 整体隔离到src/composeMain不参与鸿蒙编译,真机上的 IntUI Light / Dark 色值与上游 0.27.0 构建期生成的主题类逐位一致,ArkUI 页面作为验收宿主。
项目地址: AtomGit/oh-tpc/jewel.git
上游仓库: JetBrains/Jewel
开发工具: 华为云码道
环境安装: 环境安装指南
一、背景
1.1 为什么做 OpenHarmony 平台 KMP/CMP 适配
Jewel 为 Compose for Desktop 应用提供"IntelliJ New UI 外观"这一件事的完整答案:ThemeDefinition 把调色板(ThemeColorPalette 的 Gray1...14 / Blue1...13 等 8 组索引色阶)、全局色(边框三色、outline 五色、文本五色、面板底色)、全局度量(2dp 焦点描边、24dp 行高)、三种字体样式(Inter 13sp×1.3、JetBrains Mono 13sp×1.2)和图标数据组装成单一主题对象;int-ui-standalone 再按组件给出样式工厂------默认按钮四态用 blue(4) / blue(3) / blue(2) / gray(12),Tab 选中下划线明色 blue(4) 暗色 blue(6),输入框聚焦描边 blue(4) / blue(6)......这些颜色语义索引表才是 jewel 真正的数据资产,与渲染框架无关。
OpenHarmony 应用同样需要这套 New UI 设计语言,但上游构建(纯 Kotlin/JVM 单平台、Compose Desktop 1.7.1、Kotlin 2.0.21-RC、无 AGP)不会生成 OpenHarmony 变体。如果只把示例页面改成 ArkTS 并手抄一份色表,页面可以显示色块,却无法证明 jewel 的主题模型在 OpenHarmony ARM64 设备上完成编译、链接和执行------手抄色表必然与上游漂移。适配需要同时验证主题模型内核、Native 动态库、C ABI、N-API、ArkUI 页面和 HAP 交付链路。
| 障碍 | 具体问题 |
|---|---|
| 没有纯引擎 | 上游全部模块依赖 androidx.compose.* / AWT / JBR 字体,不存在"整库真编译"的选项,必须先做模型内核提取。 |
| 目标缺失 | 必须增加 ohosArm64() / ohosX64() 才能生成 OpenHarmony KLIB。 |
| 工具链不一致 | 上游 Kotlin 2.0.21-RC / Gradle 8.10 / JDK 17 与鸿蒙工具链无法共存于一个 Gradle 构建。 |
| 构建逻辑耦合 | 上游构建逻辑在 buildSrc convention plugin 里(detekt / ktfmt / IntelliJ Platform 代码生成),Gradle 见到 buildSrc 目录就自动构建,必须整体规避。 |
| 语言边界不同 | ArkTS 不能直接持有 Kotlin 对象,需要 C ABI、C++ N-API 和 JSON。 |
| 主题数据在构建期生成 | 调色板常量由上游 intellij-theme-generator 在 JVM 构建期从 IntelliJ 主题 JSON 拉取生成,鸿蒙侧需要等价的数据落地管线。 |
| 交付链路复杂 | .so、CMake、N-API、HAP、签名和 hdc 都要单独验收。 |
因此,本项目把边界放在:主题模型内核的 ohos 编译、根构建替换(cmp_qrose 模式)和 C ABI/N-API 桥接层。色值语义完全由上游原版定义负责(生成管线与上游钉死同一个 IntelliJ Platform 版本),ArkTS 只负责调用 Native、解析 JSON 和展示结果。
1.2 库提供的能力
Demo 提供一个真实的主题数据会话,对应桥接的全部入口:
- 整包主题(theme) :
jewelThemeInfo({theme})一次返回整个ThemeDefinition------8 组色阶全量 hex、raw 键、全局色、度量、三种字体样式、图标色板和 10 类组件样式表; - 色键查询(key) :请求附带
key时返回lookup结果(Blue4 → #FF3574F0),支持索引键与 raw 键,越界键返回exists:false而不崩溃; - 明暗切换 :
theme: light|dark重查内核,页面全量刷新------数据来自 Native 而非 ArkTS 本地反转; - 版本握手 :响应携带
version: 0.27.0-ohos.1,来自真实 Native 调用而非静态文字。
响应包含:
| 字段 | 作用 |
|---|---|
ok |
操作是否成功,失败时 error 带原文。 |
name / isDark / version |
主题名、明暗标志、fork 版本握手。 |
palette / rawKeys |
8 组色阶(#AARRGGBB 数组)与原始主题键(如 windowsPopupBorder)。 |
globals / metrics |
边框 / outline / 文本 / 面板底色与 2dp / 24dp 度量。 |
textStyles |
default / editor / console 三种字体的 family、字号、行高、字重。 |
iconPalette |
图标色板(调色板键解析后的 hex)。 |
components |
10 类组件的逐状态颜色与 dp 度量(button / buttonOutlined / checkbox / radioButton / tab / slider / progress / link / divider / textField)。 |
lookup |
可选:key 查询结果(argb + exists)。 |
上游 ThemeColorPalette.lookup() 对越界索引抛 IndexOutOfBoundsException------内核保留该行为并有测试钉死,桥侧用 runCatching 收敛为 exists:false,语义不跨边界走样。
1.3 实现适配
| 维度 | 要求 |
|---|---|
| 代码复用 | 主题模型(色阶、lookup 语义、全局色、度量、字体、组件样式索引)以上游源码为唯一参照逐项端口,包名与上游对齐;色值常量与上游构建期生成物逐位一致。 |
| 平台目标 | 根构建直接声明 ohosArm64 + ohosX64,保留 jvm 作交叉验证,库模块出 KLIB,example/nativeApp 出双 ABI 动态库。 |
| 构建隔离 | 根构建替换为极简 ohos 构建(cmp_qrose 模式);buildSrc 改名 buildSrc.upstream,ui / decorated-window / int-ui / markdown / ide-laf-bridge / samples 全部留盘不进构建。 |
| 桥接稳定 | 使用 1 个 C ABI 和 UTF-8 JSON,不传递 Kotlin 对象地址;异常永远不跨 FFI。 |
| 数据管线 | 调色板生成脚本 + expUI 主题 JSON 快照随仓入库,可离线复现,生成物头部记录出处。 |
| UI 完整 | 四 Tab 覆盖全部桥接出口:调色板、主题、组件、查询 + 明暗切换。 |
| 仓库规范 | 项目说明、文章、效果图和源码链接统一使用 AtomGit。 |
本项目的 ArkUI 页面不是 Compose Multiplatform UI 实现。
Jewel上游的全部 Composable(依赖 Compose Desktop 1.7.1 / JBR / AWT)被隔离在src/composeMain不参与鸿蒙编译;ArkTS Demo 把桥暴露的主题模型画出来------真机上看到的是 jewel 的真实色值,不是手绘近似。
二、实现路线图
text
第 1 阶段:项目初始化 ── 盘点模块与依赖边界、确认"模型内核提取"路线、确定交付物
第 2 阶段:目标与依赖打通 ── 根构建替换、buildSrc 改名规避、eazytec Maven 置前、Kotlin 2.2.21-1.0.0 钉版
第 3 阶段:内核与数据落地 ── 主题模型纯 Kotlin 化、expUI 主题 JSON 快照、调色板生成管线
第 4 阶段:原生桥接 ── @CName C ABI、组件样式索引解析、C++ N-API、参数校验和内存释放
第 5 阶段:ArkUI 宿主 ── 四 Tab 页面、明暗切换、色键查询器和版本握手
第 6 阶段:示例与验证 ── HAP、签名、设备安装、真机运行和效果图
三、逐步实现过程
第 1 阶段:项目初始化
1.1 工程边界
text
foundation/ 上游核心模块(重写为 KMP:jvm + ohosArm64 + ohosX64)
foundation/src/commonMain/ 新增:主题内核(8 个手写文件 + 2 个生成文件)
foundation/src/commonTest/ 新增:24 项交叉验证用例
foundation/src/composeMain/ 上游 Compose 源码隔离区(42 个文件,不参与任何 target 编译)
buildSrc.upstream/ 上游构建逻辑原样改名(Gradle 自动构建规避)
example/nativeApp/ Kotlin/Native 动态库(Bridge + MiniJson + StyleResolver)
example/harmonyApp/ DevEco Stage、CMake N-API 和 ArkTS 页面
tools/ 调色板生成脚本 + expUI 明暗主题 JSON 快照
README.OpenHarmony.md / _CN.md 鸿蒙适配说明(中英双份)
README.upstream.md 上游原文保存
ui/ decorated-window/ int-ui/ markdown/ ide-laf-bridge*/ samples/ ui-tests/
上游其余模块,留盘不进 settings
构建拓扑走 cmp_qrose 模式 (根构建即 ohos 构建):settings.gradle.kts 只 include :foundation 与 :example:nativeApp,rootProject.name = "ohos_jewel"。上游的 compose / compose-compiler / detekt / ktfmt / IntelliJ Platform 代码生成插件全部随根构建替换剥离------本 fork 不发 Maven Central。
buildSrc 必须改名是本仓独有的坑:Gradle 只要见到名为 buildSrc 的目录就会在每次构建前自动编译它,而上游 buildSrc 依赖的插件条目已随版本目录重写移除,一 sync 即死。改名 buildSrc.upstream 后 Gradle 不再认领,上游构建逻辑以"留盘参考"的形态存在。
1.2 工具链矩阵
| 项目 | 配置 |
|---|---|
| Kotlin Multiplatform | HarmonyOS Kotlin 2.2.21-1.0.0(eazytec Maven) |
| JDK | 21(org.gradle.java.home 指向 Temurin 21.0.12) |
| OpenHarmony 目标 | ohosArm64 + ohosX64(jvm 保留作交叉验证) |
| Gradle | 8.14.1(wrapper 随适配替换;2.2.21 旧线要求的版本) |
| 主题数据源 | IntelliJ Platform idea/243.19420.21 expUI 明暗主题 JSON |
| DevEco product | 5.1.0(18) 兼容 |
| DevEco Studio | 26.0.0 Release |
| ABI | arm64-v8a + x86_64 |
| Bundle | org.terminator.ohos.jewel |
bash
export JAVA_HOME="/path/to/jdk-21"
java -version
1.3 源码依赖度盘点
| 模块 | 文件形态 | 依赖形态 | 处置 |
|---|---|---|---|
foundation |
42 个 Kotlin 文件 | 主题模型 + Compose UI(SelectableLazyColumn / Tree / modifier)混合 | 拆分:模型进 commonMain,UI 进 composeMain |
ui |
99 个文件 | 全量 Compose 组件 + 4 处 java.awt | 留盘不进构建 |
decorated-window |
--- | AWT + JNA 窗口装饰 | 留盘不进构建 |
int-ui |
--- | int-ui 主题值 + IntelliJ 主题生成器 | 样式索引表随桥进 nativeApp;模块本身留盘 |
markdown / ide-laf-bridge* / samples / ui-tests |
--- | commonmark / IntelliJ Platform / CMP Desktop | 留盘不进构建 |
盘点结论决定了路线:foundation 里真正可移植的是 ThemeColorPalette(索引访问 + lookup() 正则语义)、GlobalColors 家族、GlobalMetrics、字体样式与 ThemeDefinition 装配------除 Compose Color / TextStyle / Dp 类型外零依赖;int-ui 侧的组件样式工厂全部是"调色板索引 + dp 常量"的纯数据。这两块合起来就是鸿蒙内核的全部内容。
1.4 页面边界
text
标题区 IntUI Light / Dark · 版本握手行(0.27.0-ohos.1)· Light-Dark 切换开关
Tab 切换 调色板 / 主题 / 组件 / 查询(底部四 Tab)
调色板页 rawKeys 芯片 + 8 色系横滑色块(Gray1..14、Blue1..13...),点击弹 key+hex
主题页 globals 卡片(以 panelBackground 为底演示边框/outline/文本色)、度量、
三种 TextStyle 示例(editor/console 等宽渲染)、iconPalette 色块墙
组件页 默认/Outlined 按钮四态静态样本 + 可交互真按钮、复选框、单选、Tab 下划线、
滑块、进度条、链接、输入框三态、分隔线(颜色尺寸全部来自桥数据)
查询页 色键输入框 + 快捷芯片 → lookup 色块/hex/exists + 关于卡片(版本、数据源、仓库链接)
页面启动时先调 jewelThemeInfo({theme:'light'}) 建模,切换开关重查内核。
第 2 阶段:目标与依赖打通
2.1 路线验证(本适配的成败手)
与 KStore 不同,Jewel 没有 kotlinx 依赖矩阵可查------它的依赖是 androidx.compose.* 与 IntelliJ Platform,ohos 制品线上不存在对应物。验证落在两头:
text
· 上游 foundation 的主题模型文件 → 仅依赖 Compose 的 Color/TextStyle/Dp 类型壳,语义纯 Kotlin ✓ 可提取
· 上游 int-ui-standalone 样式工厂 → 全部是 palette(index) 引用 + dp 常量 ✓ 可索引表化
· 上游 Composable / buildSrc 生态 → Compose Desktop 1.7.1 / JBR / AWT ✗ 必须隔离
结论:走 cmp_qrose 审核路线 (模型内核提取),而不是 KStore 的整库真编译。工具链随之钉旧线:HarmonyOS Kotlin 2.2.21-1.0.0(qrose / haze 同款),Gradle 降 8.14.1,JDK 21。
2.2 工具链钉版与仓库置前
settings.gradle.kts 把 eazytec 仓库写在最前(且必须用命名仓库形式,原因见 8.1 踩坑第 2 条):
kotlin
pluginManagement {
repositories {
maven {
name = "eazytec-public"
url = uri("https://maven.eazytec-cloud.com/nexus/repository/maven-public/")
}
maven {
name = "eazytec-harmonyos"
url = uri("https://maven.eazytec-cloud.com/nexus/content/groups/harmonyos")
}
gradlePluginPortal()
google()
mavenCentral()
}
}
rootProject.name = "ohos_jewel"
include(":foundation")
include(":example:nativeApp")
版本目录收敛到一项:
toml
[versions]
kotlin = "2.2.21-1.0.0"
[plugins]
kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
2.3 编译主题内核
foundation/build.gradle.kts 收敛为纯 KMP 模块:
kotlin
plugins {
alias(libs.plugins.kotlin.multiplatform)
}
kotlin {
applyDefaultHierarchyTemplate()
jvmToolchain(21)
jvm() // 交叉验证用
ohosArm64()
ohosX64()
sourceSets {
commonMain.dependencies {}
commonTest.dependencies {
implementation(kotlin("test"))
}
}
}
// Upstream Compose sources are quarantined under src/composeMain and are not
// registered as a source set for any target --- same policy as cmp_qrose.
实测:
bash
./gradlew :foundation:compileKotlinJvm :foundation:compileKotlinOhosArm64 :foundation:compileKotlinOhosX64
# BUILD SUCCESSFUL ×3
2.4 测试任务的处置
与 KStore 相同的纪律:测试统一在 JVM 跑,根构建全局禁用 ohos 测试任务:
kotlin
subprojects {
tasks.matching { it.name.contains("Ohos", ignoreCase = true) && it.name.contains("Test") }
.configureEach { enabled = false }
}
上游 Compose 测试文件随 src/composeMain 隔离,commonTest 是新写的 24 项模型对账用例(见 6.2)。
第 3 阶段:内核与数据落地
3.1 主题模型纯 Kotlin 化
内核文件对照上游逐项端口,包名保持一致:
| 内核文件 | 上游参照 | 端口要点 |
|---|---|---|
theme/JewelColor.kt |
androidx.compose.ui.graphics.Color |
data class 承载 ARGB Long;#AARRGGBB 序列化 |
theme/ThemeColorPalette.kt |
foundation 同名类 | 索引访问、([a-z]+)(\d+) 正则 lookup、grey 别名、raw 回退逐位一致(含越界抛异常) |
GlobalColors.kt |
foundation 同名类家族 | 仅把 Compose Color 换 JewelColor,嵌套结构不变 |
GlobalMetrics.kt |
foundation 同名类 | Dp 换 Float(2dp / 24dp) |
theme/TextStyleData.kt |
createDefaultTextStyle / createEditorTextStyle |
Inter 13sp×1.3、JetBrains Mono 13sp×1.2,倍率常量随行 |
theme/ThemeIconColors.kt |
ThemeIconData |
只保留图标色板;值保留"调色板键"形态惰性解析(同上游 painter hints) |
theme/ThemeDefinition.kt |
foundation 同名类 | 同字段装配 |
theme/intui/IntUiThemes.kt |
IntUiGlobalColors / IntUiTheme 工厂 |
light()/dark() 的全部索引默认值逐项端口 |
JewelColor 用 data class 而不是 value class 是编译器逼出来的决策:value class 在 JVM target 要求 @JvmInline,而 HarmonyOS Kotlin 的 Native target 报 Unresolved reference 'JvmInline'------两头矛盾,data class 跨平台无坑且结构相等正好匹配测试需求。
3.2 主题数据管线(与上游同源的生成链)
上游的调色板常量不在源码里------intellij-theme-generator 在 JVM 构建期从 raw.githubusercontent.com/JetBrains/intellij-community/idea/243.19420.21/.../expUI_*.theme.json 拉取并生成 Kotlin 对象。鸿蒙侧复刻同一条管线,但把数据快照进仓:
bash
git clone --depth 1 --branch idea/243.19420.21 --filter=blob:none --sparse \
https://github.com/JetBrains/intellij-community.git
git sparse-checkout set platform/platform-resources/src/themes/expUI
# → tools/expUI_light.theme.json / tools/expUI_dark.theme.json(随仓入库)
py tools/gen_palettes.py # → IntUiPaletteLight.kt / IntUiPaletteDark.kt
生成物数据量:Light 88 索引色 + 1 raw 键(windowsPopupBorder)+ 9 图标色;Dark 97 索引色(暗色色阶更长:Green12 / Yellow11 / Orange11 / Purple12 / Teal12)。生成文件头部记录 JSON 出处与上游钉死版本,杜绝"手抄色表"嫌疑------色值与上游构建期生成物逐位一致由 6.2 的测试钉死。
一个容易踩的语义坑:expUI 主题 JSON 的 icons.ColorPalette 值不是 hex 而是调色板键引用 (如 Checkbox.Background.Selected = "Blue4")。上游运行期才解析,内核保持同样形态(Map<String, String> + resolveColor 惰性解析),桥在序列化时对齐调色板。
第 4 阶段:原生桥接(技术难点)
4.1 三层桥接
text
ArkTS
│ JSON string
▼
C++ N-API entry(libjewel_napi.so)
│ const char*
▼
Kotlin/Native C ABI(libohosjewel.so)
│ JewelThemeInfo
▼
UTF-8 JSON + DisposeString
直接导出 Kotlin 对象会暴露 ABI 和生命周期问题;按状态拆几十个函数又会让 C ABI 面膨胀。选择单入口整包返回 :主题数据量小(几十 KB 文本),一次 JSON.parse 建模,符号验收也只需盯一个函数。
4.2 Kotlin/Native 导出函数
kotlin
@CName("JewelThemeInfo")
fun jewelThemeInfo(request: String): String {
return try {
val json = MiniJson.parse(request)
val isDark = json.str("theme").equals("dark", ignoreCase = true)
val key = json.str("key").trim()
val theme = IntUiThemes.theme(isDark)
buildThemeJson(theme, key.ifBlank { null })
} catch (t: Throwable) {
"""{"ok":false,"error":${jsonString(t.message ?: t.toString())}}"""
}
}
| C ABI 符号 | ArkTS 方法 | 覆盖内核 |
|---|---|---|
JewelThemeInfo |
jewelThemeInfo(req) |
IntUiThemes.light()/dark() 装配 + 调色板 lookup + 组件样式解析 |
libohosjewel_symbols |
(内部) | DisposeString 等运行时接口(llvm-nm 同样可见的全局 T 符号) |
StyleResolver 是 jewel 版的"qrose Rasterizer":把 int-ui-standalone 十类组件样式工厂的调色板索引表逐项端口(默认按钮 light:底 blue(4)/hover blue(3)/pressed blue(2)/disabled gray(12)、字 gray(14)/disabled gray(8)......),在桥内解析为具体 #AARRGGBB。上游为 Color.Unspecified 的字段(checkbox content 继承环境色、dark tab 背景继承面板)按上游语义映射到 text.normal / panelBackground,不硬编码。
4.3 C++ N-API 方法分发
cpp
napi_property_descriptor desc[] = {
{"jewelThemeInfo", nullptr, NapiJewelThemeInfo, nullptr, nullptr, nullptr,
static_cast<napi_property_attributes>(napi_default | napi_enumerable), nullptr},
};
回调统一三步:napi_typeof 校验必须是 string、OH_LOG_INFO 打字节数、调 KN 函数并包装返回。KN 返回的字符串必须经 libohosjewel_symbols()->DisposeString 归还 Kotlin 运行时,绝不 free()。libohosjewel_api.h 按社区已审仓库同款手写(只声明 ExportedSymbols 结构与业务符号),不拖整份生成头。
4.4 CMake 和类型声明
cmake
add_library(ohosjewel SHARED IMPORTED)
set_target_properties(ohosjewel PROPERTIES
IMPORTED_LOCATION ${KN_SO}
IMPORTED_NO_SONAME TRUE)
add_library(jewel_napi SHARED napi_init.cpp)
target_link_libraries(jewel_napi PUBLIC
libace_napi.z.so
libhilog_ndk.z.so
ohosjewel)
OHOS_ARCH 为空或 libohosjewel.so 缺失直接 FATAL_ERROR(提示先跑 link 任务);$ORIGIN rpath 加 POST_BUILD 拷贝保证 NAPI so、KN so、libc++ 三件套同目录,dlopen 时互相可见。
typescript
export const jewelThemeInfo: (request: string) => string;
第 5 阶段:ArkUI 宿主
5.1 建模和触发查询
typescript
private loadTheme(key: string): void {
const req: ThemeQuery = new ThemeQuery();
req.theme = this.dark ? 'dark' : 'light';
req.key = key;
const raw: string = jewelThemeInfo(JSON.stringify(req));
const parsed: ThemeInfo = JSON.parse(raw) as ThemeInfo;
if (!parsed.ok) { this.status = `失败: ${parsed.error}`; return; }
this.info = parsed; // @State 全量刷新四个页面
this.paletteGroups = this.buildGroups(parsed.palette);
}
请求模型用显式声明的 ThemeQuery 类构建------严格 ArkTS 禁止未定型对象字面量(arkts-no-untyped-obj-literals),这条在真机构建期翻过车(见 8.1 第 13 条)。
5.2 页面交互
四 Tab 自上而下:调色板页 rawKeys 芯片 + 8 色系横滑色块(点击弹 key = hex 对话框);主题页 globals 卡片以 panelBackground 为底色逐行演示边框 / outline / 文本色,配 metrics、三种 TextStyle 示例与 iconPalette 色块墙;组件页默认 / Outlined 按钮四态静态样本并排 + 可交互真按钮、复选框、单选、Tab 下划线条、滑块、进度条、链接、输入框(真实输入 + normal / focused / disabled 三态边框样本)、分隔线,每张卡片标注 corner / pad / min / border 数值;查询页色键输入框 + 快捷芯片(Blue4 / Gray14 / windowsPopupBorder)→ lookup 结果 + 关于卡片(版本握手、IntelliJ Platform 数据源、仓库链接)。
5.3 对比度和错误边界
页面底色仅黑白属允许的 chrome 例外,所有主题色一律来自桥数据 (grep 可证:源码硬编码 hex 仅 #000000 / #FFFFFF / 两条说明灰)。ok:false 时错误原文进状态行;Native 调用抛错同样被 try/catch 收进日志。查询越界键走 exists:false,不崩溃。
第 6 阶段:示例与验证
6.1 工程结构
text
example/
├── nativeApp/
│ ├── build.gradle.kts 双 ABI sharedLib(baseName=ohosjewel)+ cxxShared + copyKnSo*
│ └── src/ohosMain/kotlin/example/nativeapp/
│ ├── Bridge.kt @CName + MiniJson(扁平 JSON,继承自 cmp_qrose 审核模式)
│ └── StyleResolver.kt 组件样式索引表 → 具体 ARGB
└── harmonyApp/
├── build-profile.json5 signingConfigs 留空(签名由 DevEco 自动生成回填)
├── AppScope/app.json5 bundleName org.terminator.ohos.jewel、versionName 0.27.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/libohosjewel_api.h
├── src/main/cpp/types/libjewel_napi/{index.d.ts, oh-package.json5}
└── src/main/ets/pages/Index.ets ArkUI 四 Tab 验收页面
6.2 构建、签名和安装
bash
./gradlew :foundation:jvmTest # 24 项交叉验证
./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 中勾选自动生成,证书不进入仓库(仓库内 build-profile.json5 的 signingConfigs 保持空数组)。
bash
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.jewel -m entry
四、完整代码对照
4.1 整体架构
text
org.jetbrains.jewel.foundation/**(主题模型内核,commonMain)
│ ThemeColorPalette / GlobalColors / GlobalMetrics / TextStyleData
org.jetbrains.jewel.foundation.theme.intui/**(生成常量 + 装配器)
│ IntUiPaletteLight / IntUiPaletteDark / IntUiThemes
Bridge.kt + StyleResolver + MiniJson
│ JewelThemeInfo
libjewel_napi.so
│ jewelThemeInfo
Index.ets
│ JSON parse + @State 更新
▼
ArkUI 真机页面(四 Tab)
4.2 文件清单
| 文件 | 职责 |
|---|---|
settings.gradle.kts |
极简 ohos 构建、eazytec Maven 置前(命名仓库)、ohos_jewel、上游其余模块移出 |
gradle/libs.versions.toml |
Kotlin 2.2.21-1.0.0 单条目 |
build.gradle.kts(根) |
版本 0.27.0-ohos.1、全局禁用 Ohos*Test* 任务 |
foundation/build.gradle.kts |
纯 KMP:jvm + ohosArm64 + ohosX64 + commonTest |
foundation/src/commonMain/** |
主题内核 8 个手写文件 + 2 个生成调色板 |
foundation/src/commonTest/** |
24 项模型对账用例 |
tools/gen_palettes.py + tools/expUI_*.theme.json |
调色板生成管线与数据快照 |
example/nativeApp/build.gradle.kts |
双 ABI sharedLib、cxxShared 定位 libc++、copyKnSo* 回填 |
nativeApp/.../Bridge.kt |
1 个 C ABI、MiniJson、错误通道 |
nativeApp/.../StyleResolver.kt |
十类组件样式索引表解析 |
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 |
鸿蒙适配主文档(17 节) |
4.3 关键 API 对照
| 层次 | API | 作用 |
|---|---|---|
| 内核 | ThemeColorPalette.gray(4) / lookup("Blue4") |
索引访问与键查询(语义与上游逐位一致,含越界抛异常) |
| 内核 | IntUiThemes.light() / dark() |
主题装配(全局色 / 度量 / 字体 / 内容色默认值端口自上游工厂) |
| 内核 | TextStyleData.default / .editor |
Inter 13sp×1.3 / JetBrains Mono 13sp×1.2 |
| 内核 | ThemeIconColors.resolveColor |
图标色板键 → 调色板惰性解析 |
| 桥 | StyleResolver.componentsJson |
十类组件样式索引 → 具体 ARGB + dp 度量 |
| Native | JewelThemeInfo |
C ABI JSON 导出(单入口整包) |
| N-API | jewelThemeInfo(req) |
向 ArkTS 暴露能力 |
| ArkTS | loadTheme(key) |
触发查询并刷新四页 UI |
4.4 ArkTS 与 Kotlin 的边界
typescript
const q = JSON.parse(jewelThemeInfo(JSON.stringify(
{ theme: 'light', key: 'Blue4' })));
// q.ok === true; q.lookup = { key: 'Blue4', argb: '#FF3574F0', exists: true }
// q.components.button.background === '#FF3574F0'(light 默认按钮底色 = Blue4)
kotlin
val theme = IntUiThemes.theme(isDark)
val resolved = runCatching { theme.colorPalette.lookup("Blue4") }.getOrNull()
// 上游 lookup 越界抛 IndexOutOfBoundsException,桥收敛为 exists:false
两者之间只传输扁平 JSON 字符串,不传输 Kotlin 对象或未校验的动态结构。
五、关键决策说明
决策 1:模型内核提取而不是整库真编译
KStore 整库真编译的前提是"引擎零 UI 依赖 + kotlinx ohos 制品线存在"。Jewel 两个前提都不满足:全部模块依赖 Compose Desktop 1.7.1 / JBR / AWT,且上游连 KMP 骨架都没有(纯 JVM 单平台)。可移植且有价值的是主题模型------提取进 commonMain 后,真机上的每个色值仍由上游语义决定(生成管线与上游钉死同一 IntelliJ Platform 版本),不是手抄近似。
决策 2:工具链钉 2.2.21-1.0.0 旧线,Gradle 降 8.14.1
内核只依赖 kotlin-stdlib,没有高版本元数据压力,2.2.21 旧线(qrose / haze 同款)最稳;Gradle 随线降 8.14.1。KStore 那条"工具链跟着依赖走升 2.4.20"的经验在这里不适用------两条线的选择逻辑互为镜像:有高版本制品依赖就升线,纯内核就钉旧线。
决策 3:调色板走"构建期生成 + 数据快照"管线
上游色值本来就在 JVM 构建期生成,鸿蒙侧复刻同一管线并把 JSON 快照进 tools/:可离线复现(py tools/gen_palettes.py)、生成物头部带出处、jvmTest 钉死数值。运行期 JSON 解析和 kotlinx.serialization 依赖一并省掉(MiniJson 只处理桥请求)。
决策 4:单 C ABI 整包返回 + 组件样式表放桥侧
主题是"一次性建模、多次消费"的数据,不是高频调用------单入口整包返回让符号验收只盯一个函数,ArkTS 一次 JSON.parse 建模。组件样式索引表(哪些色阶用于哪个状态)本质是 jewel 模型数据,但为保持内核与"渲染映射"分层清晰,放 nativeApp 的 StyleResolver(qrose 把 Rasterizer 放 nativeApp 是同一先例)。
决策 5:jvmTest 交叉验证 + 测试即对账
24 项 commonTest 跑在 JVM 上,验证的是与 ohos .so 同一份内核代码:色阶形状、lookup 语义、全局色 / 字体 / 度量与上游默认值逐项对账、明暗互异、图标色解析。上游 Compose 测试随 composeMain 隔离------它们测的是被隔离的 UI,不是内核。
六、测试与验证
6.1 测试环境
本次验证使用 Windows(x64)、JDK 21(Temurin 21.0.12)、DevEco Studio 26.0、DevEco 自带 hvigor / node 命令行与鸿蒙真机(arm64)。
6.2 JVM 交叉验证
bash
./gradlew :foundation:jvmTest
24 项用例全部通过:
| 测试类 | 用例数 | 钉住什么 |
|---|---|---|
JewelColorTest |
6 | hex 解析(6/8 位)、#AARRGGBB 格式化、往返一致、非法字面量拒绝、Unspecified 语义 |
ThemeColorPaletteTest |
9 | 1 基索引、lookup 正则与大小写、grey 别名、raw 回退、越界抛异常(同上游)、OrNull 访问器、Empty 语义 |
IntUiThemesTest |
9 | 明暗标志、色阶形状(Light 88 / Dark 97)、lookup 与索引一致、全局色逐项等于上游默认索引、字体样式(13/16.9/15.6)、内容色、2dp/24dp、图标色板解析、明暗互异 |
其中 IntUiThemesTest.globalColorsFollowTheUpstreamIntUiDefaults 逐项断言 borders.normal == gray(12)、panelBackground == gray(13)(light)/ gray(2)(dark)、outlines.focused == blue(4) / blue(6)......这是"内核没漂"的核心闸门。
6.3 HAP 验证
text
:foundation:compileKotlinOhosArm64 / OhosX64: BUILD SUCCESSFUL
linkDebugSharedOhosArm64 / linkDebugSharedOhosX64: BUILD SUCCESSFUL
llvm-nm -D: JewelThemeInfo / libohosjewel_symbols 均为全局 T 符号
llvm-readelf -d: DT_NEEDED 含 libc++_shared.so(copyKnSo* 已同拷)
BuildNativeWithNinja: finished(双 ABI 编出 libjewel_napi.so)
CompileArkTS: finished
PackageHap: finished
BUILD SUCCESSFUL
install bundle successfully
start ability successfully
entry-default-unsigned.hap 约 6.9 MB,内含双 ABI 各三件套;HAP 内 KN so arm64 约 2.0MB、x86_64 约 1.9MB,NAPI so 约 64KB(debugShared 未 strip 时 arm64 3.7MB / x64 3.4MB,含 Kotlin/Native 运行时)。本仓的 HAP 全链路在 DevEco 命令行完成验证(node + hvigorw.js --mode module -p product=default assembleHap),ArkTS 编译错误在真机阶段清零。
6.4 功能用例
- 启动后标题区出现
IntUI Light与握手行IntUI 主题桥 · 0.27.0-ohos.1,状态行IntUI Light · 0.27.0-ohos.1 · rawKeys 1 个(来自真实 Native 调用,非静态文字); - 调色板页 8 色系横滑色块齐全,Light 下 Gray13 =
#FFF7F8FA、Blue4 =#FF3574F0,点击色块弹出key = hex;rawKeys 显示windowsPopupBorder = #FFB9BDC9; - 切换 Dark 后全页颜色重查内核刷新:Gray1 =
#FF1E1F22、Gray2 =#FF2B2D30、Blue6 =#FF3574F0(与 Light Blue4 同为品牌蓝); - 组件页默认按钮四态:底色 blue4 / hover blue3 / pressed blue2 / disabled gray12,字色 gray14 / disabled gray8;Outlined 按钮 light 边框 gray9、聚焦 blue4;Tab 选中下划线 light = blue4 / dark = blue6;滑块、进度条、输入框三态边框随主题切换;
- 主题页 globals 卡片以
panelBackground为底,outline 宽 2、行高 24;default 字号 13 / 行高 16.9,editor / console 行高 15.6(等宽渲染并注明真实字体名 Inter / JetBrains Mono 由 JBR 提供); - 查询页输入
Blue4→#FF3574F0exists:true;输入Blue99→ exists:false 不崩溃;快捷芯片一键查询; hdc shell hilog | grep Jewel可见jewelThemeInfo request bytes=...请求日志。
6.5 验证结论
主题模型内核、Native 动态库、C ABI、N-API、ArkUI 页面和 HAP 已完成链路验证。应用在鸿蒙真机上成功安装并进入前台,页面展示了真实的 jewel 主题数据会话:版本握手、8 色系调色板、明暗切换、十类组件样式与色键查询闭环(见下节效果图)。
七、运行效果
7.1 真机截图

7.2 命令速查
bash
git clone https://atomgit.com/oh-tpc/jewel.git
cd jewel
export JAVA_HOME="/path/to/jdk-21"
./gradlew :foundation:jvmTest # 24 项交叉验证
./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.jewel -m entry
hdc shell hilog | grep Jewel
八、遗留问题与改进方向
8.1 踩坑复盘
- 只改 ArkTS 页面不算 CMP 适配,必须让
:foundation真正编译成ohosArm64/ohosX64动态库------而 jewel 连 KMP 骨架都没有,先补kotlin("multiplatform")再谈目标; - 本机全局
~/.gradle/init.d/china-mirrors.gradle会把 aliyun 镜像插到所有仓库最前,且会把 pluginManagement 里的匿名maven(url)仓库在 remove / re-add 过程中丢弃------eazytec 仓库必须写成maven { name = ...; url = uri(...) }命名形式,依赖仓库模式用PREFER_SETTINGS(PREFER_PROJECT会被 init 脚本注入的 project 级镜像反客为主); settings.gradle.kts里 eazytec 双仓必须排在pluginManagement最前,否则配置期直接Unresolved reference: ohosArm64;buildSrc目录存在即被 Gradle 自动构建------上游 buildSrc 依赖的插件已随版本目录重写移除,必须改名buildSrc.upstream,"留盘"和"可构建"只能二选一;value class两头受气:JVM 要求@JvmInline、HarmonyOS Kotlin Native 报Unresolved reference 'JvmInline'------跨平台内核的颜色类型用data class;- Kotlin 块注释支持嵌套:注释文本里出现
styling/*.kt字样时其中的/*会开启嵌套注释,报"Unclosed comment"且指向文件末尾,极难定位; - expUI 主题 JSON 抓取:
raw.githubusercontent.com在本网络不可达且公共镜像 404,github.com本身可达------用git clone --branch idea/243.19420.21 --filter=blob:none --sparse稀疏克隆拿到主题文件后快照进仓; icons.ColorPalette的值不是 hex 而是调色板键(Gray8、Blue4),按 hex 解析会炸------保留字符串、查询期经palette.lookup惰性解析(与上游行为一致);- 上游
lookup()对越界索引抛异常 而不是返回 null,第一版测试按"返回 null"理解写错了------内核必须保留上游行为,桥侧runCatching收敛; - KN so 的
DT_NEEDED含libc++_shared.so,必须与 KN so 同目录部署,copyKnSo*从~/.konan/dependencies取配套文件一起落地; - 根
.gitignore的build/与.idea/*是根锚定模式,盖不住嵌套目录------DevEco 打开工程后生成的example/harmonyApp/.idea/、entry/libs/、oh_modules/要单独追加,否则混进提交(本仓实测.idea/.deveco缓存差点入库); - HAP 缺当前 ABI 的 so 会报安装失败
9568347,abiFilters必须双 ABI,每个 ABI 三件套缺一不可; - 严格 ArkTS 的真机构建坑(DevEco 编译期才暴露,共 6 处):请求对象必须用显式声明的类构建(
arkts-no-untyped-obj-literals);@State属性不能与CustomComponent基类方法重名(tabIndex改名mainTab);Slider.blockSize要SizeOptions不是 number;TextDecorationType枚举成员是驼峰Underline不是UNDERLINE; - KN 返回字符串必须
DisposeString归还运行时,N-API 层不能free();签名材料由 DevEco 自动写入build-profile.json5,仓库内保持空数组,证书绝不进入 AtomGit。
8.2 已知问题
- 不含 Compose Composable:上游 UI 层(ui / decorated-window / int-ui / markdown / ide-laf-bridge / samples)未编译到鸿蒙,这是路线本身的决定------等社区 compose.ui ohos 制品与上游版本对齐后可评估把 composeMain 接回;
- 组件样式表为精选集:当前十类(button / buttonOutlined / checkbox / radioButton / tab / slider / progress / link / divider / textField),上游 int-ui-standalone 还有 chip / comboBox / menu / scrollbar / tree / segmentedControl 等约二十类未透出;
- checkbox / radio 的 content 色上游为
Unspecified(继承环境色),内核取text.normal,方框描边走GlobalColors.outlines------按上游语义映射,已在文档标注; - 图标只透出 iconPalette 颜色映射;
iconOverrides(IntelliJ 图标路径)与selectionColorPalette属 Swing / Compose painter 管线,不入内核(dark 主题本就无图标色板); - Inter / JetBrains Mono 为 JBR 桌面字体,Demo 用系统字体渲染并注明真实字体名;textStyles 透出 family / size / lineHeight / weight 四要素,decoration / shadow 未建模;
- android / js / apple 目标不存在于上游(纯 JVM 库),无目标剥离问题;发布、dokka、kover 一并剥离,本 fork 不发 Maven Central。
8.3 未来优化方向
- 扩展组件样式表(chip / comboBox / menu / scrollbar / tree / segmentedControl......
StyleResolver每类一个xxxJson,无新机制); - 把内核发布为 ohos KLIB /
.har形态,让 ArkTS 工程直接依赖而不是走 so 三件套; - 在 CI 中加入 ohos 编译、ELF 符号检查和 HAP 构建;
- 补充真机(x86_64)与其他设备 ABI 的验收矩阵和自动化截图脚本;
- 跟踪社区 Compose for OpenHarmony 制品线,版本对齐后评估 UI 层接回方案。
九、总结
9.1 核心难点回顾
text
上游 foundation + int-ui-standalone(主题模型 + Compose UI 混合)
→ 路线判定:无纯引擎 → cmp_qrose"模型内核提取"
→ 工具链钉旧线 Kotlin 2.2.21-1.0.0(Gradle 8.14.1)
→ 根构建替换 + buildSrc 改名规避 + Compose 源码隔离 composeMain
→ 主题数据管线(expUI JSON 快照 + 生成脚本,与上游同源)
→ ohosArm64 / ohosX64 KLIB + 24 项 jvmTest 对账
→ C ABI(@CName × 1,整包 JSON)+ 组件样式索引表
→ C++ N-API(DisposeString 纪律)
→ ArkTS JSON 解析
→ ArkUI 真机四 Tab 验收(调色板 / 主题 / 组件 / 查询 + 明暗切换)
9.2 封装层次
- 内核层:
ThemeColorPalette索引与 lookup 语义、GlobalColors/GlobalMetrics/TextStyleData/ThemeDefinition,包名与上游对齐; - 数据层:生成的 IntUI Light / Dark 调色板常量(与上游构建期生成物逐位一致)+
IntUiThemes装配器; - Native 层:生成双 ABI 动态库并通过 1 个 C ABI 输出整包 JSON;
- N-API 层:完成参数检查、字符串转换和 Native 内存释放;
- ArkTS 层:管理页面状态、JSON 建模、明暗切换、查询分发与错误文本;
- DevEco 层:完成 CMake、HAP、签名、安装和真机运行。
9.3 三条经验
- 适配路线由"库的依赖形态"决定,不能套模板:KStore 有纯引擎加 kotlinx 制品线,正确答案是整库真编译;Jewel 全库 Compose 化,正确答案是模型内核提取------两者的共同底线是"真机上的数据 / 行为由上游原版语义决定",整库也好内核也好,手抄就是漂移;
- 数据型库的交叉验证要钉到"数值":24 项用例里最有说服力的是全局色逐项等于上游索引默认值、色阶形状等于 expUI JSON、lookup 越界行为与上游一致------测试即对账,而不是覆盖率数字;
- 构建期陷阱要在"第一次构建"前排掉:buildSrc 自动构建、init 脚本仓库重排、嵌套注释、ArkTS 严格模式字面量------这些坑都不在 Kotlin 或 ArkTS 的语言面里,而在工具链接缝上,且全部只在真实构建时暴露。
9.4 适配成果
当前适配已完成:
- Jewel
0.27.0(上游 main @ 8d24db5b)主题模型内核的双 ABI Kotlin/Native 编译(8 个手写内核文件 + 2 个生成调色板 + 24 项交叉验证); - 1 个 C ABI 符号(
JewelThemeInfo,llvm-nm验证全局 T 符号)和 C++ N-API 包装层; - DevEco Stage ArkUI 四 Tab 验收页面(调色板、主题、组件、查询、明暗切换、版本握手);
- 24 项 JVM 交叉验证全绿 + HAP 构建(双 ABI 六件套 so,约 6.9MB)、签名、设备安装和真机验证;
- AtomGit 项目文档、OpenHarmony README(中英双份)、调色板生成管线和本地效果图。
适配过程 FAQ
以下问题来自
ohosArm64目标接入、NAPI 桥接、HAP 构建与真机验收的实际流程,按出现频率排序。
Q1:DevEco Studio Sync 失败或 ohpm 依赖下载超时,怎么处理?
- 确认 DevEco Studio 版本与工程
build-profile.json5的compatibleSdkVersion一致; - 在
File > Settings > Build, Execution, Deployment > Ohpm配置可用的 ohpm 仓库镜像或代理; - 删除
oh_modules、oh-package-lock.json5与.hvigor缓存后重新 Sync; - 公司网络需要代理时,同时给 ohpm 与 hvigor 配置
http.proxy。
Q2:CMake 报 Missing .../libxxx.so 或找不到 Kotlin/Native 动态库?
- 先在
example目录执行./gradlew :nativeApp:linkDebugSharedOhosArm64(模拟器用linkDebugSharedOhosX64); - 确认
libxxx.so与libc++_shared.so已复制到ohos/entry/libs/<abi>/; - 检查
entry/build-profile.json5的abiFilters与设备 ABI 是否一致(真机arm64-v8a,模拟器x86_64); - 清理
entry/.cxx与entry/build后重试。
Q3:ArkTS 报 Cannot find module 'libxxx_napi.so' 或调用返回 undefined?
- 确认 CMake 的
add_library(<target> SHARED ...)名称与import ... from 'libxxx_napi.so'完全一致; - 解包 HAP,确认
libs/<abi>/libxxx_napi.so已打包; - 删除
entry/build、entry/.cxx与oh_modules后重新构建; - 检查
module.json5的srcEntry与页面路径是否正确。
Q4:真机安装报 signature verification failed 或 install failed?
- 在 DevEco Studio 的
Signing Configs配置证书、Profile 与密码; bundleName必须与签名 profile 中的 bundle name 一致;- 设备需加入 profile 的
deviceIds,调试证书有效期需覆盖当前时间; - 用
hdc list targets确认设备在线,再执行hdc install -r xxx-signed.hap。
Q5:应用启动白屏、闪退或 NAPI 调用崩溃?
- 用
hdc hilog | grep -i "napi\|kotlin"查看原生日志; - 确认
libc++_shared.so与 Kotlin/Native 工具链版本匹配,且 ABI 与设备一致; - 检查 C ABI 返回的字符串是否由 Native 侧分配、ArkTS 侧复制后是否调用
XxxFree释放; - 在 DevEco 的 Native 调试配置中附加
lldb,定位空指针与越界访问。
Q6:Gradle 报 ohosArm64 目标不存在或 Kotlin/Native 编译失败?
- 使用鸿蒙发行版 Kotlin(如
2.2.21-1.0.0),并在settings.gradle.kts配置 eazytec 仓库; - Gradle 建议 8.x、JDK 21,版本不匹配时先统一工具链;
- 确认
kotlin { ohosArm64() }已应用,且local.properties的 SDK 路径有效; - 清理
~/.gradle/caches中对应版本的 Kotlin/Native 缓存后重试。
Q7:HAP 构建报 SDK 版本不匹配或 compatibleSdkVersion 错误?
- 对齐 DevEco Studio、HarmonyOS SDK 与
build-profile.json5中的compatibleSdkVersion; - 升级 DevEco Studio 后同步升级 hvigor 与 SDK,避免
modelVersion与 IDE 不匹配; - 命令行构建时设置
DEVECO_SDK_HOME指向 DevEco 的sdk目录。
Q8:命令行 hvigor 构建失败,但 DevEco 里能构建?
- 使用 DevEco 自带的 node 与 hvigor:
"$DEVECO/Contents/tools/node/bin/node" "$DEVECO/Contents/tools/hvigor/bin/hvigorw.js" ...; - 设置
JAVA_HOME、NODE_HOME、DEVECO_SDK_HOME; - 加上
--no-daemon排除守护进程缓存影响,并清理entry/build与.hvigor。