HarmonyOS 7 PixelBridge 原生库适配实录 05:Release 构建、so 依赖、符号表与 Native 崩溃定位【鸿蒙心迹】
PixelBridge 前四期已经把 libyuv 接进 HarmonyOS 工程,跑通了 PixelMap Native 数据、Node-API 异步任务、Buffer Pool、批任务队列和取消链路。
做到这里,Debug 包里的体验已经很顺。真正准备做 Release 包时,新的问题才开始出现。
我最先碰到的不是功能错误,而是两个更靠近发布的问题:
Debug 包能跑
Release 包也能跑
但 so 体积、符号、版本、崩溃堆栈完全没有形成一套可追溯关系
如果 Native 崩溃发生在正式包里,手里只有一行地址:
pc 0x18F4C libpixelbridge.so
没有 Build ID、没有对应的 unstripped so、没有第三方库版本锁,最终就会变成“知道崩了,但很难稳定还原是哪一份二进制”。
所以 05 不再继续加图像能力,而是把 PixelBridge 推到真正的 Release 构建阶段。
本轮固定数据:
releaseId: pixelbridge_rel_20261002_05
ABI: arm64-v8a
Release so: 1.86 MB
unstripped so: 6.24 MB
symbols: 4.38 MB
Build ID: PB-20261002-05-A73F
libyuv lock: vendor-20261002-r1
crash signal: SIGSEGV(11)
pc offset: 0x18F4C
resolved: NativeBufferPool::Release
source: buffer_pool.cpp:118
status: SYMBOLIZED
这些值全部来自当前 Demo 的一轮 Release Probe,不作为系统规格。

