鸿蒙+Flutter + Rust ,<flutter_rust_bridge>三方库适配鸿蒙实战:从“未初始化”到 Callback 真机稳定通过
前言
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
这篇文章不是“抄官方文档”的泛泛教程,
而是基于一个真实项目的完整排障与修复过程整理出来的实战记录。
项目目标很明确:
在鸿蒙真机上,
把 flutter_rust_bridge + cargokit 这条链路跑通,
并且把最难的 Rust -> Dart callback 链路做到稳定可回归。
如果你也遇到过下面这些报错,
这篇文章就是写给你的:
flutter_rust_bridge has not been initializedDart_InitializeApiDL: symbol not found- callback 一跑就崩
- 清理后偶现“上一轮能跑,这一轮不行”
欢迎先收藏,
然后按目录一步步照着做。
本文适用于:希望把 Flutter + Rust 真正落地到 OpenHarmony/HarmonyOS 真机的开发者。
本文强调:不是“临时跳过测试”,而是把核心链路修到可复现、可回归、可解释。
目录
- 一、项目背景与适配目标
- 二、环境准备与版本矩阵
- 三、三方库关系图与调用链
- 四、问题复盘:为什么一直报未初始化
- 五、根因一:OHOS 下 Dart API DL 初始化缺失
- 六、根因二:C 文件被编成 Mach-O 不是 ELF
- 七、根因三:重复符号导致链接冲突
- 八、Demo 侧如何做“可测、可看、可回归”
- 九、真机验证:Callback 三轮压测 + 全量通过
- 十、为什么
cargo会很慢?如何提速 - 十一、常见坑位与排查清单
- 十二、可复用的鸿蒙适配模板
- 总结
一、项目背景与适配目标
1.1 业务背景
我们有一个 Flutter 项目,
其中核心能力由 Rust 提供,
并通过 flutter_rust_bridge 对外暴露接口。
在 Android/iOS 上这条链路通常问题不大,
但迁移到鸿蒙后,
会出现一些“看起来像初始化问题,实际上是构建链路问题”的复合故障。
1.2 本次适配目标
本次不是只修“能跑一下”,
而是分三层目标推进:
- 功能层:Flutter 可以调用 Rust,Rust 也能回调 Dart。
- 稳定层:真机多轮运行稳定,去掉 callback 的 skip。
- 工程层:清理缓存后重编译仍可复现,不靠“玄学状态”。
1.3 结果先看
最终结果:
- callback 定向测试:真机连续 3 轮通过。
integration_test/simple_test.dart:全量 8 项通过。- 关键初始化与动态库符号问题全部闭环。
二、环境准备与版本矩阵
2.1 核心环境
| 项目 | 版本/信息 | 说明 |
|---|---|---|
| Flutter SDK | 3.35.8-ohos-0.0.2 |
鸿蒙分支 Flutter |
| 设备 | FMR0223825079397 |
HarmonyOS 真机 |
| 系统 | HarmonyOS 6.x | 真实设备验证 |
| FRB | 本地 path 依赖 | 可直接改源码 |
| Cargokit | 本地 path 依赖 | 与 FRB 联动 |
2.2 目录布局(关键)
new/
├── flutter_rust_bridge/
│ ├── frb_dart/
│ └── frb_rust/
├── cargokit/
└── frb_ohos_demo/
├── lib/
├── rust/
├── integration_test/
└── ohos/
这套布局的好处是:
- 你能直接修改三方库源码并立刻在 demo 验证。
- 避免“你以为改了,实际依赖还是远端版本”的错觉。
2.3 依赖方式(必须本地 path)
# frb_ohos_demo/pubspec.yaml
dependencies:
flutter_rust_bridge:
path: ../flutter_rust_bridge/frb_dart
rust_lib_frb_ohos_demo:
path: rust_builder
# frb_ohos_demo/rust/Cargo.toml
[dependencies]
flutter_rust_bridge = { path = "../../flutter_rust_bridge/frb_rust" }
如果你不用 path 依赖,后续很多修复根本不会生效在当前 demo 上。
三、三方库关系图与调用链
3.1 三方库各自做什么
| 组件 | 作用 | 你需要关注什么 |
|---|---|---|
frb_dart |
Dart 侧加载动态库、调用入口 | RustLib.init() 初始化顺序 |
frb_rust |
Rust 侧编解码、回调、线程与 FFI | dart_api_dl 初始化与符号 |
cargokit |
Rust 构建与产物拷贝到平台工程 | 构建目标三元组是否正确 |
3.2 调用链路(从按钮到回调)
链路看起来短,
但鸿蒙上最容易出问题的恰恰是 C 动态链接 + Dart API DL 这一层。
四、问题复盘:为什么一直报未初始化
4.1 典型报错现象
在页面上你会看到类似:
Action: Call Rust 'greet("Tom")'
Result: 'Rust call failed: Bad state: flutter_rust_bridge has not been initialized...'
以及测试阶段:
Failed to load dynamic library 'librust_lib_frb_ohos_demo.so':
Error relocating ...: Dart_InitializeApiDL: symbol not found
4.2 “未初始化”不一定是你没 init()
很多人第一反应是:
“我是不是忘记 await RustLib.init(); 了?”
这次实战证明:
不一定。
你确实可能已经调用了 init(),
但由于动态库加载期符号解析失败,
Dart 侧看起来就像“没初始化”。
4.3 先定位,再修复
我们按照这个顺序排查:
- 看测试日志与 hilog,确认是加载阶段还是调用阶段崩。
- 用
nm看.so符号是未定义还是已导出。 - 看 build script 输出,确认 C 文件到底被谁编译成什么格式。
- 再决定改
io.rs、build.rs还是 demo 侧构建脚本。
五、根因一:OHOS 下 Dart API DL 初始化缺失
5.1 问题本质
dart-opaque 回调链路会用到 Dart_*PersistentHandle* 相关符号。
如果 Dart_InitializeApiDL 没有正确执行,
这条链路一定炸。
5.2 修复点(frb_rust)
文件:new/flutter_rust_bridge/frb_rust/src/ffi_binding/io.rs
#[no_mangle]
pub unsafe extern "C" fn frb_init_frb_dart_api_dl(data: *mut std::ffi::c_void) -> isize {
#[cfg(feature = "dart-opaque")]
return dart_sys::Dart_InitializeApiDL(data);
#[cfg(not(feature = "dart-opaque"))]
return 0;
}
5.3 这段逻辑的意义
- 当启用
dart-opaque时,必须走Dart_InitializeApiDL。 - 这不是“可选优化”,是 callback 正常工作的前置条件。
经验:只要你要 Rust 回调 Dart,就把
dart_api_dl初始化当成强约束。
六、根因二:C 文件被编成 Mach-O 不是 ELF
6.1 关键异常
我们在构建输出里看到了这一行:
running: "cc" ... "-c" "src/dart_api/dart_api_dl.c"
在跨到 OHOS 目标时,
如果这里还走宿主 cc,
就很容易产出 Mach-O 对象,
而非 OHOS 需要的 ELF。
这会带来两类后果:
- 动态加载时符号无法解析;
- 链接过程出现 “archive member is neither ET_REL nor LLVM bitcode”。
6.2 修复点(build.rs)
文件:new/flutter_rust_bridge/frb_rust/build.rs
if std::env::var("CARGO_CFG_TARGET_ENV")
.map(|x| x == "ohos")
.unwrap_or(false)
{
let mut build = cc::Build::new();
if let Ok(linker) = std::env::var("RUSTC_LINKER") {
build.compiler(linker);
}
build
.file("src/dart_api/dart_api_dl.c")
.include("src/dart_api")
.warnings(false);
if let Ok(target) = std::env::var("TARGET") {
build.flag(&format!("--target={target}"));
}
build.compile("frb_dart_api_dl");
}
6.3 修复后如何验证
nm -u rust_builder/ohos/.cxx/.../librust_lib_frb_ohos_demo.so \
| rg 'Dart_InitializeApiDL|Dart_NewPersistentHandle_DL'
如果没有未定义输出,
说明这条符号问题已被吃掉。
七、根因三:重复符号导致链接冲突
7.1 为什么会重复
一开始为了兜底,
在 io.rs 里手工导出了若干 Dart_*_DL 全局。
但后来 dart_api_dl.c 也正确编译并参与链接后,
两边都定义同名符号,
就会触发 duplicate symbol。
7.2 冲突日志(典型)
ld.lld: error: duplicate symbol: Dart_CurrentIsolate_DL
ld.lld: error: duplicate symbol: Dart_NewPersistentHandle_DL
7.3 修复原则
- 保留一种权威定义来源即可。
- 在本案例里:让
dart_api_dl.c负责这组符号。 - 删除
io.rs中 OHOS 专用的手工全局定义。
7.4 另一个隐藏冲突:demo 重复注入
demo 的 rust/build.rs 一开始也手动编译了 dart_api_dl.c,
这会与 FRB 自身注入形成双份静态库。
最终做法是:
// new/frb_ohos_demo/rust/build.rs
fn main() {}
统一让三方库自己管理
dart_api_dl,demo 不再重复注入。
八、Demo 侧如何做“可测、可看、可回归”
8.1 扩展一个最小 callback API
Rust 侧提供轻量 callback 接口,
避免被复杂业务噪音干扰。
pub fn rust_call_dart_ping(callback: impl Fn() -> DartFnFuture<()>) -> String {
callback();
"pong-from-rust".to_string()
}
8.2 Dart 侧集成测试样例
testWidgets('Can perform Rust->Dart callback', (tester) async {
await RustLib.init();
var called = false;
final result = await api.rustCallDartPing(callback: () async {
called = true;
});
expect(called, true);
expect(result, contains('pong'));
});
8.3 页面侧做“可视化回执”
建议把每个能力都做一行状态展示:
- init 是否成功
- sync/async 接口是否通过
- stream 是否收到事件
- callback 是否触发
- opaque 对象生命周期是否正常
这样出现回归时,
你在 UI 上一眼就能定位是哪一段链路挂了。
8.4 测试矩阵建议
| 测试类别 | 建议用例 | 目标 |
|---|---|---|
| 初始化 | RustLib.init() |
早发现加载失败 |
| 同步调用 | greet_sync |
验证基础 FFI |
| 异步调用 | greet |
验证线程与回传 |
| 回调 | Rust->Dart callback | 验证最易崩链路 |
| Stream | rust stream to dart | 验证持续事件通路 |
九、真机验证:Callback 三轮压测 + 全量通过
9.1 单项压测(callback)
flutter test integration_test/simple_test.dart \
-d FMR0223825079397 \
--plain-name "Can perform Rust->Dart callback"
建议做 3~10 轮,
每轮都包含:
- 构建 hap
- 安装到真机
- 启动能力并 attach
- 执行 callback 用例
- 卸载与回收
9.2 全量回归
flutter test integration_test/simple_test.dart -d FMR0223825079397
本次结果:
- callback 连续 3 轮通过;
- 全量 8 项全部
All tests passed。
9.3 一组实用排查命令
# 设备是否在线
hdc list targets
# 只跑回调用例
flutter test integration_test/simple_test.dart \
-d FMR0223825079397 \
--plain-name "Can perform Rust->Dart callback" -v
# 查看符号
nm -g rust_builder/ohos/.cxx/.../librust_lib_frb_ohos_demo.so \
| rg 'Dart_InitializeApiDL|frb_init_frb_dart_api_dl'
9.4 验证结果记录表
| 轮次 | callback | 全量测试 | 备注 |
|---|---|---|---|
| Round 1 | 通过 | - | 冷编译 |
| Round 2 | 通过 | - | 连续回归 |
| Round 3 | 通过 | - | 连续回归 |
| Full | - | 8/8 通过 | 真机执行 |
十、为什么 cargo 会很慢?如何提速
10.1 慢的根本原因
鸿蒙目标是交叉编译,
每次清理后都要重新做:
- Rust 依赖编译
- C 依赖编译
- 链接与 strip
- hap 打包与签名
所以你看到 cargo 慢是正常现象,
特别是第一次构建。
10.2 提速建议(非常实用)
- 不要每次都
flutter clean+cargo clean。 - 稳定阶段优先增量构建。
- 只在“确认构建链修复有效性”时做全量 clean。
- 把 callback 用例拆成
--plain-name单测先跑。
10.3 推荐流程
- 改一处源码。
- 跑 callback 定向测试。
- 再跑全量测试。
- 最后才做一次 clean 验证闭环。
这个流程比“每次全清理重来”快非常多。
十一、常见坑位与排查清单
11.1 设备明明在线,Flutter 却说找不到
现象:
hdc list targets有设备;flutter test -d <id>报无设备。
排查思路:
- 看 Flutter 调的是哪个
hdc二进制。 - 确认 DevEco toolchain 路径一致。
- 断开重连设备后再试。
11.2 “Unable to locate an OpenHarmony SDK”
这个提示在某些日志里会出现,
不一定代表真失败。
关键是看后续是否能:
- 成功 build hap;
- 安装到设备;
- 拉起 ability 并执行测试。
11.3 误区清单
- 误区 1:只改 demo,不改三方库核心逻辑。
- 误区 2:只看 Dart 报错,不看动态库符号。
- 误区 3:遇到 callback 崩溃就直接 skip。
- 误区 4:把“偶尔成功”当成“已经修复”。
11.4 发布前自检表
| 检查项 | 通过标准 | 是否建议自动化 |
|---|---|---|
| 动态库符号 | 无未定义 Dart_*_DL |
是 |
| callback 用例 | 连续 3 轮通过 | 是 |
| 全量集成测试 | 全通过 | 是 |
| clean 后回归 | 至少 1 次通过 | 是 |
| 日志归档 | 有构建与测试记录 | 是 |
十二、可复用的鸿蒙适配模板
12.1 建议你固定下来的模板结构
# 统一本地依赖,避免“改了不生效”
flutter_rust_bridge:
path: ../flutter_rust_bridge/frb_dart
# Rust 端同样固定 path
flutter_rust_bridge = { path = "../../flutter_rust_bridge/frb_rust" }
# 推荐回归脚本
hdc list targets
flutter test integration_test/simple_test.dart \
-d FMR0223825079397 \
--plain-name "Can perform Rust->Dart callback"
flutter test integration_test/simple_test.dart -d FMR0223825079397
12.2 可以继续演进的方向
后续你可以继续做:
- callback 压测升级到 10~30 轮;
- 引入自动化脚本输出成功率;
- 把回调链路扩展到带参数、异常、取消场景;
- 增加 CI 上的模拟检查(符号与构建链静态检查)。
12.3 一份“可取消 skip”的策略建议
- 第一阶段:只恢复最小 callback 用例,不跳过。
- 第二阶段:连续 3 轮稳定后再恢复复杂 callback。
- 第三阶段:全量跑通后,把 skip 彻底移除并固化到回归脚本。
这比“一口气全开”更稳。
附:关键修复文件一览
| 文件 | 修改目的 |
|---|---|
new/flutter_rust_bridge/frb_rust/src/ffi_binding/io.rs |
修正 OHOS 下 Dart API DL 初始化与符号策略 |
new/flutter_rust_bridge/frb_rust/build.rs |
强制 C 文件使用 OHOS 交叉编译器 |
new/frb_ohos_demo/rust/build.rs |
移除 demo 侧重复注入,避免冲突 |
参考链接(建议收藏)
- OpenHarmony 官网:https://www.openharmony.cn/
- 华为开发者联盟:https://developer.huawei.com/consumer/cn/
- DevEco Studio:https://developer.huawei.com/consumer/cn/deveco-studio/
- Flutter 官方文档:https://docs.flutter.dev/
- Dart 官方文档:https://dart.dev/
- Rust 官网:https://www.rust-lang.org/
- Cargo 文档:https://doc.rust-lang.org/cargo/
- flutter_rust_bridge 仓库:https://github.com/fzyzcjy/flutter_rust_bridge
- flutter_rust_bridge crates 页面:https://crates.io/crates/flutter_rust_bridge
- cc-rs 仓库(
build.rsC 编译核心):https://github.com/rust-lang/cc-rs - Flutter 仓库:https://github.com/flutter/flutter
总结
这次适配最大的收获,
不是“某个报错消失了”,
而是把一条跨语言、跨工具链、跨平台的链路彻底打通了:
- 调用层:Flutter -> Rust 正常;
- 回调层:Rust -> Dart callback 正常;
- 构建层:OHOS 交叉编译目标正确;
- 工程层:可清理、可重编译、可回归。
如果你正在做 Flutter + Rust 的鸿蒙落地,
建议你把本文的三件事记住:
dart_api_dl初始化必须可验证;- C 编译目标必须是 OHOS 交叉链;
- 不要重复定义
Dart_*_DL符号。
如果这篇文章对你有帮助,欢迎点赞👍、收藏⭐、关注🔔,你的支持是我持续创作的动力!
相关资源:
更多推荐




所有评论(0)