开源鸿蒙平台 CMP 三方库「flagpack-compose」适配全流程

本文记录 flagpack-compose 接入 OpenHarmony 的完整过程,覆盖国旗数据集盘点、Valkyrie 生成代码的纯 Kotlin 数据抽取、ohosArm64 / ohosX64 目标、Kotlin/Native 动态库、C ABI/N-API 桥接、扫描线光栅化、ArkUI 真机页面、HAP 构建和真机验收。

flagpack-compose 是基于 Compose Multiplatform 的国旗图标库:把 flagpack-core 的 250+ 国家/地区国旗 SVG 用 Valkyrie 转成 Compose ImageVector Kotlin 代码(零资源文件、零运行时加载),对外提供 FlagIcons.Small/Medium/Large.<Country> 三档尺寸扩展属性,ImageVector.name 中编码 Country:Alpha2:Alpha3:Numeric:Size 元数据。本次适配与 KStore 的"真编译整库"路线有本质区别:这个库的全部源码就是 Compose 代码(768 个 ImageVector 文件),而鸿蒙社区 CMP 停在 1.9.2、ImageVector 依赖 androidx.compose.ui.graphics------因此采用社区早期路线(qrose / Vico / Reorderable 的"核心复用 + UI 隔离"):把 764 面国旗的几何数据与 ISO 3166 元数据抽取成纯 Kotlin 数据集原样编译成 OpenHarmony KLIB ,768 个上游 ImageVector 文件平移到不编译的 composeMain,鸿蒙侧由 Kotlin/Native 扫描线光栅化器复现矢量语义(填充/缠绕规则/渐变/描边/圆弧/裁剪),ArkUI 页面作为验收宿主。

项目地址: AtomGit/oh-tpc/flagpack-compose

上游仓库: niyajali/flagpack-compose

开发工具: 华为云码道

环境安装: 环境安装指南

一、背景

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

flagpack-compose 为 Compose Multiplatform 应用提供"国家/地区 ↔ 国旗图标"这一件事的完整答案:FlagIcons.Medium.China 一行拿到 ImageVector 直接丢进 Image,三档尺寸(Small 16×12、Medium 20×15、Large 32×24,viewport 单位)覆盖导航栏/列表/详情三种排版密度,元数据(两字码/三字码/ISO 数字码)编码在 ImageVector.name 里可反解析。上游用 Gradle 插件(flagpack.icons)从上游 git 仓库同步 SVG 并经 Valkyrie CLI 生成 Kotlin 源码,编译期全量内联、运行时零加载。KMP 项目把这些放在 commonMain,由 Android、iOS、桌面、Web 共享。OpenHarmony 应用同样需要这套能力(注册页国籍选择器、地址簿国旗、多语言导航),但上游构建(Kotlin 2.2.21 / CMP 1.9.3 / AGP 8.12.3 / Gradle 9.2.1)不会生成 OpenHarmony 变体。

如果只把示例页面改成 ArkTS,页面可以显示图片,却无法证明公共数据集在 OpenHarmony ARM64 设备上完成编译、链接和执行。适配需要同时验证数据集、矢量语义光栅化、Native 动态库、C ABI、N-API、ArkUI 页面和 HAP 交付链路。

障碍 具体问题
目标缺失 必须增加 ohosArm64() / ohosX64() 才能生成 OpenHarmony KLIB。
源码即 Compose 库的 768 个源文件全部 import androidx.compose.ui.graphics.*,社区 CMP 1.9.2 与上游 CMP 1.9.3 版本对不齐,整库真编译等于再发行一套 Compose。
数据形态特殊 值钱的不是算法而是几何数据集(6378 条路径 + ISO 元数据),需要一条无损抽取管线。
绘制语义要复现 even-odd / non-zero 双缠绕规则、线性渐变、描边、圆弧、非矩形裁剪遮罩,ArkTS 侧没有现成矢量引擎。
语言边界不同 ArkTS 不能直接持有 Kotlin 对象,需要 C ABI、C++ N-API 和 JSON。
交付链路复杂 .so、CMake、N-API、HAP、签名和 hdc 都要单独验收。

因此,本项目把边界放在:国旗几何数据集的 ohos 编译、根构建替换(cmp_qrose 模式)和 C ABI/N-API 桥接层 + KN 光栅化。矢量语义由上游数据原样负责(抽取统计写死进测试断言防漂移),ArkTS 只负责调用 Native、解析 JSON 和展示结果。

1.2 库提供的能力

Demo 提供一个真实的国旗浏览器,对应桥接的全部入口:

  • 列表(list) :flagpackList({size:"m"}) 一次返回 255 面国旗的 {code, alpha3, numeric, name} 元数据(不含几何,列表场景一次拿全);
  • 渲染(render) :flagpackRender({code:"CN",size:"m",scale:12}) 按两字码/三字码/数字码/英文名任一查找,KN 光栅化输出 PNG Base64;
  • 三档尺寸 :S 16×12 / M 20×15 / L 32×24(viewport),scale 1...32 控制输出像素(像素 = viewport × scale);
  • 完整绘制语义:6378 条路径里的每一类特征都支持------纯色填充(even-odd / non-zero)、线性渐变(巴西/塞浦路斯等 8 面)、描边与圆端帽(198 面)、SVG 圆弧(肯尼亚/墨西哥等 8 面)、半透明 alpha、非矩形裁剪遮罩(英国米字旗等 58 面 1113 条路径)。

响应包含:

字段 作用
ok 操作是否成功,失败时 error 带原文。
code / alpha3 / numeric / name 命中旗帜的 ISO 3166 元数据(来自上游 ImageVector.name 的无损拆解)。
size / width / height 档位与输出像素尺寸。
bytes / pngBase64 PNG 字节数与 Base64(ArkTS createImageSource 一次解码成 PixelMap)。

数据侧完整特征(渐变/描边/缠绕/圆弧/裁剪/alpha)全部随抽取进入 .so,JVM 侧保留 14 项 jvmTest 交叉验证用例 + 764 面全量渲染证据,证明 ohos .so 里执行的绘制数据没有漂移。

