HarmonyOS 7 JsonPulse 适配实录05:HAR预构建SO与ABI分发打包
前四篇把 JsonPulse 的能力链已经做得很像一个真正可用的 Native 服务。
01 解决 CMake、Node-API 模块注册和第一条 ArkTS → C++ 调用;02 把 String 输入换成 Uint8Array,明确 TypedArray、padding 和 Buffer Owner;03 用 napi_async_work 把 48MB JSON 移出 ArkTS 调用线程;04 再加 ThreadSafeFunction、进度、取消和 Retry,线程安全与资源生命周期也有了明确证据。
继续开发下去,我遇到的工程问题已经不再是“怎么解析 JSON”。
而是:
这套 Native 能力怎么从 JsonPulse Demo 里拿出来,变成别的 HarmonyOS 应用也能稳定依赖的包。
HarmonyOS 当前应用包模型里,HAR 是静态共享包,可以包含代码、C++ 库、资源和配置;Hvigor 也已经提供预构建库快速链接能力。另一方面,HarmonyOS 应用 Native 库使用随应用发布的 libc++_shared.so,官方明确提醒:如果 HAR 内 Native 库和宿主应用使用的 SDK / libc++_shared 版本不一致,可能出现 ABI 兼容问题。
所以 05 的核心不只是:
Build HAR
而是把下面四件事一起做完:
多 ABI 产物;
符号边界;
SDK / libc++ 对齐;
消费应用真实导入验证。
本轮统一数据:
taskId:
native_20261003_05
packageName:
@jsonpulse/native
moduleName:
jsonpulse_native
version:
1.0.0
harFile:
jsonpulse-native-1.0.0.har
sdkApi:
26
buildType:
Release
cxxStandard:
C++17
abis:
arm64-v8a
x86_64
arm64SoSize:
1.84MB
x86SoSize:
2.11MB
harSize:
4.62MB
exportedNativeModule:
libjsonpulse.so
arktsDeclaration:
index.d.ts
packageNameMatched:
true
sdkVersionAligned:
true
cxxAbiBoundary:
C_ONLY
duplicateLibcxxDetected:
0
unresolvedSymbols:
0
exportedSymbolViolations:
0
consumerImportSuccess:
true
consumerParseSuccess:
true
packageBuildCost:
4.8s
status:
PACKAGE_READY