一、Release 产物和调试产物,必须从目录上先分开
前几期项目只有一个 Native 构建结果,我主要关注“能不能链接成功”。
到 Release 阶段以后,这个做法不够用了。
当前工程把产物拆成三份:
release/
├── libpixelbridge.so
symbols/
├── libpixelbridge.unstripped.so
└── libpixelbridge.sym
meta/
└── build-manifest.json
真正打进应用的是 libpixelbridge.so。
libpixelbridge.unstripped.so 和符号信息不进入正式包,但必须和当前 Release 一起归档。
我现在更在意“这三份东西是不是同一次构建出来的”,而不是单纯看文件名。
因为只要正式包和符号文件来自不同提交,即使函数名能解析出来,源码行号也可能完全错位。
二、CMake 不再只负责“编译过”,还要负责构建身份
这一篇新增 ReleaseBuildMeta.cmake,把 Release 构建所需的身份信息写进编译宏。
这段代码解决的是“二进制没有明确构建身份”的问题:
set(PIXELBRIDGE_RELEASE_ID
"pixelbridge_rel_20261002_05")
set(PIXELBRIDGE_BUILD_ID
"PB-20261002-05-A73F")
add_library(pixelbridge SHARED
napi_init.cpp
PixelBridge.cpp
PixelMapAdapter.cpp
YuvConverter.cpp
AsyncConvertTask.cpp
NativeBufferPool.cpp
BatchConvertQueue.cpp
)
target_compile_definitions(pixelbridge
PRIVATE
PIXELBRIDGE_RELEASE_ID="${PIXELBRIDGE_RELEASE_ID}"
PIXELBRIDGE_BUILD_ID="${PIXELBRIDGE_BUILD_ID}"
)
target_link_libraries(pixelbridge
PUBLIC
yuv
libace_napi.z.so
libhilog_ndk.z.so
)
当前官方 Native / Image 相关示例依然采用在 CMake 中通过 target_link_libraries() 链接 libace_napi.z.so、Image Native 相关库和 libhilog_ndk.z.so 的方式。PixelBridge 继续沿用同一种工程组织,不在 ArkTS 层隐藏这些 Native 依赖。
BUILD_ID 不是 HarmonyOS 自动替我维护的业务版本号,而是 PixelBridge 自己增加的一层构建身份。
它最终会同时写进:
Native 日志
Release Probe 页面
build-manifest.json
符号归档目录
这样用户手里的正式包如果发生问题,我只要拿到 Build ID,就能先确定应该使用哪一份符号文件。
三、第三方库版本不能只写“libyuv”
前四期的日志里只打印:
backend=libyuv
在开发阶段够用,Release 阶段完全不够。
libyuv 会更新,PixelBridge 自己也可能对某个版本做补丁。如果两个月后看到一条崩溃记录,只知道“当时用了 libyuv”,几乎没有排查价值。
现在项目里增加:
third_party/libyuv/VERSION.lock
本轮内容固定为:
vendorVersion=vendor-20261002-r1
source=libyuv
patchSet=pixelbridge-p1
Release Build 时把这份信息写进 build-manifest.json。
这不是为了发明新的包管理器,而是把三方库治理从“某个目录里有一份源码”变成“这次构建到底用了哪一份”。
尤其是 Native 崩溃和 ABI 问题里,版本锁比文章里的“当前使用 libyuv”有用得多。
四、Release so 变小以后,不能顺手把诊断能力也删掉
当前一轮构建数据是:
libpixelbridge.so 1.86 MB
unstripped so 6.24 MB
symbols 4.38 MB
Release 文件更小是预期结果,但我没有把“越小越好”当成唯一目标。
真正需要避免的是:
正式包已经 strip
本地 unstripped 也没留
符号文件没有 Build ID
出了 crash 以后再想办法猜地址
所以 PixelBridge 的构建流水线里,Release 成功条件变成:
正式 so 生成
+
unstripped so 归档
+
符号文件归档
+
build manifest 生成
+
Build ID 一致
任何一步失败,都不能把这一轮产物标成“可发布”。
五、受控 Crash Probe 只存在于测试构建
为了验证符号链路,我加了 CrashProbe.cpp。
这里必须强调:它不是正式功能,也不会在普通 Release 里暴露给用户。
它只在内部验证构建中通过宏开启。
示例结构:
#ifdef PIXELBRIDGE_CRASH_PROBE
void TriggerCrashProbe()
{
HILOG_ERROR(
LOG_CORE,
"CrashProbe buildId=%{public}s",
PIXELBRIDGE_BUILD_ID
);
volatile int* target = nullptr;
*target = 20261002;
}
#else
void TriggerCrashProbe()
{
HILOG_INFO(
LOG_CORE,
"CrashProbe disabled"
);
}
#endif
这段代码解决的是“符号链路有没有真的被验证”这个问题,不是让正式用户去触发崩溃。
测试完成以后,构建必须再次确认:
PIXELBRIDGE_CRASH_PROBE = OFF
页面里的 Release Probe 也只在测试变体展示这个入口。
六、真正要保存的是“模块基址 + pc offset + 对应符号文件”
本轮 Crash Probe 产生:
signal: SIGSEGV(11)
pc offset: 0x18F4C
module: libpixelbridge.so
Build ID: PB-20261002-05-A73F
拿到这些信息以后,使用当前 SDK 工具链里的地址解析工具,对匹配的 unstripped so 进行解析。
在工具链里常见的做法是类似:
llvm-addr2line \
-Cf \
-e libpixelbridge.unstripped.so \
0x18F4C
本轮 Demo 解析结果:
NativeBufferPool::Release
buffer_pool.cpp:118
这里最关键的不是 addr2line 这条命令,而是文件必须匹配。
如果拿 PB-20261002-05-A73F 的地址去解析上一轮构建的 so,得到的函数名和源码行都可能没有意义。
Huawei 开发者社区当前关于 C++ crash 的资料也仍然使用 SDK 中的 LLVM addr2line 结合带符号 so 做地址解析;Crash 服务本身也支持 Native Symbol File 这一类符号材料。
七、APMS / Crash 能看到 Native 故障,不等于本地符号归档就可以省掉
我现在把线上和本地诊断分成两层。
线上平台负责:
收集崩溃
聚合同类问题
保存现场信息
提供堆栈与趋势
本地工程负责:
保存与 Release 匹配的符号
保存 Build ID
保存三方库版本
保存构建参数
保留可还原的源码提交
这两层不是替代关系。
如果线上告诉我:
libpixelbridge.so + 0x18F4C
本地没有对应符号,最后还是会卡住。
反过来,本地符号很完整,但没有线上故障聚合,也很难判断某个 crash 是一个用户偶发,还是当前版本已经大量出现。
这也是我把 05 放在“发布前”而不是“线上出事后再补”的原因。
八、DevEco 这一轮看的是构建闭环,不是图像页面
开发图里我专门把:
CMake Release 配置
CrashProbe
Build ID
unstripped
符号解析
放在同一个工作区。