1.3 实现适配

维度 要求
数据复用 764 面国旗的几何与元数据经无损抽取管线进入 commonMain(12 个数据分片约 2.6MB),抽取统计(6378 路径/10 渐变/620 描边/3804 even-odd/70 圆弧/1113 裁剪)写死进测试断言;上游 768 个 ImageVector 文件 git mv 到 composeMain 不编译(纯 rename,历史可追)。
平台目标 根构建直接声明 ohosArm64 + ohosX64,保留 jvm 作交叉验证,库模块出 KLIB,example/nativeApp 出双 ABI 动态库。
构建隔离 根构建替换为极简 ohos 构建(cmp_qrose 模式);上游 sample/* 五端示例与 build-logic(flagpack.icons 同步插件)留盘不进构建。
桥接稳定 使用 2 个 C ABI(FlagpackRender / FlagpackList)和 UTF-8 JSON,不传递 Kotlin 对象地址;异常永远不跨 FFI。
绘制保真 扫描线填充(双缠绕规则)+ 4×4 超采样抗锯齿 + 渐变逐像素求值 + 描边扫掠 + 裁剪遮罩;JVM 侧镜像同一份光栅化源码出可复现 PNG 证据。
UI 完整 三 Tab 覆盖全部桥接入口:懒加载网格、四码搜索、尺寸切换、收藏持久化、详情大图、版本与社区信息。
仓库规范 项目说明、文章、效果图和源码链接统一使用 AtomGit。

本项目的 ArkUI 页面不是 Compose Multiplatform UI 实现。flagpack-compose 上游本身只出 ImageVector 资产不碰容器,适配面天然落在"数据 + 出图";ArkTS Demo 只是把桥暴露的国旗浏览器画出来。

二、实现路线图

text 复制代码
第 1 阶段:项目初始化     ── 盘点模块与依赖边界、确认 768 文件的 Compose 依赖形态、确定"数据抽取"路线
第 2 阶段:目标与依赖打通 ── 根构建替换、eazytec Maven 置前、Kotlin 2.2.21-1.0.0 钉版、Gradle 降 8.14.1
第 3 阶段:数据抽取管线   ── ExtractFlagData.java 词法+递归下降、DSL 归一化、矩形裁剪识别、12 分片生成
第 4 阶段:纯 Kotlin 核心 ── FlagModel / Flagpack 注册表 / FlagPathParser(贝塞尔与圆弧扁平化)
第 5 阶段:光栅化与桥接   ── 扫描线 + 超采样 + PNG、@CName × 2、C++ N-API、内存释放纪律
第 6 阶段:ArkUI 宿主     ── 懒加载网格、四码搜索、尺寸切换、收藏、详情大图
第 7 阶段:示例与验证     ── HAP、签名、设备安装、真机运行和效果图

三、逐步实现过程

第 1 阶段:项目初始化

1.1 工程边界
text 复制代码
lib/                                国旗数据与纯 Kotlin 核心(进 ohos 构建,jvm + ohosArm64 + ohosX64)
lib/src/commonMain/.../core/        FlagModel / Flagpack / FlagPathParser
lib/src/commonMain/.../core/data/   抽取生成的 12 个数据分片(勿手改)
lib/src/composeMain/.../icons/      上游 768 个 ImageVector 源文件(git mv 平移,不编译)
example/nativeApp/                  Kotlin/Native 动态库(Bridge + Rasterizer + PngEncoder)
example/harmonyApp/                 DevEco Stage、CMake N-API 和 ArkTS 页面
scripts/ExtractFlagData.java        可复现抽取器(JDK 单文件源码启动,零第三方依赖)
scripts/CheckPng.java               PNG 像素探针(交叉验证辅助)
README.OpenHarmony.md / _CN.md      鸿蒙适配说明(中英双份)
README.upstream.md                  上游原文保存
sample/ build-logic/                上游五端示例与同步插件,留盘不进 settings

构建拓扑走 cmp_qrose 模式 (根构建即 ohos 构建):settings.gradle.kts 只 include :lib 与 :example:nativeApp,rootProject.name = "ohos_flagpack-compose"。上游的 compose / compose-compiler / AGP / vanniktech 发布 / flagpack.icons 插件全部随根构建替换剥离------本 fork 不发 Maven Central。

1.2 工具链矩阵
项目 配置
Kotlin Multiplatform HarmonyOS Kotlin 2.2.21-1.0.0(eazytec Maven)
JDK 21(org.gradle.java.home 指向 Temurin jdk-21.0.12.101-hotspot)
OpenHarmony 目标 ohosArm64 + ohosX64(jvm 保留作交叉验证)
Gradle 8.14.1(上游 9.2.1 必须降级;2.2.21 旧线不支持 Gradle 9.x)
上游基线 Kotlin 2.2.21 / CMP 1.9.3 / AGP 8.12.3(对齐 HarmonyOS Kotlin 同代,源码级兼容成本为零)
DevEco product 5.1.0(18) 兼容
DevEco Studio 5.1.0 Release
ABI arm64-v8a + x86_64
Bundle org.terminator.ohos.flagpack
bash 复制代码
export JAVA_HOME="/path/to/jdk-21"
java -version
1.3 源码依赖度盘点
内容 文件数 依赖形态 处置
国旗 ImageVector 764 androidx.compose.ui.graphics.*(ImageVector/PathData/group/path) 数据抽取:几何与元数据进 commonMain,源文件平移 composeMain
FlagIcons / 三档 FlagList 4 Compose ImageVector 容器 随平移进 composeMain,鸿蒙侧由 Flagpack 注册表对应
build-logic 同步插件 ~15 JGit + KotlinPoet + Valkyrie CLI 留盘不进构建(重同步上游在上游环境跑)
sample 五端 --- Compose 容器(android/desktop/ios/web) 留盘不进构建

抽取前的关键侦察(决定管线设计):全库 DSL 命令集只有 moveTo / lineTo / horizontalLineTo / verticalLineTo(含 Relative 变体)、curveTo、arcTo(isMoreThanHalf, isPositiveArc)、close------没有 quadTo 与 reflective;路径参数只有 fill(SolidColor|Brush.linearGradient) / fillAlpha / pathFillType / stroke / strokeAlpha / strokeLineWidth / strokeLineCap;group 参数只有 clipPathData;命名色只有 White / Black / Red / Blue / Gray 五种。方言越窄,抽取器越可靠。

1.4 页面边界
text 复制代码
标题区          FLAGPACK COMPOSE · OHOS / 三 Tab 切换(浏览 / 收藏 / 关于)
搜索区          单行输入框(占位:名称 / 两字码 / 三字码 / 数字码)
尺寸区          S 16×12 / M 20×15 / L 32×24 三选一(切换即重渲)
状态行          如「255 / 255 面 · medium」
网格区          4 列 LazyForEach 懒加载,卡片 = 国旗缩略图(约 96×72)+ 两字码
详情层          点卡片弹出:大图(约 448×336)+ 元数据行 + 收藏按钮
关于区          数据规模、版本、社区链接

页面启动即调 flagpackList 拿元数据并按行缓存,缩略图进入视口才渲染(LRU 上限 320 张)。

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

2.1 路线判定(本适配的成败手)

KStore 那批库能"真编译整库",前提是社区 Maven 上有它们依赖的 kotlinx ohos 制品线。flagpack-compose 的依赖面是 androidx.compose.ui.graphics.vector.ImageVector------社区 CMP 停在 1.9.2(上游 CMP 1.9.3 / Kotlin 2.2.21 同代但制品不齐),把 ImageVector 编进 ohos 等于连 Compose 一起再发行。因此走社区已过审的数据隔离路线(qrose 先例):值钱的是数据不是框架 ------768 个文件里每面国旗就是一组路径(填充/渐变/缠绕规则/描边/圆弧/裁剪)+ ImageVector.name 里的 ISO 元数据,把它们无损抽成纯 Kotlin 数据集,矢量语义在鸿蒙侧由光栅化器复现。

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

上游 Kotlin 就是 2.2.21,与 HarmonyOS Kotlin 2.2.21-1.0.0 同代------版本主体天然一致,不需要 KStore 那样由依赖元数据反推。代价在 Gradle:2.2.21 旧线不支持 Gradle 9.x,上游 wrapper 9.2.1 必须降到 8.14.1(qrose 同款 wrapper)。JDK 21 经 gradle.properties 的 org.gradle.java.home 钉死(本机无 JAVA_HOME 也能构建)。

settings.gradle.kts 把 eazytec 仓库写在最前:

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_flagpack-compose"

include(":lib")
include(":example:nativeApp")

gradle.properties 同步写入 version=1.0.0-ohos.1、kotlin.native.ignoreDisabledTargets=true、kotlin.native.distribution.downloadFromMaven=true;根 build.gradle.kts 全局禁用 Ohos*Test* 任务。

2.3 编译纯 Kotlin 核心

lib/build.gradle.kts 收敛为纯 KMP 模块:

kotlin 复制代码
plugins {
    alias(libs.plugins.kotlin.multiplatform)
}

kotlin {
    explicitApi()                    // 上游即开启,抽取数据全部显式 public
    applyDefaultHierarchyTemplate()
    jvmToolchain(21)

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

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

实测:

bash 复制代码
./gradlew :lib:compileKotlinJvm :lib:compileKotlinOhosArm64 :lib:compileKotlinOhosX64   # BUILD SUCCESSFUL

764 面国旗(约 2.6MB 生成的 Kotlin 数据)一次编过三个目标,explicitApi 下零告警------抽取器的输出质量在编译期就被钉住。

第 3 阶段:数据抽取管线(技术难点)

3.1 抽取器设计

scripts/ExtractFlagData.java 用 JDK 11+ 单文件源码启动(零第三方依赖、零构建接入):

bash 复制代码
"$JAVA_HOME/bin/java" scripts/ExtractFlagData.java .

四层结构:词法器 (跳过注释/字符串,0x 十六进制、浮点、标识符、标点成 token)→ 定位 ImageVector.Builder(...)(读 name 按 : 拆元数据、读 viewport,其余跳过)→ .apply {} 体 (递归解析 group(clipPathData = PathData {...}) 裁剪栈与 path(参数...) 命令体)→ 生成(每档按名称排序切 4 片共 12 个 Kotlin 文件)。

3.2 DSL 归一化

命令体统一转绝对坐标串:

text 复制代码
M x,y | L x,y | C x1,y1,x2,y2,x,y | A rx,ry,rot,largeArc,sweep,x,y | Z

horizontalLineTo/verticalLineTo 与全部 Relative 变体在抽取期归一化掉;arcTo(isMoreThanHalf, isPositiveArc) 与 SVG large-arc-flag / sweep-flag 同名同义,直接映射。归一化换来的是运行期解析器可以做到 100 行量级(FlagPathParser),且光栅化器只见一种方言。

3.3 矩形裁剪识别(2768 → 1113 的坑)

几乎每面旗最外层都有一个 group(clipPathData = 全幅矩形)(Valkyrie 的归一化产物),语义上是空操作。第一版识别只匹配 4 点矩形,漏掉"4 角 + 回到起点 + Z"的 5 点变体,导致 2768 条路径被误判为非矩形裁剪、裁剪数据白白翻倍。放宽为"任意角起步、任意绕向、允许闭合点重复"后,真实非矩形裁剪降到 1113 条(58 面)------英国米字旗斜线、韩国太极圆区这类真裁剪全部保留,数据瘦身近半。

3.4 抽取统计(写死进测试防漂移)
特征 数量 说明
路径总数 6378 三档合计
even-odd 填充 3804 缠绕规则必须双支持
描边路径 620 圆端帽 18、strokeAlpha<1 共 35
线性渐变 10 处 Brazil / Cyprus / SouthGeorgia 等 8 面
fillAlpha<1 16 半透明叠层
圆弧命令 70 Bahrain / Belize / BIOT / Burundi / FrenchGuiana / Kenya / Mexico / PNG 8 面
非矩形裁剪路径 1113 58 面(United Kingdom / South Korea 等)
国旗数 255 / 255 / 254 large 档上游即缺 England,保持一致

数据分片形态:路径串为 const 字符串进常量池,构造参数只写非默认值,每片 ≤ 64 面规避单函数 64K 字节码上限。

第 4 阶段:纯 Kotlin 核心

4.1 数据模型(FlagModel.kt)
kotlin 复制代码
public enum class FlagSize(val label: String, val viewportWidth: Float, val viewportHeight: Float) {
    SMALL("small", 16f, 12f), MEDIUM("medium", 20f, 15f), LARGE("large", 32f, 24f),
}

public class FlagPath(
    public val pathData: String,                       // 归一化路径串
    public val fill: Long? = null,                     // ARGB 纯色
    public val fillGradient: FlagLinearGradient? = null, // 优先于 fill
    public val fillAlpha: Float = 1f,
    public val evenOdd: Boolean = false,               // false = non-zero
    public val stroke: Long? = null,
    public val strokeAlpha: Float = 1f,
    public val strokeWidth: Float = 0f,
    public val roundCap: Boolean = false,
    public val clipIndices: List<Int> = emptyList(),   // 引用 FlagDefinition.clips
)

public class FlagDefinition(
    public val name: String, public val alpha2: String,
    public val alpha3: String, public val numeric: String,
    public val viewportWidth: Float, public val viewportHeight: Float,
    public val paths: List<FlagPath>,
    public val clips: List<String> = emptyList(),
)
4.2 注册表(Flagpack.kt)

按档惰性加载(flagDataSmall1() + ... + flagDataSmall4()),对外提供 flags / infos / count / findByAlpha2 / findByAlpha3 / findByNumeric / findByName / find(find 按两码 → 三码 → 数字码 → 名称依次尝试,桥的查询语义直接来自这里)。元数据列表走 infos() 不触几何。

4.3 路径解析器(FlagPathParser.kt)

支持 M/L/H/V/C/A/Z 全大小写(归一化数据只需大写,小写为容错保留);三次贝塞尔按弦长自适应 20~80 步扁平化;圆弧走标准 SVG 端点→圆心参数化、每步 ≤5° 采样。输出 FlagGeometry(闭合多边形列表 + bounds),供光栅化与测试共用。

第 5 阶段:光栅化与原生桥接(技术难点)

5.1 扫描线光栅化器(Rasterizer.kt)
text 复制代码
viewport 坐标 --sx=width*SS/vw--> 超采样像素空间(SS=4)
    ↓
逐扫描线求边交点 → 按 x 排序 → even-odd 按对取区间 / non-zero 带向缠绕累计
    ↓
区间光栅化进超采样位图(像素中心判定) & 裁剪遮罩按位与(每旗缓存)
    ↓
合成:16 采样覆盖率 × 路径 alpha × 渐变/纯色 → source-over 叠加
    ↓
PngEncoder(RGBA 8bit + stored zlib)→ Base64

四类绘制语义的落地:填充 按 evenOdd 走对偶区间或缠绕累计;渐变 逐输出像素中心反投影回 viewport 沿轴求 t、分段线性插值 colorStops;描边 把折线段扫掠成同向缠绕四边形(重叠不加深),StrokeCap.Round 在顶点补圆盘近似圆角连接;裁剪每面旗的非矩形裁剪多边形按索引缓存超采样位图,路径覆盖与裁剪按位与------英国米字旗的斜十字、韩国的太极圆区都靠它收在旗面内。抗锯齿靠 4×4=16 个二值采样下采样,不需要子像素灰度缓冲。

5.2 Kotlin/Native 导出函数
kotlin 复制代码
@CName("FlagpackRender")
fun flagpackRender(request: String): String   // {"code","size","scale","background"} → PNG Base64

@CName("FlagpackList")
fun flagpackList(request: String): String     // {"size"} → 全量元数据 JSON
C ABI 符号 ArkTS 方法 覆盖能力
FlagpackRender flagpackRender(req) Flagpack.find 查找 + 光栅化 + PNG
FlagpackList flagpackList(req) Flagpack.infos 元数据列表(255 面一次拿全)

列表与渲染拆成两个符号:逐面 render 做列表太重,列表页一次 JSON 拿全元数据、缩略图按需渲染 + LRU 缓存。桥内 60 行 MiniJson(继承 cmp_qrose 审核模式),不上 kotlinx.serialization------klib 依赖为零。每个导出整体 try/catch,Kotlin 异常永远不跨 FFI。

5.3 C++ N-API 方法分发
cpp 复制代码
napi_property_descriptor desc[] = {
    {"flagpackRender", nullptr, NapiFlagpackRender, nullptr, nullptr, nullptr,
     static_cast<napi_property_attributes>(napi_default | napi_enumerable), nullptr},
    {"flagpackList", nullptr, NapiFlagpackList, nullptr, nullptr, nullptr,
     static_cast<napi_property_attributes>(napi_default | napi_enumerable), nullptr},
};

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

5.4 CMake 和类型声明
cmake 复制代码
add_library(ohosflagpack SHARED IMPORTED)
set_target_properties(ohosflagpack PROPERTIES
  IMPORTED_LOCATION ${KN_SO}
  IMPORTED_NO_SONAME TRUE)

add_library(flagpack_napi SHARED napi_init.cpp)
target_link_libraries(flagpack_napi PUBLIC
  libace_napi.z.so
  libhilog_ndk.z.so
  ohosflagpack)

OHOS_ARCH 为空或 libohosflagpack.so 缺失直接 FATAL_ERROR(提示先跑 link 任务);$ORIGIN rpath 加 POST_BUILD 拷贝保证 NAPI so、KN so、libc++ 三件套同目录。Gradle 侧 linkDebugSharedOhosArm64 / OhosX64 任务 finalizedBy 两个 Copy 任务,把 KN so 与 ~/.konan/dependencies 里匹配 triple 的 libc++_shared.so 自动回填 entry/libs/<abi>/。

typescript 复制代码
export const flagpackRender: (request: string) => string;
export const flagpackList: (request: string) => string;

第 6 阶段:ArkUI 宿主

6.1 列表、搜索与懒加载
typescript 复制代码
private reload(): void {
  const all = this.allRowsForCurrentSize();          // flagpackList 结果按档缓存
  const q = this.query.trim().toLowerCase();
  const list = all.filter((row: FlagRow): boolean => /* 名称/两码/三码/数字码四码任一命中 */);
  this.dataSource.reload(list);                       // LazyForEach 数据源
  this.status = `${list.length} / ${all.length} 面 · ${this.sizeLabel}`;
}