一、先把 JsonPulse 从 entry 工程里拆成真正的 Library 模块
前四篇里的 libjsonpulse.so 一直属于主应用 entry。
05 新建:
library/jsonpulse_native/
目录里保留:
src/main/cpp/
native/
third_party/simdjson/
CMakeLists.txt
index.d.ts
oh-package.json5
主应用变成 Consumer。
这样做以后,最重要的变化不是目录好看,而是:
JsonPulse Native
必须在没有 Demo 私有代码的情况下独立构建。
如果 Native 模块偷偷依赖 entry 下某个头文件、某个资源路径,拆库时马上就会暴露。
二、HAR 能装 C++ 库,但不能把 ABI 风险一起藏起来
HarmonyOS 当前 HAR 定义明确允许包含 C++ 库。
这意味着 JsonPulse 可以把:
libjsonpulse.so
index.d.ts
ArkTS wrapper
配置
一起交付。
但“可以放进去”不等于“随便放进去”。
官方 C++ 标准库机制特别提醒:
系统库使用 libc++.so;
应用 Native 库使用 libc++_shared.so;
两者不能混用;
HAR 中 libc++_shared 版本如果和应用不一致,
可能产生兼容问题。
所以本轮第一条 Package Gate 是:
sdkVersionAligned=true
duplicateLibcxxDetected=0
HAR 和 Consumer 全部使用同一套 API 26 SDK 构建。
不会在包里私塞一份来源不明的旧 libc++_shared.so。
三、C++17、Release、隐藏实现符号都写在 Library 自己的 CMake
第一段代码解决的是:
让打包出来的 so 自己具备完整构建约束,不依赖 Consumer 帮忙补编译参数。
cmake_minimum_required(VERSION 3.16)
project(jsonpulse_native)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
add_library(jsonpulse SHARED
native/napi_init.cpp
native/jsonpulse.cpp
native/async_parse.cpp
native/progress_tsfn.cpp
third_party/simdjson/simdjson.cpp
)
target_compile_definitions(jsonpulse
PRIVATE
NDEBUG
)
target_compile_options(jsonpulse
PRIVATE
-fvisibility=hidden
)
target_link_libraries(jsonpulse
PUBLIC
libace_napi.z.so
libhilog_ndk.z.so
)
这里 -fvisibility=hidden 很关键。
三方库里大量 C++ 符号没有必要全部暴露到动态符号表。
真正对外的是:
Node-API 模块入口;
明确需要的 C ABI;
而不是整个 simdjson 内部类型。
四、跨模块 Native ABI 只保留 C 边界,不暴露 std::string
HarmonyOS 官方 C++ 兼容性文档明确建议 Native API 使用 C 接口隔离 C++ 运行环境。
所以 JsonPulse 即使内部大量使用:
std::vector
std::string
simdjson::dom::parser
对外也不暴露:
std::string Parse(...);
更不会让 Consumer 直接拿:
std::vector<Result>
跨 so 传。
项目侧记录:
cxxAbiBoundary=C_ONLY
真正的 ArkTS 调用还是 Node-API。
如果未来另一个 Native 模块需要复用 Core,则再提供稳定的:
extern "C"
POD struct
pointer + length
error code
接口。
五、多 ABI 产物必须在包发布前就验证
本轮 HAR 覆盖:
arm64-v8a
x86_64
产物:
arm64-v8a:
libjsonpulse.so
1.84MB
x86_64:
libjsonpulse.so
2.11MB
x86_64 主要用于当前模拟器 / 测试环境。
arm64-v8a 用于真机。
05 不把:
同一个 so
硬塞到所有 ABI
也不在运行时猜错架构以后再处理 dlopen 失败。
Build 阶段就要求每个目标 ABI 都有对应产物。
六、oh-package 的名字必须和 Consumer 依赖名一致
第二段代码解决的是:
Package Name、HAR 文件和 Consumer 依赖必须形成一个稳定身份。
{
"name": "@jsonpulse/native",
"version": "1.0.0",
"description": "JsonPulse native parser",
"main": "index.d.ts",
"keywords": [
"node-api",
"simdjson",
"native"
]
}
本轮:
packageNameMatched=true
HarmonyOS 当前 HAR 常见问题里,也明确把:
依赖名
和 HAR 内 oh-package name 不一致
列为常见集成错误之一。
这类问题应该在发布包探针阶段直接挡住。
七、Consumer 不应该重新知道 simdjson 的 include 路径
如果消费方还要配置:
third_party/simdjson/include
说明包并没有真正封装完成。
Consumer 只需要:
{
"dependencies": {
"@jsonpulse/native":
"file:../library/jsonpulse_native"
}
}
ArkTS:
import jsonpulse
from 'libjsonpulse.so'
如果另一个 Native 模块需要链接共享包中的预构建 so,HarmonyOS 当前 Hvigor 还支持基于模块名和 so 名生成 CMake Config Package,并通过:
moduleName::soName
方式快速链接。
消费侧不需要再手写 so 的绝对路径。
八、符号检查是 Native 包发布前最值得自动化的一步
“能打出 HAR”并不能证明 so 健康。
JsonPulse 在打包后执行符号与依赖检查,核心看:
DT_NEEDED
动态符号
未解析符号
意外暴露的 C++ 符号
项目脚本会调用 SDK LLVM 工具链:
llvm-readelf -d libjsonpulse.so
llvm-nm -D libjsonpulse.so
重点确认:
libace_napi.z.so
等依赖正确;
没有宿主找不到的私有系统 so;
没有一大片 simdjson C++ 符号外泄;
没有 unresolved symbol。
本轮:
unresolvedSymbols=0
exportedSymbolViolations=0
九、PACKAGE_READY 之前一定要跑一个“陌生 Consumer”
这是 05 最重要的一步。
如果只在 JsonPulse 原工程里:
build library
entry 引用同工程模块
很多隐式路径问题可能被掩盖。
所以我单独做一个最小 Consumer:
只安装 HAR;
只 import libjsonpulse.so;
只调用 parseSummary。
测试:
orders_2_4mb.json
12,480 records
最终:
consumerImportSuccess=true
consumerParseSuccess=true
这比“库模块自身编译成功”更接近真实分发。
十、HAR 的静态共享特性也要考虑包体积
HAR 是编译态复用。
如果多个 HAP 分别引用同一 HAR,代码和资源可能被重复打包。
JsonPulse 当前只有单 entry 消费,因此 HAR 合适。
如果未来:
多个 HAP
多个 Feature
共享同一大 Native 库
就需要重新评估:
HSP
是否更合适。
05 不把“HAR”当成所有场景的唯一答案,只根据当前发布形态选择。
十一、libc++_shared 的版本一致性必须进入发布检查
HarmonyOS 当前官方文档给出了一个很具体的风险:
如果 HAR 内使用的 libc++_shared.so 版本和应用不一致,可能出现符号缺失等兼容问题。
因此 Package Probe 记录:
sdkApi=26
sdkVersionAligned=true
duplicateLibcxxDetected=0
真正发布时还会把:
compileSdkVersion
compatibleSdkVersion
NDK revision
写进构建 Manifest。
以后出现:
symbol not found
可以先核对版本,而不是盲猜业务代码。
十二、DevEco 图里必须同时出现“库工程”和“消费工程”
开发图:

左侧同时能看到:
library/jsonpulse_native
consumer/entry
中间:
Library CMake
oh-package
Consumer link / dependency
右侧:
arm64-v8a PASS
x86_64 PASS
packageName PASS
SDK PASS
Consumer import PASS
Consumer parse PASS
底部日志最终:
harSize=4.62MB
status=PACKAGE_READY
十三、手机运行图不是“安装 HAR 按钮”,而是包探针结果
最终运行图:

它集中展示:
@jsonpulse/native
1.0.0
C++17
arm64-v8a:
1.84MB PASS
x86_64:
2.11MB PASS
HAR:
4.62MB
unresolvedSymbols=0
consumerImportSuccess=true
consumerParseSuccess=true
并明确提醒:
HAR Native 与宿主 SDK / libc++_shared
必须兼容。
最终:
PACKAGE_READY
十四、05 最后固定九组包体测试
第一组,Library 模块独立 Release 构建。
第二组,arm64-v8a so 存在并可加载。
第三组,x86_64 so 存在并可加载。
第四组,Package Name 与依赖名一致。
第五组,.d.ts 导出与 Native 方法一致。
第六组,未解析动态符号为 0。
第七组,C++ 实现符号没有越界暴露。
第八组,SDK / libc++ 版本对齐。
第九组,最小 Consumer 能真实解析一份 JSON。
全部通过以后:
PACKAGE_READY
才允许进入最终回归。
十五、下一篇只做最终验收,不再增加功能
JsonPulse 到现在已经覆盖:
CMake
Node-API
TypedArray
Buffer 生命周期
Async Work
Promise
ThreadSafeFunction
Progress / Cancel
HAR
多 ABI
符号检查
消费验证
06 不再增加新 API。
最后固定:
5 个数据集
2 个 ABI
4 轮
40 次
把正确性、异步链路、资源释放、符号、SDK 兼容和性能基线全部跑一遍。
到 06,这个系列正式收口。
十六、HAR 内部结构必须能被人直接读懂
05 不只看:
jsonpulse-native-1.0.0.har
这个文件存在。
构建完成以后还会解包检查:
ArkTS 类型声明是否进入包;
两套 ABI 的 so 是否都被收集;
module / package 元信息是否正确;
有没有把测试资源、临时 JSON、符号文件误打进去。
Native 包最怕“本地能跑,发布包里少文件”。
这种问题直到 Consumer 安装以后才暴露,定位成本会非常高。
所以 JsonPulse 把 HAR 文件列表也纳入 Package Probe。
预期只有:
对外类型声明;
必要配置;
Native so;
必要资源。
不会把:
测试数据集
构建缓存
本机绝对路径
调试临时文件
带进发布包。
十七、arm64-v8a 和 x86_64 的体积差异本身也要记录
本轮:
arm64-v8a:
1.84MB
x86_64:
2.11MB
体积不一致并不代表有问题。
不同目标架构生成的机器码大小本来就可能不同。
真正应该关注的是:
同一 ABI
上一版本 1.84MB
下一版本突然 4.20MB
这时要检查:
是否误打开调试符号;
是否重复静态链接三方库;
是否 export 了大量不必要模板符号;
是否把另一份运行时一起打包。
所以 05 的 Package Manifest 会按 ABI 保存:
soSize
neededLibraries
exportedSymbolCount
buildId
而不是只记录总 HAR 大小。
十八、符号可见性不能只靠 -fvisibility=hidden
-fvisibility=hidden 是第一层。
但有些显式导出的构造函数、Node-API 注册入口以及依赖链仍然会进入动态符号表。
JsonPulse 的发布检查会把:
允许导出的符号白名单
和:
llvm-nm -D
结果比较。
例如:
模块初始化相关符号
明确的 C 导出接口
允许出现。
如果看到:
simdjson::dom::parser...
std::__n1::vector...
业务内部 class 方法
大面积出现在导出表,就记为:
exportedSymbolViolations
本轮:
0
十九、DT_NEEDED 比“能运行一次”更值得提前检查
某个 so 在开发机上能加载,不代表它没有偷偷依赖一个不该依赖的库。
JsonPulse 会查看:
Dynamic Section
DT_NEEDED
确认依赖都属于:
应用自身 Native 库;
HarmonyOS NDK 允许访问的库;
应用随包发布的 libc++_shared。
如果出现:
开发机私有路径;
系统 default namespace 私有 so;
临时测试库;
就直接拒绝发布。
HarmonyOS 当前 Native 动态链接有 namespace 隔离,普通应用不能随意加载 default namespace 中的系统私有 so。
这类问题越早在包阶段发现越好。
二十、Consumer 还要验证“卸载后干净安装”
同工程依赖测试通过以后,我又做了一次更严格的验证:
卸载 Consumer;
清理旧安装;
重新安装只引用 HAR 的版本;
第一次启动立即 import;
执行 parseSummary。
原因是 DevEco 增量构建和旧安装目录可能残留上一版本 so。
如果只有“覆盖安装”能工作,说明包并没有真正自洽。
本轮干净安装仍然:
consumerImportSuccess=true
consumerParseSuccess=true
PACKAGE_READY 才成立。
二十一、版本号不应该只存在 oh-package.json5
Native 包至少有三类版本:
Package Version
Bridge API Version
Third-party Revision
JsonPulse 当前:
package:
1.0.0
bridge:
1
simdjson revision:
写入 build manifest
ArkTS 启动自检会读取:
getNativeVersion()
确认:
index.d.ts
和 Native Bridge
属于同一代。
这样以后升级 simdjson,而 Node-API 接口不变,可以只增加内部 Revision;如果接口契约变化,再提升 Bridge API。
二十二、License 和 NOTICE 也属于 Native 分发的一部分
把开源 C++ 库打进 HAR 后,工程问题还包括:
许可证文本;
NOTICE;
第三方版本;
修改说明。
这些不能等上架前临时补。
JsonPulse 的 HAR 构建会把第三方 License 清单作为发布产物的一部分。
同时记录:
三方源码版本;
是否有项目 Patch;
Patch 对应 commit。
05 虽然重点是技术集成,但真正可分发的三方适配包必须让依赖来源可追溯。
二十三、Debug 符号和 Release HAR 要分开保存
发布 HAR 不需要携带一整套调试符号。
但 Native 崩溃定位又离不开符号表。
所以 JsonPulse 构建产物拆成:
distribution/
jsonpulse-native-1.0.0.har
symbols/
arm64-v8a/
x86_64/
正式分发只交付 HAR。
CI / DFX 环境另外保存对应 Build ID 的符号文件。
这样既不把包体积撑大,也能保证未来 FaultLog 可以符号化。
二十四、05 的包体积基线也要有“变化原因”
本轮:
HAR:
4.62MB
下一版本如果涨到 5MB,不会自动判错。
但构建报告需要能解释:
新增 ABI?
新增调试信息?
simdjson 升级?
新增 Native API?
重复 so?
Native 包体积最怕“悄悄变大”。
因此 06 最终回归虽然主要测试运行能力,包体积基线也会作为 Release Gate 的附加指标。
二十五、HAR 发布不是把源码隐藏起来,而是把契约稳定下来
做到这里,JsonPulse 05 真正完成的不是:
把一个目录压成 .har。
而是建立了一条交付契约:
固定 Package Name;
固定 ArkTS 类型声明;
固定 Node-API 模块名;
固定多 ABI 产物;
固定 C ABI 边界;
固定 SDK / libc++ 兼容要求;
固定符号白名单;
固定 Consumer 验收;
固定版本与 License 元数据。
当这些信息都能被机器检查以后,Native 三方库才真正从“某个项目里的 C++ 代码”变成“可复用组件”。
这也是 05 和前四篇最大的区别:前面关心运行时,05 开始关心生命周期的另一端——交付与升级。
二十六、Package Probe 还要验证安装后的真实搜索路径
构建产物里有 libjsonpulse.so,不代表运行时动态链接器一定能找到它。HarmonyOS 应用 Native 库受 app namespace 管理,真正安装以后只允许从应用可访问的 Native 路径和 NDK namespace 加载。因此 JsonPulse 会在 Consumer 首次启动时记录实际加载结果,而不是只在构建目录里检查文件存在。只要运行时出现 dlopen 失败、依赖 so 不可见或 rpath 异常,Package Gate 就直接失败。
这条检查尤其适合后续出现“库中还有库”的情况。如果 libjsonpulse.so 再依赖另一个业务 so,就必须明确它是否同目录交付、是否需要 rpath,以及 HAR 是否真正把依赖链完整带入消费应用。
二十七、PACKAGE_READY 还要绑定 Build ID
Native 崩溃排查时,最怕拿错符号文件。JsonPulse 在 HAR Manifest 里记录每个 ABI 对应的 Build ID,并和 CI 保存的 symbols 目录绑定。这样线上出现 Native 堆栈后,可以精确找到 arm64-v8a / 1.0.0 / API26 对应的符号,而不是拿“差不多同一版”的 so 去解析。对 C++ 组件来说,可追溯构建身份本身就是交付质量的一部分。
参考资料
- HAR / HSP 应用包术语:
https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/application-package-glossary - 预构建库快速链接:
https://developer.huawei.com/consumer/cn/doc/doccenter-deveco-studio/ide-hvigor-so - C/C++ 标准库机制:
https://developer.huawei.com/consumer/cn/doc/HarmonyOS-Guides/c-cpp-overview - Node-API 跨语言调用:
https://developer.huawei.com/consumer/cn/doc/doccenter-games/games-universal-using-napi-interaction-0000002411166425
更多推荐



所有评论(0)