
把 on_chain 这套 Flutter 三方库跑在鸿蒙上是我最近大半个月主要在做的事。它把链上交互、高性能私钥签名和多链资产治理这些能力封装在统一 Dart API 里原本在 Android/iOS 上都是现成的可一转到 HarmonyOS NEXT 这条新赛道依赖的原生实现完全得换血。说句实话纯 Dart 层的代码几乎不用动真正费劲的是底层能力层——安全存储、安全随机数、原生加密、网络通道这些才是不可篡改数字化底座的地基。这篇文章不是泛泛讲原理而是把我适配过程中踩过的坑、改过的代码、验证过的方法逐条记录下来。如果你手里有 Flutter 链上项目要鸿蒙化或者准备在鸿蒙应用里做钱包、转账、签名验签这类功能这篇指南能帮你少折腾至少一周。1. 为什么非要动 on_chain鸿蒙生态带来的连锁反应1.1 on_chain 到底解决什么问题on_chain 的出现本质上是想终结 Flutter 链上开发东拼西凑的状态。做链上应用的人都有体会光是把生成助记词、派生私钥、构造交易、签名、广播、查余额、验签这一套打通就得在若干个底层库之间来回搬运数据还要自己处理不同链的差异。on_chain 做的事就是把这些流程收敛成一套面向业务开发者的高层接口内部再按链类型做分发调用方不用关心对方是 EVM 兼容链还是 UTXO 系链只要拿到统一的数据模型就能干活。但这里有个很容易被忽略的点on_chain 虽然是个 Dart 包它的部分能力并不是纯 Dart 的。私钥派生过程中的 secp256k1 运算、助记词存储时的安全写入、连接链上节点时的 WebSocket 长连接这些底层能力天然要触碰操作系统。以前这些能力在 Android 侧能找到现成实现鸿蒙生态起来之后很多底层支撑件都要重新找替代品。这正是整个适配工作的源头。1.2 Dart 层很平静Native 层在打架我第一次跑鸿蒙适配的时候第一个直觉是Dart 代码跨平台问题应该不大结果确实如此——on_chain 里百分之八十的代码是纯 Dart 的交易结构、地址编码、签名消息组装、资产模型这些逻辑换到鸿蒙上根本不用动。真正的冲突发生在引擎和原生交互那一层。Flutter 在鸿蒙上的运行方式跟 Android 不太一样。Android 上有成熟的插件体系把原生能力暴露给 Dart 的方式是 Platform Channel鸿蒙上虽然也有类似的通道机制但插件生态的成熟度差一个量级。以前一个MethodChannel(path_provider)就能拿到目录鸿蒙上要么自己写原生实现要么找社区适配版本要么绕道纯 Dart 方案。这个找替代的过程就是适配工作的主旋律。1.3 三条路线重写、桥接、还是分层适配面对鸿蒙化我最初列了三个方案。第一是重写一个鸿蒙原生版本不走 Flutter直接 ArkTS 重做所有链上逻辑。这个方案功能上最可控但工程量极大而且后续 Flutter 侧的更新很难同步过来。第二是找个桥接层把 Android 的 so 库直接搬到鸿蒙上用风险在于 ABI 兼容性和系统接口差异很容易出现能编过、跑不稳的局面。第三就是最终采用的分层适配保留 on_chain 的上层 Dart 接口不动把底层能力抽象成服务接口再分别实现 Android、iOS、鸿蒙三套服务。这么做的好处是把修改范围控制在能力层业务代码一行不用改后续 on_chain 上游升级也只需要重新对齐服务层接口。适配完成后这套服务抽象还可以复用到别的鸿蒙 Flutter 项目里属于一次性投资、长期复用。2. 地基工程鸿蒙化改造前必做的环境与依赖处理2.1 工程迁移怎么起头鸿蒙化适配不是从改代码开始的而是从换工具链开始的。我建议先确认手上的 Flutter SDK 是否带鸿蒙平台支持。有社区分支维护的 Flutter 引擎能识别ohos平台执行创建工程或添加平台目录之后项目里会自动生成ohos/目录里面是鸿蒙原生工程的骨架后续用官方 IDE 打开这个目录继续开发。工具链这块有个细节IDE 版本、鸿蒙 SDK 版本和 Flutter SDK 分支要一起对齐三者的版本不匹配是整个工程最隐蔽的坑。我之前吃过一次亏Flutter SDK 用了一个较新的分支IDE 还是旧版编译时直接报了一个看不清来源的原生符号错误。后来把三者锁定到互相兼容的版本号上问题就消失了。所以拿到工程第一步先查版本矩阵别急着写代码。2.2 依赖替换的对照清单适配过程中最耗神的不是写代码而是把 on_chain 间接依赖的每一层能力在鸿蒙生态里找到对应实现。我当时整理过一张替换清单这里直接分享给你原能力Android 上常用方案鸿蒙上可用方案注意事项安全存储EncryptedSharedPreferences / Keystore系统安全存储服务Access Token 能力封装每次读写有异步开销建议加缓存层安全随机数SecureRandom系统安全随机数生成接口绝对不要用普通 Random 生成私钥或 nonce原生加密厂商 SDK 或 OpenSSL系统 Crypto Framework 对应能力需确认算法套件是否覆盖 secp256k1持久化配置SharedPreferences系统 Preferences 能力只存普通配置不存私钥目录获取path_provider系统文件目录接口不同目录的备份、隐藏属性差异很大网络权限AndroidManifest 声明module.json5 里声明 ohos.permission.INTERNET不声明一定连不上节点WebSocketokhttp / dart:iodart:io WebSocket 基本可用部分原生网关代理场景需另测这里强调一点看到系统安全存储服务别以为像 SharedPreferences 一样随手读写它天生就带权限校验和密文存储调用方要从业务签名中提取凭据才能解码数据。所以在 on_chain 的适配里我在安全存储外层又包了一个 Dart 服务内部自动处理了凭据获取和异常重试业务侧只暴露read/write两个同步方法。2.3 Platform Channel 在鸿蒙侧的正确打开方式on_chain 的能力层要跟鸿蒙原生对话最稳的通道还是 MethodChannel。鸿蒙侧 Flutter 引擎会拦截通道名往对应的原生模块分发。我第一次写鸿蒙侧通道实现时以为跟 Android 的onMethodCall一模一样结果注册方式、线程模型、参数类型映射都有细微差别。比如 Dart 侧传进来的MapString, dynamic鸿蒙侧拿到的对象类型、取值方式跟 Android 的ReadableMap并不一致。建议只把高频且轻量的操作放到 MethodChannel 上比如读安全随机数、读安全存储。涉及大数据量或者高频繁发签名时MethodChannel 的编解码开销会占大头不如走 FFI 直接内存操作。适配 on_chain 时我采用了一个折中方案私钥派生和签名这类计算密集操作下沉到原生后通过预先分配好的共享内存区域传参数和返回值通道只负责触发和状态回传。这个模式看着复杂但在批量签名场景下稳定性提升非常明显。3. 硬骨头私钥签名链路与链上交互的高性能实现3.1 一条私钥从生成到签名的完整链路说私钥签名是硬骨头因为它不是单点问题而是一条链路。别着急写代码先把它拆清楚用安全随机数生成 128~256 位熵按 BIP39 规范把熵映射成助记词用 PBKDF2-HMAC-SHA512 把助记词加盐迭代成种子按 BIP44 路径从种子派生主密钥、子密钥从公钥计算链上地址对交易摘要或消息做哈希后执行 ECDSA 签名。这个过程里每一步都有鸿蒙化适配的触点。第一步的安全随机数不能沿用普通随机源第三步的 PBKDF2 迭代次数高纯 Dart 跑起来偏慢第六步的 secp256k1 运算在 Dart 层也不是强项。真正聪明的做法是把 1、3、6 这些计算密集或安全敏感的操作下沉到鸿蒙原生层把 2、4、5 这类偏编码、偏格式的逻辑留在 Dart 层。下沉之后我还要做一件事——对拍测试。签名算法的特性就是同样的输入必须产生同样的输出或者至少验签结果必须一致。我准备了一组公开测试向量先走纯 Dart 实现跑一遍再做鸿蒙原生实现跑一遍两边结果逐字节比对。这一步做扎实了后面才敢谈优化。3.2 性能优化从一键签名到批量签名很多链上应用并不满足于单笔签名而是要批量转账、批量授权、批量生成地址。on_chain 的性能压力恰恰来自这里。纯 Dart 实现里secp256k1 的标量乘法要循环很多轮几百次派生下来延迟就上去了。鸿蒙原生 Crypto Framework 在这类椭圆曲线运算上就有明显优势实测下来派生公钥和签名的耗时能缩短一个数量级上下具体数值跟硬件有关但体感差异是确定性的。性能优化除了走原生代码设计上还有两个小技巧。第一是派生路径缓存同一个 BIP44 路径如果反复使用把派生结果缓存到内存里避免重复计算第二是批量预派生钱包初始化时一次派生未来可能用到的一批地址索引后续签名直接取缓存。这两个技巧配合原生下沉能把批量场景的吞吐顶上去一个量级。3.3 安全存储与随机数的红线关于私钥、助记词的存储我的原则很简单私钥不允许躺平在普通文件或普通配置里。鸿蒙生态里系统安全存储服务才是合理落点。适配 on_chain 时我给存储服务定了三层约束第一助记词和私钥只能经安全存储服务写入业务层拿到的只是别名索引第二写入前必须在内存中完成格式校验避免脏数据落盘第三读取时返回的明文只允许在一次签名事务内存在用完立即置空。安全随机数同理。Dart 的Random()本质是伪随机连普通安全场景都不够格更别说生成私钥。鸿蒙原生侧的强随机接口才是正确的源。我做的适配是在 Dart 层封装了一个SecureRandomService所有涉及私钥生成的入口都强制走这个服务哪怕只生成一个临时 nonce 也不例外。这条红线守不住后面所有签名验签都失去意义。3.4 链上交互通道、心跳与数据可信on_chain 的链上交互主要分两类HTTP 轮询和 WebSocket 订阅。轮询相对简单鸿蒙上的网络权限对齐好之后Dart 层的HttpClient基本能正常工作。真正麻烦的是 WebSocket 长连接状态管理。鸿蒙应用切后台再回前台连接可能已经断了如果 on_chain 还认为通道可用后续请求就会静默失败。我在适配里加了一个链上会话管理器监听应用生命周期信号回前台时自动检查连接状态断线则透明重连。重连之后还要做一次数据校准把断线期间可能漏掉的事件重新拉一遍。这个机制看着轻巧实际上决定了资金类操作能不能在弱网环境下保持准确。数据可信还有一层含义节点返回的余额、交易状态如果只存在内存里很容易被篡改。on_chain 的账本层默认做了本地快照的签名绑定任何一次状态更新都会附带刷新后的摘要后面消费数据时先验再展示。4. 多链资产治理抽象统一还是治理混乱4.1 协议层先把链抽象成数据多链治理的难点不在 UI而在协议层。on_chain 的答案是引入ChainDescriptor这个概念每个链都被描述成一个配置对象包含链类型、节点端点、币种精度、浏览器前缀等元信息。适配鸿蒙时这部分纯 Dart 逻辑完全保存不用动。真正需要落地的是协议层的分发策略。EVM 兼容链之间虽然 RPC 大同小异但不同链的交易结构、费用模型差异很大UTXO 系链的交易组装逻辑更是另一套。on_chain 内部按链类型注册了一批 Handler每个 Handler 负责交易构造、解析和签名装配。鸿蒙适配不影响这套结构因为 Handler 本身是 Dart 实现的只要底层调用签名服务和链上通道保持统一即可。4.2 统一资产模型与状态同步资产治理是用户最直观感知的部分。我强烈建议不要为每条链单独造一套资产字段而是定义统一资产模型来源链、合约地址、精度、余额快照、授权状态、最近更新时间。余额快照要跟链上事件挂钩收到一笔转账事件就自动刷新对应资产的快照。鸿蒙侧的状态同步用的是轮询 订阅双轨常规余额走低频轮询交易确认和跨链消息走 WebSocket 订阅。双轨同步最怕数据不一致我在适配里给每条状态记录加了更新时间戳和事件序号消费端合并数据时永远以最新事件序号为准。这套机制在多链场景下能显著减少某些链余额刷新了、某些没有的割裂感。4.3 核心治理流程的鸿蒙化落地我把资产治理拆成三个核心流程聚合查询、授权检查、跨链流转。聚合查询的逻辑是并发向多个链节点发请求聚合成一张统一资产列表。授权检查是针对需要预授权的代币类型查当前授权额度是否足够不足则引导用户发起授权交易。跨链流转最复杂要构造源链交易、签名、广播之后在目标链等待确认消息。这三个流程在鸿蒙上的适配关键点完全不同。聚合查询吃网络和并发要控制线程池上限避免一次性打垮节点。授权检查吃状态缓存授权额度要高频刷新。跨链流转吃签名和重试签名要稳定重试要可靠。我在代码里分别给三个流程建了独立的配置项这样在鸿蒙上可以根据设备性能动态调整并发数和缓存策略。5. 实操全过程从空工程到完整适配可复现路线5.1 环境准备与工程初始化我复现一下整个适配路线方便你对照操作。第一步是搭环境装好官方 IDE配好鸿蒙 SDK把 Flutter SDK 切换到带ohos平台支持的分支。第二步创建工程执行 Flutter 工程创建命令后项目里会生成ohos/目录这个目录就是鸿蒙原生壳。第三步是改pubspec.yaml把 on_chain 及依赖的库解析到兼容鸿蒙的版本。这里有个小经验先逐个依赖跑一遍编译而不是一步到位全部改完。每轮只看一个依赖的错误改完一轮再进入下一轮不然错误信息会叠在一起人很容易懵。整个过程保持 IDE 和 Flutter SDK 版本固定不随意升级。5.2 移植顺序先能力层后业务层我的移植顺序跟直觉正好相反先做原生能力层再做业务接入。具体分四步把 on_chain 的底层服务抽象成接口暂时保留 Android 实现保证 iOS/Android 还能编译用鸿蒙原生工程实现这些服务接口写 ArkTS 代码接入 Platform Channel 或 FFI 绑定让 Dart 侧能调到鸿蒙实现最后才切on_chain的配置开关把默认实现从 Android 换成鸿蒙。这个顺序的好处是每一步都有可运行的工程不会出现改了三天代码、最后一把编译过不去的情况。我见过太多人一上来就改业务代码最后链条断裂定位问题花了大量时间。5.3 关键代码改造示例敏感部分已脱敏下面给一段 Dart 侧接入安全存储服务的示例真实代码有所精简但结构一致// 服务抽象业务层只面对这个接口 abstract class SecureStoreService { FutureString write(String alias, String data); FutureString? read(String alias); Futurevoid delete(String alias); } // 鸿蒙平台实现内部通过 MethodChannel 与原生通信 class OhosSecureStoreService implements SecureStoreService { static const MethodChannel _channel MethodChannel(on_chain/secure_store); override FutureString write(String alias, String data) async { final result await _channel.invokeMethodString(write, { alias: alias, data: data, }); if (result null) { throw Exception(secure_store write failed); } return result; } override FutureString? read(String alias) async { return _channel.invokeMethodString(read, {alias: alias}); } override Futurevoid delete(String alias) async { await _channel.invokeMethod(delete, {alias: alias}); } }ArkTS 侧的实现思路大致是这样注册通道后在write/read/delete三个分支里调用系统安全存储能力。注意返回值要处理成 Dart 侧能识别的标准类型不要自定义对象否则序列化会出问题。5.4 联调与验证从能编译到能跑通编译通过只是第一步真正拿得准是联调验证。我通常准备一块鸿蒙真机检测顺序按照链路依赖关系先测安全随机数生成再测助记词生成接着测地址派生然后测签名和验签最后测链上广播和资产查询。每个环节都有一组固定测试向量这条链路全部通过才算适配完成。还有一种专项验证值得做异常注入。我会人为制造安全存储读取失败WebSocket 断线签名结果超时等场景确认 on_chain 的降级逻辑是否按预期执行。这类验证在模拟器上很难触发真机上通过改测试代码制造环境才靠谱。6. 踩坑实录鸿蒙化适配期间的常见问题与排查技巧6.1 高频问题速查表现象大概率根因解决方案编译报错找不到ohos平台Flutter SDK 分支不对或 IDE 版本过旧锁定版本矩阵重新切换 SDK 分支插件通道调不通原生侧未注册对应 MethodChannel检查原生工程里的通道注册代码和模块配置签名结果验签失败字节序不一致或哈希算法调用错用测试向量对拍确认哈希输入和签名格式私钥派生耗时长secp256k1 纯 Dart 运算把派生下沉到鸿蒙原生 Crypto 能力WebSocket 连不上节点缺少网络权限检查 module.json5 是否声明 INTERNET 权限切后台再回前台请求失败长连接已断开但状态未更新接入生命周期监听重连并校准状态助记词写入后又读不出来安全存储服务的凭据作用域问题确认调用方签名和凭据获取方式正确批量签名速度上不去每次都派生相同路径无缓存加派生路径缓存和批量预派生ArkTS 侧拿到的参数类型异常Dart 侧传了自定义 Map 类型统一用基础类型传参复杂对象改成 JSON 字符串6.2 几个只有上手才能发现的定位技巧排查这类问题最忌讳对着日志猜。我的习惯是第一现场复现然后按数据流看问题出在哪一段。比如签名失败先看助记词是否一致再看派生路径是否一致接着看哈希输入是否一致最后看签名编码是否一致——逐段打点用一个断点定位出问题的一段。还有一个很实用的技巧保留一套纯 Dart 版本的 on_chain 实现当参考实现。跑鸿蒙版本得到的结果跟参考实现比对如果参考实现验证通过、鸿蒙版本验证失败那问题一定出现在原生层如果参考实现本身就跟测试向量对不上那先排查纯 Dart 逻辑。这套对拍思维能帮你省下大量猜谜时间。6.3 后台链路与事件丢失的兜底手段链上钱包类应用最怕的事件丢失我做了一层兜底本地事件游标。每条链维护一个最近处理的事件序号断线重连后从这个游标往后拉补上漏掉的事件。针对适配鸿蒙时的场景我又加了一个冷启动拉取逻辑应用启动或回前台时先把游标之后的事件回来确保 UI 展示永远不落后于链上真实状态。兜底逻辑本身也可能出错所以我在事件入库前做了一次轻量验签能关联到交易哈希的事件要跟链上回执核对一次不能关联的至少做 JSON 格式和字段完整性校验。这套机制跑下来多链资产治理的准确率有明显提升。结尾做完整套鸿蒙化适配我最大的体会是跨端适配拼的从来不是写代码的速度而是对分层边界的敏感度。on_chain 的鸿蒙化看起来牵涉一堆底层替换但只要守住Dart 逻辑不碰、能力层抽象、原生实现可换这条线整个工程就稳得住。最后再分享一个小技巧适配完成后别急着收工把写过的服务接口和踩坑记录整理成一份内部适配手册下次再遇到其他 Flutter 库要往鸿蒙迁直接照着手册走成本能再降一半。这套能力抽象现在也沉淀了下来后续不管鸿蒙生态怎么变上层业务代码都不用再恐慌。