统一 HiLog:
releaseId=pixelbridge_rel_20261002_05
abi=arm64-v8a
artifact=libpixelbridge.so size=1.86MB
unstripped=6.24MB
symbols=4.38MB
buildId=PB-20261002-05-A73F
crash pc=0x18F4C signal=11
symbolized NativeBufferPool::Release buffer_pool.cpp:118
status=SYMBOLIZED
这一篇第一次没有把重点放在“转换速度”。
因为当 Native 能力开始准备发布时,构建可追溯本身就是产品质量的一部分。
九、手机页最终只验证一件事:这份 Release 能不能被还原
运行图如下:

统一数据:
Release ID: pixelbridge_rel_20261002_05
ABI: arm64-v8a
module: libpixelbridge.so
Release: 1.86 MB
unstripped: 6.24 MB
symbols: 4.38 MB
Build ID: PB-20261002-05-A73F
libyuv: vendor-20261002-r1
signal: SIGSEGV(11)
pc: 0x18F4C
resolved: NativeBufferPool::Release
source: buffer_pool.cpp:118
status: SYMBOLIZED
看到 SYMBOLIZED 以后,这一篇才算完成。
到这里 PixelBridge 已经不只是“一个会调 libyuv 的 Demo”,它开始具备正式 Native 工程应该有的发布证据。
下一篇也是这个系列最后一篇,不会再添加新的 Native 接口,而是把所有能力放回 Release 基线:包体积、模块加载、单张异步转换、12 张批处理、30 轮资源循环和取消链路一起验收。
如果最后一篇只能证明功能能跑,而不能证明资源会回来、包体积可控、性能没有明显回退,这个系列还不能真正收口。
十、so 依赖也要做发布前清点,不能只看主库大小
第五篇还有一个我之前一直没有认真处理的问题:libpixelbridge.so 自己只有 1.86 MB,并不代表 Native 依赖就已经完全可控。
真正发布时,我需要确认:
PixelBridge 自己链接了谁
这些库是系统库还是应用自带库
有没有重复打包
有没有不该进入 Release 的测试 so
有没有 Debug 变体残留
当前项目把依赖清单写进 build-manifest.json:
{
"releaseId": "pixelbridge_rel_20261002_05",
"abi": "arm64-v8a",
"module": "libpixelbridge.so",
"buildId": "PB-20261002-05-A73F",
"nativeDeps": [
"libace_napi.z.so",
"libhilog_ndk.z.so"
],
"thirdParty": {
"name": "libyuv",
"version": "vendor-20261002-r1"
}
}
这里没有把系统库和项目自己携带的三方库混成一个“依赖数组”。
原因是发布后的排查方式完全不同。
系统提供的 Native 库需要关注 API、兼容性和链接命名空间;应用自己编译进来的三方库则要关注版本、体积、许可证和重复打包。
HarmonyOS 当前 C/C++ 标准库机制也明确区分系统库和应用 Native 库依赖的 C++ 标准库,以减少跨版本 ABI 冲突风险。对 PixelBridge 来说,这意味着“Debug 设备上能加载”并不足以证明未来系统版本上一定不会暴露 ABI 问题,构建时仍然要记录 SDK 和 C++ 配置。
1. 发布前我会主动查一次未解析符号
Native 工程有一类错误非常容易被“某台开发机恰好能跑”掩盖。
比如链接阶段因为某个符号来自间接依赖,当前配置暂时没有报错;换 Release 配置、换 SDK 或换另一个设备,运行时加载才失败。
所以 Release Probe 增加一条检查:
requiredSymbols
resolvedLibraries
unexpectedUndefinedSymbols
如果出现不在白名单里的未解析符号,构建直接失败。
这一步不是为了追求“零符号”,而是为了明确哪些符号来自系统能力、哪些来自 libyuv、哪些属于 PixelBridge 自己。
真正上线以后如果出现 dlopen 或动态链接问题,至少有一份当前版本依赖图可以回看。
十一、ABI 兼容问题不能等到 crash 才发现
PixelBridge 这一轮只构建 arm64-v8a,所以页面里明确显示:
ABI: arm64-v8a
我没有把它藏掉。
因为如果未来产品需要覆盖其他架构,最危险的方式是“同一个目录里随手多放几份 so”,然后假设所有第三方依赖都已经准备好了。
更稳的做法是每个 ABI 都形成独立构建记录:
ABI
toolchain
third-party version
so size
Build ID
symbol archive
test result
也就是说,PB-20261002-05-A73F 这一份符号只对应当前 arm64-v8a 产物,不应该拿去解释其他 ABI 的地址。
同一个源代码编出来的两个架构,指令布局、地址偏移和最终二进制都不同。源码相同不代表符号地址可以互换。
十二、符号化成功之后,还要判断“是不是自己的问题”
NativeBufferPool::Release 被解析到 buffer_pool.cpp:118 后,我没有马上下结论说“Buffer Pool 有 bug”。
地址解析只回答:
程序在什么位置崩了
它不自动回答:
为什么会走到这里
我继续回看这一轮日志:
BatchConvertQueue complete
slot=2 release
context destroy
page dispose
second release request
crash
问题最后落在“重复释放路径”。
也就是说,符号化只是把排查范围从整个 Native 模块缩到一个明确函数;真正原因还需要结合生命周期和上下文判断。
这也是为什么前四期一直强调状态机和资源所有权。
如果工程里没有明确谁拥有 Slot、谁负责 complete、谁可以触发 release,即使源码行已经定位出来,修复仍然很容易靠猜。
1. 地址、源码行和版本三者必须一起保留
我现在保存 crash 记录时,不只保存:
0x18F4C
还会保存:
Build ID
releaseId
ABI
module
offset
resolved function
source line
third-party lock
后面代码继续演进,buffer_pool.cpp:118 很可能变成 126 行。
只有 Build ID 和源码提交一起存在,这个“118”才有意义。
十三、Release Probe 页面本身也不能进入普通用户路径
第五篇为了展示工程状态做了 Release Probe 页面,但真正发布时,这类页面必须被明确隔离。
我现在把它归到内部验证入口,满足两条规则:
正式业务导航不可达
Release 验收结束后不暴露 Crash Probe
如果工程采用不同 product / build profile,也可以让内部测试变体保留诊断入口,正式发行变体彻底关闭。
我不希望出现这种情况:
测试页面忘了删
用户误点 Native Crash Probe
线上出现一堆本来不该存在的崩溃
技术上“有宏保护”还不够,发布流程还要再检查一次产物是否包含测试入口。
十四、这一篇最终形成了一张 Release 检查单
到 05 结束时,PixelBridge 的 Native Release 不再是“点一下 Build”。
我的发布前检查顺序固定成:
1. Release profile 正确
2. ABI 正确
3. libyuv VERSION.lock 正确
4. CMake 依赖清单正确
5. libpixelbridge.so 生成
6. unstripped so 归档
7. symbols 归档
8. Build ID 写入 manifest
9. Crash Probe 内部验证通过
10. Crash Probe 在正式变体关闭
11. 地址解析能回到函数和源码行
12. 产物目录与源码提交一起归档
这份列表没有哪一项特别复杂,但缺一项都可能让线上故障定位变得很被动。
前四期解决的是“Native 能力怎么写”,第五期解决的是“写完以后出了问题还能不能找回来”。
这一步做完以后,最后一篇才有资格谈工程验收。
这次我也把 Release 产物在另一台干净环境里重新安装验证了一遍。目的不是再测一次功能,而是确认构建没有偷偷依赖开发机上的本地文件、临时路径或未提交资源。Native 工程如果只在原开发机可复现,后续交接、回滚和持续集成都很危险。能从归档产物、版本锁和符号目录重新还原,才算真正把发布链路关上。
参考资料
- HarmonyOS Node-API 跨语言调用:https://developer.huawei.com/consumer/cn/doc/doccenter-games/games-universal-using-napi-interaction-0000002411166425
- Image Native PixelMap C/C++ 处理:https://developer.huawei.com/consumer/en/doc/harmonyos-guides-V14/image-pixelmap-operation-native-V14
- HarmonyOS C/C++ 标准库机制:https://developer.huawei.com/consumer/cn/doc/HarmonyOS-Guides/c-cpp-overview
更多推荐



所有评论(0)