网格用 Grid + LazyForEach(IDataSource):只有进入视口的卡片才挂载,卡片 aboutToAppear 里同步调 flagpackRender 出缩略图(scale 6/5/3 ≈ 96×72),Promise 解码 PixelMap 后写入进程级 LRU 缓存(上限 320 张,数组维护插入序避免迭代器协议兼容性问题)。

6.2 详情、收藏与关于

点卡片弹详情层:scale 28/22/14 渲染约 448×336 大图 + 元数据行(名称/两码/三码/ISO 数字码)+ 收藏按钮;收藏写入 AppStorage(会话级持久),收藏 Tab 联动展示。关于 Tab 给出数据规模(255/255/254、渐变/描边/裁剪支持)、版本(1.0.0-ohos.1、上游 1.0.0、HarmonyOS Kotlin 2.2.21-1.0.0)与社区入口。

第 7 阶段:示例与验证

7.1 工程结构
text 复制代码
example/
├── nativeApp/
│   ├── build.gradle.kts                      双 ABI sharedLib(baseName=ohosflagpack)+ cxxShared + copyKnSo*
│   └── src/ohosMain/kotlin/example/nativeapp/
│       ├── Bridge.kt                         @CName × 2 + MiniJson + Base64
│       ├── Rasterizer.kt                     扫描线 + 超采样 + 渐变 + 描边 + 裁剪
│       └── PngEncoder.kt                     最小 PNG(stored zlib)
└── harmonyApp/
    ├── build-profile.json5                   signingConfigs 留空(签名由 DevEco 配置回填)
    ├── AppScope/app.json5                    bundleName org.terminator.ohos.flagpack、versionName 1.0.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/libohosflagpack_api.h
        ├── src/main/cpp/types/libflagpack_napi/{index.d.ts, oh-package.json5}
        └── src/main/ets/pages/Index.ets      ArkUI 验收页面(三 Tab)
7.2 构建、签名和安装
bash 复制代码
./gradlew :lib:jvmTest                                            # 14 项交叉验证 + 764 面全量渲染
./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.flagpack -m entry

四、完整代码对照

4.1 整体架构

text 复制代码
flagpack/icons/**(上游 ImageVector,768 文件 → composeMain 不编译)
    │ scripts/ExtractFlagData.java 无损抽取
flagpack.core/**(纯 Kotlin 数据集 + 解析器,进 ohos 构建)
    │ FlagDefinition / FlagPath / Flagpack / FlagPathParser
Rasterizer.kt + PngEncoder.kt(KN 光栅化 + PNG)
    │ FlagpackRender / FlagpackList
libflagpack_napi.so
    │ flagpackRender / flagpackList
Index.ets
    │ JSON parse + PixelMap 解码 + @State 更新
    ▼
ArkUI 真机页面(三 Tab 国旗浏览器)

4.2 文件清单

文件 职责
settings.gradle.kts 极简 ohos 构建、eazytec Maven 置前、ohos_flagpack-compose、sample/build-logic 移出
gradle/libs.versions.toml 只留 Kotlin 2.2.21-1.0.0 一个插件
build.gradle.kts(根) group io.github.niyajali、版本 1.0.0-ohos.1、全局禁用 Ohos*Test* 任务
lib/build.gradle.kts 纯 KMP:jvm + ohosArm64 + ohosX64,explicitApi
lib/.../core/FlagModel.kt 等 数据模型 / 注册表 / 路径解析器(手写核心)
lib/.../core/data/FlagData*{1..4}.kt 12 个抽取数据分片(生成物,勿手改)
scripts/ExtractFlagData.java 可复现抽取器(词法 + 递归下降 + 归一化 + 矩形裁剪识别)
example/nativeApp/build.gradle.kts 双 ABI sharedLib、cxxShared 定位 libc++、copyKnSo* 回填
nativeApp/.../Bridge.kt 2 个 C ABI、MiniJson、错误通道
nativeApp/.../Rasterizer.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 验收页面(懒加载网格/搜索/收藏/详情)
README.OpenHarmony_CN.md 鸿蒙适配主文档(22 节)

4.3 关键 API 对照

层次 API 作用
数据 Flagpack.find(size, "CN") 两码/三码/数字码/名称任一查找
数据 Flagpack.infos(size) 255 面元数据列表(不含几何)
解析 FlagPathParser.parse(pathData) 归一化路径串 → 扁平化多边形 + bounds
光栅化 Rasterizer.render(flag, w, h, bg) 完整矢量语义 → PNG 字节
Native FlagpackRender / FlagpackList C ABI JSON 导出
N-API flagpackRender(req) / flagpackList(req) 向 ArkTS 暴露能力
ArkTS reload() / openDetail(row) 列表过滤与详情渲染

上游接口与鸿蒙桥的对应:FlagIcons.Medium.China(ImageVector)↔ flagpackRender({code:"CN",size:"m"});三档 *FlagList ↔ flagpackList;ImageVector.name 的 China:CN:CHN:156:Medium ↔ 响应字段 name/code/alpha3/numeric/size。

4.4 ArkTS 与 Kotlin 的边界

typescript 复制代码
const list = JSON.parse(flagpackList(JSON.stringify({ size: 'm' })));
const res = JSON.parse(flagpackRender(JSON.stringify({
  code: 'CN', size: 'm', scale: 12, background: '#FFFFFF'
})));
const bytes = new util.Base64Helper().decodeSync(res.pngBase64);
const pm = await image.createImageSource(bytes.buffer.slice(...)).createPixelMap();
kotlin 复制代码
val size = when (json.str("size")) { "s","small" -> SMALL; "l","large" -> LARGE; else -> MEDIUM }
val flag = Flagpack.find(size, query) ?: return notFound(query)
val image = Rasterizer.render(flag, (flag.viewportWidth*scale).toInt(), (flag.viewportHeight*scale).toInt(), background)

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

五、关键决策说明

决策 1:数据抽取而不是整库真编译

KStore 真编译整库的前提是 kotlinx ohos 制品线齐备;flagpack-compose 的依赖面是 androidx.compose.ui.graphics,社区 CMP 1.9.2 与上游 1.9.3 对不齐,真编译等于再发行 Compose。而库的价值在数据(几何 + 元数据),抽取管线一次完成"框架剥离":6378 条路径的特征计数写死进 jvmTest,抽取与上游的任何漂移在测试期就炸------数据语义的可信度不靠肉眼靠断言。

决策 2:Kotlin 2.2.21-1.0.0 天然同代,Gradle 降 8.14.1

上游 Kotlin 就是 2.2.21,与 HarmonyOS Kotlin 同代,不需要反推钉版;但 2.2.21 旧线不支持 Gradle 9.x,上游 wrapper 9.2.1 必须降到 8.14.1------与 KStore(2.4.20 线沿用 9.x)正好相反,两条工具链线的经验不能互相套用。

决策 3:composeMain 隔离 + 上游遗留留盘(cmp_qrose 模式)

768 个 ImageVector 文件 git mv 到 composeMain(768 个纯 rename,零内容变化,历史可追),等社区 compose.ui ohos 制品与 CMP 对齐后整体接回 commonMain,光栅化降级为 fallback。上游 sample/* 五端示例与 build-logic 同步插件留盘不进 settings.gradle.kts------它们是上游工作流的一部分,不是鸿蒙构建的一部分。

决策 4:路径方言归一化 + const 字符串 12 分片

抽取期把 H/V/Relative/arcTo 全部归一化为绝对 M/L/C/A/Z,运行期解析器百行量级;数据分片每片 ≤ 64 面、路径串进常量池,规避 Kotlin 单函数 64K 字节码上限;构造参数只写非默认值,2.6MB 数据在 explicitApi 下零告警编过三个目标。

决策 5:光栅化器 jvmTest 镜像交叉验证

光栅化器放 example/nativeApp(对齐 qrose 过审结构:lib = 数据/语义,example = 出图与桥),同时把 Rasterizer.kt / PngEncoder.kt 镜像到 lib/src/jvmTest(文件头声明镜像关系)------同一份数据、同一份渲染实现,在 JVM 上跑出 764 面全量 PNG 与 12 面特征矩阵作为可复现证据。本次适配正是靠"像素探针 + 视觉模型复核"抓出超采样坐标 4 倍 bug(旗面只画到画布 1/4),修复后 CN/US/KR/GB/BR 五面特征图全部通过。改 nativeApp 光栅化代码后同步镜像再跑测试,是本仓的纪律。

决策 6:列表与渲染拆两个 C 符号

255 面逐个 render 做列表太重;flagpackList 一次 JSON 拿全元数据(不含几何),缩略图按需渲染 + 进程级 LRU(上限 320)------列表页启动只渲染进入视口的卡片,滚动顺滑。

六、测试与验证

6.1 测试环境

本次验证使用 Windows(x64)、JDK 21(Temurin jdk-21.0.12.101-hotspot)、Gradle 8.14.1 wrapper、DevEco Studio 5.1.0 Release、OpenHarmony 命令行工具(hvigor / hdc)与鸿蒙真机(arm64)。

6.2 JVM 交叉验证

bash 复制代码
./gradlew :lib:jvmTest

14 项断言 + 764 面全量渲染全部通过:

测试类 钉住什么
datasetCounts / englandOnlyInSmallAndMedium 255/255/254,large 缺 England 与上游一致
metadataRoundTrip / alpha2UniquePerSize ImageVector.name 元数据无损拆解(China:CN:CHN:156)、两码唯一
everyPathParsesToPolygon / everyFlagCoversViewport 6378 条路径全部可解析、每面旗 bbox 覆盖视口(抽取完整性)
paintFeatureCounts 渐变 10 / 描边 620 / even-odd 3804 / 圆端帽 18 / alpha<1 计数逐项对齐抽取统计
clipIndicesAreSound 1113 条裁剪路径索引健全、裁剪串可解析、UK 斜线裁剪在位
arcFlagsParse Bahrain / Kenya / Mexico / PNG 圆弧路径解析无异常
RasterPreviewTest 764 面全部渲染出 PNG;12 面特征矩阵(CN/GB-UKM/BR/JP/KE/MX/SV/KR/ZA/VU/US/PT)落盘 lib/build/flag-previews/

特征矩阵每张对应一类绘制语义,配 scripts/CheckPng.java 像素探针做确定性校验(红旗面铺满画布、中心像素为正确颜色),再辅以视觉模型复核五面代表图(US 五十星、KR 太极圆弧、GB 裁剪、CN 五星、BR 渐变)------超采样坐标 bug 就是这条双重验证链抓出来的。

6.3 动态库与 HAP 验证

text 复制代码
:lib:compileKotlinJvm / compileKotlinOhosArm64 / compileKotlinOhosX64: BUILD SUCCESSFUL
linkDebugSharedOhosArm64 / linkDebugSharedOhosX64: BUILD SUCCESSFUL
llvm-nm -D: FlagpackRender / FlagpackList / libohosflagpack_symbols 均为全局 T 符号
copyKnSoArm64 / copyKnSoX64: libohosflagpack.so + libc++_shared.so 回填 entry/libs 双 ABI
BuildNativeWithCmake: finished(双 ABI 编出 libflagpack_napi.so)
CompileArkTS / PackageHap: finished
install bundle successfully
start ability successfully

libohosflagpack.so debug 未裁剪 arm64 约 13.2MB、x86_64 约 10.4MB(含 Kotlin/Native 运行时与 2.6MB 数据;release strip 后显著缩小);每个 ABI 三件套(libflagpack_napi.so / libohosflagpack.so / libc++_shared.so)齐备。

6.4 功能用例

  1. 启动进入浏览 Tab,状态行显示 255 / 255 面 · medium,4 列网格懒加载浮现缩略图(约 96×72,LRU 缓存滚动不重渲);
  2. 搜索 CN / CHN / 156 / china,四种编码均只剩 China------四码任一命中;
  3. 切 S / M / L,数量 255/255/254 联动,缩略图等高重渲;
  4. 点卡片弹详情:约 448×336 大图 + China · CN · CHN · ISO 156 元数据行;
  5. 详情点 ♥ 收藏 → 收藏 Tab 出现该旗;杀进程重进仍在(AppStorage);
  6. 搜 Brazil 看详情:金黄渐变球面平滑过渡(非平涂);
  7. 搜 United Kingdom(两字码为 GB-UKM)看详情:斜十字被旗面边界精确裁剪,四角不出界;
  8. 搜 South Korea 看详情:太极 S 形分割 + 上下小圆 + 四卦黑杠完整;
  9. 搜 El Salvador 看详情:徽记细线描边(含两点线段)清晰;
  10. 关于 Tab 展示数据规模、版本与社区入口。

6.5 验证结论

数据集、路径解析、光栅化、PNG、Native 动态库、C ABI、N-API、ArkUI 页面和 HAP 已完成链路验证。应用在鸿蒙真机上成功安装并进入前台,页面展示了真实的国旗浏览器会话:三档尺寸、四码搜索、懒加载网格、收藏与详情大图(见下节效果图)。

七、运行效果

7.1 真机截图

7.2 命令速查

bash 复制代码
git clone https://atomgit.com/oh-tpc/flagpack-compose.git
cd flagpack-compose
export JAVA_HOME="/path/to/jdk-21"
./gradlew :lib:jvmTest                                                     # 14 项交叉验证 + 764 面渲染
./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.flagpack -m entry
hdc shell hilog | grep Flagpack

八、遗留问题与改进方向

8.1 踩坑复盘

  1. 超采样坐标少乘 SS:光栅化几何乘的是输出像素比例而遮罩是 4 倍超采样网格,旗面只画到画布左上 1/4------用像素探针(确定性)+ 视觉模型(形状语义)双重验证抓出,教训是矢量渲染必须先写确定性像素断言再看图;
  2. java.lang.Math 在 Kotlin/Native common 代码直接 Unresolved reference,圆弧参数化改用 kotlin.math.PI;
  3. SolidColor(Color.White) 与 SolidColor(Color(0x...)) 括号数不同(命名色没有内层调用),抽取器按格式数括号必炸,必须按 token 序列走;
  4. Color(0x...) 的十六进制字面量跟在 Color( 后面,词法器不识别 0x 前缀就会把 xFFE31D1C 当标识符;
  5. 矩形裁剪有"4 角 + 回到起点 + Z"的 5 点变体,识别不放宽会把 2768 条路径误判非矩形(真值 1113),裁剪数据白白翻倍;
  6. arcTo 的命名参数是 isMoreThanHalf / isPositiveArc(androidx 传承),与 SVG large-arc-flag / sweep-flag 同义,不要凭 Compose 另一套 isForceToSmallArc 的记忆硬套;
  7. 英国两字码是 GB-UKM 不是 GB(上游映射自 flagpack-core 的 GB-UKM.svg),按惯例查 GB 必然 miss------桥不翻译编码,文档与示例统一用上游编码;
  8. large 档 254 面缺 England 是上游事实,适配纪律是保持一致并写测试断言,不"好心补数";
  9. El Salvador 有两点线段的纯描边装饰路径,扁平化后不足 3 点、空填充是合法状态,测试断言要放行 stroke-only 路径;
  10. Kotlin .also { i = it.second } 返回接收者(Pair)而不是索引,读取器要用解构 val (v, ni) = ...;
  11. settings.gradle.kts 里 eazytec 双仓必须排在 pluginManagement 与 dependencyResolutionManagement 最前,否则配置期直接 Unresolved reference: ohosArm64;
  12. 2.2.21 工具链线必须配 Gradle 8.x(降到 8.14.1),上游 9.2.1 wrapper 直接 Unresolved reference------与 2.4.20 线"沿用 9.x"的经验相反,降不降 wrapper 由 Kotlin 线决定;
  13. KN 返回字符串必须 DisposeString 归还运行时,N-API 层不能 free();HAP 缺当前 ABI 的 so 报安装失败 9568347,abiFilters 必须双 ABI、三件套缺一不可。

8.2 已知问题

  • 鸿蒙侧无 Compose ImageVector:出图是 KN 光栅化近似------描边用"扫掠四边形 + 圆盘"近似圆角连接(非 miter join),渐变仅 linearGradient(数据集也只有 linear);
  • PNG 用 zlib stored 未压缩:448×336 详情图约 600KB、96×72 缩略约 8KB;要瘦身只改 PngEncoder 的 IDAT,协议不动;
  • composeMain 的 768 个上游文件不编译,社区 compose.ui ohos 制品与 CMP 对齐后接回 commonMain,光栅化降级为 fallback;
  • flagpack.icons 同步插件(build-logic)未进鸿蒙构建:重新同步上游国旗需在上游环境跑 syncAndGenerate 后重跑抽取器并同步测试计数;
  • ArkTS Demo 的收藏用 AppStorage(会话级持久),未接 Preferences;
  • jvmTest 的光栅化镜像是手工同步的副本,改 nativeApp 渲染代码后必须同步镜像再跑测试。

8.3 未来优化方向

  • release 裁剪:linkReleaseSharedOhos* + strip,预期 so 显著小于 debug 13.2MB;
  • 评估 .har 形态发布,让 ArkTS 工程直接依赖而不是走 so 三件套;
  • 在 CI 中加入 ohos 编译、ELF 符号检查和 HAP 构建;
  • 补充模拟器(x86_64)与其他设备 ABI 的验收矩阵和自动化截图脚本;
  • 光栅化器增加 miter join 与 RadialGradient(当前数据集未用到,预防上游同步后出现)。

九、总结

9.1 核心难点回顾

text 复制代码
上游 768 个 ImageVector 文件(全部依赖 androidx.compose.ui.graphics)
    → 路线判定:数据抽取而非整库真编译(社区 CMP 1.9.2 对不齐)
    → 工具链对齐 Kotlin 2.2.21-1.0.0(上游同代)+ Gradle 降 8.14.1
    → 根构建替换(cmp_qrose 模式)+ composeMain 隔离(768 纯 rename)
    → ExtractFlagData.java 无损抽取(归一化 / 矩形裁剪识别 / 特征计数钉死)
    → ohosArm64 / ohosX64 KLIB(2.6MB 数据 + 解析器 + 注册表)
    → KN 扫描线光栅化(缠绕 / 超采样 / 渐变 / 描边 / 裁剪)+ PNG
    → C ABI(@CName × 2)+ C++ N-API(DisposeString 纪律)
    → ArkTS JSON + PixelMap
    → ArkUI 真机三 Tab 验收(列表 / 搜索 / 尺寸 / 收藏 / 详情)

9.2 封装层次

  • 数据层:抽取生成的 FlagDefinition / FlagPath / FlagLinearGradient(上游几何与元数据 1:1);
  • 语义层:Flagpack 注册表(四码查询、按档惰性加载)与 FlagPathParser(贝塞尔/圆弧扁平化);
  • 光栅化层:扫描线 + 缠绕规则 + 4×4 超采样 + 渐变求值 + 描边扫掠 + 裁剪遮罩 + 最小 PNG;
  • Native 层:生成双 ABI 动态库并通过 2 个 C ABI 输出 JSON;
  • N-API 层:完成参数检查、字符串转换和 Native 内存释放;
  • ArkTS 层:懒加载网格、四码搜索、LRU 缓存、收藏与详情状态管理;
  • DevEco 层:完成 CMake、HAP、签名、安装和真机运行。

9.3 三条经验

  1. UI 资产类 CMP 库的适配单位是数据不是框架:当库的全部源码就是 Compose 代码时,"真编译整库"的前提(依赖制品齐备)不存在,正确路线是把资产抽成纯 Kotlin 数据集,矢量语义在鸿蒙侧复现------抽取统计写死进测试,数据可信度靠断言不靠肉眼;
  2. 工具链经验按线区分:上游 Kotlin 2.2.21 与 HarmonyOS Kotlin 同代让钉版零成本,但 2.2.21 线必须降 Gradle 8.14.1------与 2.4.20 线"沿用 9.x"相反;wrapper 降不降由 Kotlin 线决定,不能拿另一条线的结论套用;
  3. 渲染代码要有确定性验证链:像素探针(颜色/覆盖断言)→ 特征矩阵(每类绘制语义一面旗)→ 视觉模型复核(形状语义),三层叠起来才能在真机之前抓住超采样坐标这类"看起来能跑就是画错位置"的 bug;JVM 镜像同一份光栅化源码,让渲染证据可复现、可回归。

9.4 适配成果

当前适配已完成:

  • flagpack-compose 1.0.0 国旗数据集(764 面 / 6378 路径 / 三档尺寸 / ISO 3166 元数据)的双 ABI Kotlin/Native 编译(上游 ImageVector 源文件零改动平移 composeMain,768 个纯 rename);
  • 可复现的数据抽取管线 scripts/ExtractFlagData.java(归一化方言、矩形裁剪识别、12 分片生成);
  • 完整矢量语义的 KN 扫描线光栅化器(even-odd/non-zero、4×4 超采样、线性渐变、描边、圆弧、非矩形裁剪)与最小 PNG 编码器;
  • 2 个 C ABI 符号和 C++ N-API 包装层(llvm-nm 验证全局 T 符号);
  • DevEco Stage ArkUI 三 Tab 验收页面(懒加载网格、四码搜索、尺寸切换、收藏、详情大图);
  • 14 项 JVM 交叉验证 + 764 面全量渲染全绿 + HAP 构建(双 ABI 六件套 so)、签名、设备安装和真机验证;
  • 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。

参考文档

相关推荐
resh_people1 小时前
开源鸿蒙平台 CMP 三方库「Shapes」适配全流程
华为·开源·harmonyos
Rock Chin6971 小时前
LangBot 群机器人:三个 AI 角色分工迎新人、答群规、管广告
人工智能·机器人·开源·自动化
网络豆2 小时前
开源鸿蒙平台 CMP 三方库「jujubaSVG」适配全流程.md
华为·开源·harmonyos
2501_944525762 小时前
开源鸿蒙平台 KMP_CMP 三方库「assertk」适配全流程
华为·开源·harmonyos
能鈺CMS2 小时前
CSDN-从27篇文档还原一套CMS开发规范
开源·cms·内容运营·源码下载·内容付费
miofly2 小时前
GitHub 日榜趋势速报 | 2026-10-27
开源·github
冬奇Lab12 小时前
一天一个开源项目(第248篇):CleanYourTV —— 让 AI 编程助手通过 Wi-Fi 给安卓电视去广告、换上 Apple TV 风格主屏
开源·资讯
wflynn13 小时前
GitHub 今日推荐|gpu-unlock-ada:Ada 显卡张量算力翻倍解锁
开源·github·c
SL_staff14 小时前
JVS私有化文档系统上线避坑指南:5大运维与业务协同断点及检测加固实践
java·开源·全栈