
最近在处理一个对接 Proffix ERP 的 Flutter 项目时我遇到了一个绕不开的难题项目里重度依赖的 dart_proffix_rest 这个三方库在被塞进鸿蒙工程后连编译都过不了。这个库本身在 Flutter 生态里不算热门但在瑞士和德语区的制造企业里Proffix ERP 的占有率其实相当可观国内不少做外贸系统、海外供应链软件的团队也会碰到类似需求。这篇内容就是围绕 dart_proffix_rest 的鸿蒙化适配展开的适合正在做 Flutter 跨端项目迁鸿蒙、或者需要对接国外 ERP 系统的移动端开发同学参考。我会把依赖拆解、Proffix 接口模型、插件桥接、登录认证这些环节一个个讲清楚也会穿插一些真实踩坑记录尽量让这篇东西可以直接拿来当适配手册用。1. 直接把 dart_proffix_rest 塞进鸿蒙工程会先卡在哪一层1.1 先认识一下 dart_proffix_rest 的真实构成很多人在适配前习惯性地打开库源码试图一行行去改其实没必要。dart_proffix_rest 本质上不是一个重型的原生插件它更像是一个对 Proffix REST API 的 Dart 封装库内部实现的核心逻辑是构造请求、处理认证、解析 JSON、把 ERP 里的业务对象映射成 Dart 模型。可以理解成它替你写好了怎么跟 Proffix 服务器说话这一层但真正干活时它依赖的是 Dart 生态里那些非常基础的纯 Dart 包。我翻了下我项目里锁定的版本主要依赖集中在 http、intl、json_annotation 这类包上。这类包有个共同特征它们不依赖任何 Android 或 iOS 原生能力也不需要打开相机、读取定位、操作剪贴板所以理论上把它们跑在鸿蒙环境的 Flutter 引擎上问题不大。但也别高兴太早真正让项目卡住的通常不是 dart_proffix_rest 它自己而是你为了完成移动办公自动化需求而加入的一堆配套插件。比如本地缓存要用 shared_preferences扫码要用摄像头插件离线同步要用数据库插件这些插件如果不提供鸿蒙实现那才是第一道真正的大坎。1.2 卡住的地方往往不是 Dart 代码而是插件和平台通道这里要展开说一个容易被忽略的点。Flutter 在鸿蒙上的运行机制和它在 Android 上并不完全一样。鸿蒙上跑 Flutter需要依赖 OpenHarmony 社区维护的 flutter_flutter 分支以及配套的 Flutter 引擎的鸿蒙实现。纯 Dart 代码在这个体系里是通用的但只要你用了任何带原生能力的插件就必须保证这个插件在鸿蒙侧有对应的实现类。我之前遇到的情况就很典型项目在 Android 上编译顺利但切到鸿蒙工程后dart_proffix_rest 本身的编译过了反而是它间接依赖的某些插件报错原因就是这些插件只在 pubspec.yaml 里声明了 android 和 ios 两端的 pluginClass压根没有 ohos 端的声明。这里多说一句dart_proffix_rest 也并非绝对纯净。如果你拿到的版本里集成了本地密钥存储或者安全网络配置那就需要额外检查它是否通过 MethodChannel 调了原生能力。凡是走 MethodChannel 的鸿蒙侧就必须有对应的接收逻辑否则 Dart 里一调用就会抛 MissingPluginException。1.3 关键要理解鸿蒙 Flutter是哪一层的东西我接触过不少刚上手鸿蒙开发的 Flutter 工程师大家最容易有一个误解以为鸿蒙上跑 Flutter就是把 Android 工程里的 Gradle 改一改把 compileSdk 换成鸿蒙 SDK就能跑起来。真不是这么回事。鸿蒙 Flutter 的架构大致是Flutter 引擎本身被移植到了鸿蒙系统之上Dart 代码照常运行但 Flutter 和系统之间的桥接走的是鸿蒙侧的插件协议。你可以把鸿蒙侧的插件协议理解成一套类似 Android Plugin 生命周期的东西Dart 侧发一个 MethodCall鸿蒙侧通过实现特定接口来接收并返回结果。所以适配三方库本质上是在做两件事第一确认库的纯 Dart 层代码在鸿蒙引擎上能跑第二给库所依赖的每个平台通道补一个鸿蒙实现。理解了这一层后面所有适配工作就都有方向了。千万别一上来就去改库的源码先做依赖检测再逐个补通道这才是正确的顺序。2. Proffix ERP 的调用方式与鸿蒙端实现方案的逐项对照2.1 Proffix REST API 的请求模型回顾如果你没接触过 Proffix我先用大白话描述一下它的工作方式。Proffix 是瑞士一家 ERP 厂商它的系统在中小型制造企业里用得很多模块涵盖订单、采购、库存、财务这些常规 ERP 能力。它对外提供 REST 接口dart_proffix_rest 这个库就是把这些接口封装成了 Dart 方法。Proffix REST API 的调用模型并不复杂核心流程通常是先通过认证接口登录获取一个会话标识之后对业务数据的增删改查基本都是通过标准命令接口来发请求。每个请求里业务命令、查询条件、数据版本这些参数会被拼装成一个结构体服务器返回 JSON 格式的结果。如果你的团队不是 Proffix 的老客户大概率对接的是它的云端实例或客户现场服务器。不同 Proffix 版本的接口前缀可能略有差异但请求模型基本一致。dart_proffix_rest 库内部做的其实就是把这套流程做了 Dart 封装导出类似 ProfixRestClient、PxObject 这样的统一入口让你不用手动去拼每一次 HTTP 请求。2.2 鸿蒙端网络栈的取舍既然库走的是 REST 调用鸿蒙适配时绕不开网络栈的问题。我用的是 http 包的情况这个包是纯 Dart 实现在鸿蒙 Flutter 分支上可以直接跑不需要额外适配。但这里我要提醒一个容易被忽略的细节企业级 ERP 对接尤其是国外客户的项目经常遇到自签证书、双向 TLS 校验、内网代理这些需求。如果这些需求都堆到 Dart 侧处理在 Android 上可能还算顺手但到了鸿蒙上你会发现自己对鸿蒙原生的网络库不够熟悉处理起来会很别扭。我的建议是Dart 侧只负责业务逻辑和报文拼装凡是涉及证书信任、代理设置、SSL 握手这类底层网络配置的尽量放在鸿蒙原生侧解决。鸿蒙系统对网络安全配置有自己的管理方式可以通过系统的网络安全配置或者原生网络栈实现这样既安全又不会破坏 dart_proffix_rest 本身的跨端性。当然如果项目只是走公网 HTTPS 标准认证证书是正规 CA 签发的那完全不用动网络栈dart_proffix_rest 直接就能连服务器。这一点是很多教程没有明确说的。2.3 改造前的依赖项检测清单动手前我建议先做一张表把你项目里所有依赖项过一遍。我把自己当时排查的模板列出来你可以直接照着抄依赖包用途是否纯 Dart鸿蒙适配成本http网络请求是无直接可用intl日期与数字格式化是无直接可用json_annotationJSON 序列化是无直接可用shared_preferences本地键值存储否需要替换或补 ohos 实现sqflite本地数据库否需要补 ohos 实现或改用鸿蒙原生数据库path_provider获取目录路径否需要补 ohos 实现自定义业务插件扫码、文件预览等否需要逐个写鸿蒙插件做完这张表你基本就能判断 dart_proffix_rest 的鸿蒙化难度了。如果它依赖的只有上面前三行那种纯 Dart 包那恭喜它的适配成本几乎为零。但项目整体能不能在鸿蒙上跑起来还得看你周围一圈插件是否具备鸿蒙实现。孤立的库永远好适配真正麻烦的是插件生态环境。3. 鸿蒙工程里的插件落地MethodChannel 与 EventChannel 的实战接法3.1 工程结构上怎么处理当你确认某个插件在鸿蒙上没有现成实现时一般有两种做法。第一种是找社区是否有人维护了该插件的鸿蒙分支第二种是自己在鸿蒙工程里写一个插件模块。我这次是两种都用了dart_proffix_rest 本身不用动但项目里另一个用于拍照上传的插件没有鸿蒙实现于是自己补了。工程结构上鸿蒙 Flutter 项目和 Android Flutter 项目不太一样通常会有一个鸿蒙原生工程作为壳工程Flutter 部分以模块形式集成进去。你在写插件时需要在 pubspec.yaml 里声明鸿蒙平台的插件类。大致长这样flutter: plugin: platforms: android: package: com.example.xxx pluginClass: XxxPlugin ios: pluginClass: XxxPlugin ohos: pluginClass: XxxPlugin这里的 pluginClass 要与鸿蒙原生侧实现的类名保持一致。很多人在这一步栽跟头因为在 pubspec.yaml 里加了 ohos 声明但鸿蒙侧没有对应的类编译时会报找不到插件实现。3.2 自己写鸿蒙侧插件从继承 Plugin 开始鸿蒙侧插件实现我看下来和 Android 插件实现有相似之处但细节不同。你需要在你鸿蒙工程的 ohos 模块里新建一个类继承鸿蒙 Flutter 框架提供的插件基类然后实现方法调用分发。下面是一个简化的示例说明 MethodChannel 的接收端大概长什么样import { Plugin } from ohos/flutter_ohos/plugin; import { MethodCall } from ohos/flutter_ohos/plugin; import { MethodResult } from ohos/flutter_ohos/plugin; export class ErpPlugin extends Plugin implements PluginLifecycle { onInitialize() { console.info(ErpPlugin initialized); } onMethodCall(method: string, call: MethodCall, result: MethodResult) { switch (method) { case getErpVersion: result.success(harmony-erp-client-1.0.0); break; case openDocument: this.openDocument(call.arguments as string, result); break; default: result.notImplemented(); } } }从这个示例你就能感受到鸿蒙 Flutter 插件的开发模式和 Android 上的 MethodChannel 思路基本一致方法名、参数、回调三者缺一不可。Dart 侧通过 MethodChannel 调过来的方法在这里被分发到对应分支执行执行完通过 result.success 返回数据。写插件时有一点要特别留意Dart 侧传过来的参数类型在鸿蒙侧并不总是能保持和 Android 侧一致的映射关系。比如 Dart 里的 int在 Android 侧可能是 Integer到了鸿蒙侧可能是 numberDart 里的 Map鸿蒙侧可能被转换为 Object 或者 Record。如果不做类型校验直接强转很容易运行期崩溃。所以我的习惯是在鸿蒙侧每个方法入口先做参数体检字段缺了就返回错误码绝不硬解。3.3 EventChannel 在 Proffix 长会话场景的应用MethodChannel 解决了从 Dart 到原生的调用问题但企业级 ERP 对接里还有一类场景是从原生反向通知 Dart。比如 Proffix 会话快过期了、后台同步任务进度变了、服务器推送了一条审批待办——这些都不能靠 Dart 侧定时轮询解决最好的做法是用 EventChannel。EventChannel 的核心特点是单向事件流鸿蒙原生侧作为事件源Dart 侧作为监听方。它在 flutter 和鸿蒙之间的使用方式和 MethodChannel 的声明在 pubspec.yaml 里是并列关系。鸿蒙侧的实现大体是这样的思路注册一个 EventStream 实例把需要上报的事件通过它发给 Dart 侧。我在 Proffix 场景里主要用它上报三类事件会话状态变化登录成功、会话即将过期、会话已失效数据同步进度后台拉取物料、订单、库存时进度百分比实时推送给 UI文档生成通知ERP 侧生成 PDF 报告或导出文件完成后通知 Dart 侧去下载。这种设计比起让 Dart 侧每隔几秒去请求一次服务器既省流量又不会因为频繁轮询给 ERP 服务器造成额外压力。企业 ERP 服务器通常不希望移动端像爬虫一样高频请求EventChannel 在这样的场景下是更礼貌的做法。3.4 构建配置与打包验证插件写完不代表就完工了还有两个常见的坑需要提前知道。第一鸿蒙工程里运行 Flutter 模块时需要确保鸿蒙原生模块的权限声明到位。比如你的 ERP 客户端要联网就必须在鸿蒙工程里申请网络权限否则 dart_proffix_rest 发出的请求会被系统静默拦截表现就是请求超时、数据为空、甚至报底层网络异常。这种问题特别容易伪装成库适配失败。第二鸿蒙工程引入 Flutter 插件时Gradle 或构建工具的兼容性需要检查。我见过一个比较典型的报错是关于 Flutter 的 main Gradle plugin 被 apply 方式触发的错误这个报错通常说明你的构建脚本还在按 Android 的习惯组织 Flutter 插件没有切换到鸿蒙工程的组织方式。遇到这类问题不要慌检查 your Flutter 模块的插件声明、以及鸿蒙工程对 Flutter 的依赖配置方式一般都能对上。全部配置好后建议先写一个最小验证页一个按钮点击后调用 dart_proffix_rest 里的登录方法在界面上显示登录结果。这一步跑通说明 Dart 侧到鸿蒙侧的通道是通的网络栈也是通的后续业务页面再逐个接入就顺理成章了。4. 登录认证、会话保持与安全存储企业级系统最容易翻车的三件事4.1 Proffix 登录认证的完整流程对接任何 ERP认证永远是第一个绕不过去的环节。dart_proffix_rest 对认证的封装我建议你在适配前先读一遍源码搞清楚它到底用的是什么认证方式。Proffix 的认证流程我接触下来大致是这个套路客户端向服务器发送认证请求携带用户名、密码以及必要的请求头服务器校验通过后返回一个会话标识或者令牌。之后的每一次业务请求都要带上这个会话标识证明我是谁。如果会话超时服务器返回认证失败客户端就需要重新登录或者刷新令牌。在鸿蒙适配时这段逻辑本身是纯 Dart 的几乎不用改。真正要动脑子的是下面两件事第一认证信息不能明文落在本地令牌存储必须改成鸿蒙安全的存储方案第二会话失效后如何让用户无感重登而不至于在 ERP 里做到一半被打断。4.2 鸿蒙端安全存储的适配在 Android 上很多人存令牌会用 shared_preferences 或者 EncryptedSharedPreferences。到了鸿蒙上这两个方案都不再适用。dart_proffix_rest 如果是自己内部管理令牌通常会让调用方传入存储实例这就是一个很好的扩展点。我的做法是在鸿蒙端不再把令牌交给 Flutter 侧的 shared_preferences而是走鸿蒙系统提供的安全存储能力。将令牌、刷新令牌、服务器地址等敏感信息存在系统安全区域Dart 侧通过一个自定义插件来读写。这样有多层好处其一应用被卸载或数据被备份时敏感信息不容易被直接导出其二鸿蒙系统的安全存储默认有权限边界第三方应用读不到。这里有一个细节要提醒就是密钥和存储项的类型设计。不要把用户名、密码、令牌全部塞进一个键值对里尽量按条目拆开存放并给每个条目设置独立的访问控制。企业 ERP 的审计要求通常会关注这一点而且分开存储也方便未来单独更新某个字段。4.3 会话超时与重连策略会话超时是移动办公里最影响体验的隐性炸弹。用户在车间用 PDA 扫了半天条码突然要提交数据时提示登录已过期如果处理不好用户恨不得把手机摔了。我在鸿蒙端的处理思路是这样的利用前面提到的 EventChannel让鸿蒙原生侧监听网络与系统时钟状态在本地预判会话即将过期时提前给 Dart 侧发通知Dart 侧收到后静默调用刷新令牌接口。如果刷新失败再提示用户重新登录并保留当前页面填写的数据。另外要注意鸿蒙系统对后台任务的调度有严格限制。如果 App 切到后台超过一段时间定时刷新逻辑可能不会被执行。所以不要过度依赖 Flutter 侧的 Timer 来刷新会话而是要在用户切回 App 前台时主动做一次会话状态检查。这里说的检查不是恶意打断用户而是先静默验证令牌是否有效无效就快速重登有效就继续放行。5. 结合跑路现场那些年报错信息和排查思路5.1 白屏、控件不显示先怀疑 PlatformView在企业移动办公的 App 里接入 WebView、地图、文档预览这些原生视图是家常便饭。Flutter 对这些原生视图的封装统称 PlatformView。在 Android 上跑得好好的 PlatformView到了鸿蒙上可能会出现白屏、显示错位、触摸失效这些问题尤其是快速切换页面时表现更明显。我排查过一个非常诡异的 bugERP 物料图片预览页第一次进入正常退出再进入就黑屏。起初我以为是内存问题反复加日志发现是 PlatformView 在页面销毁时没有正确释放导致鸿蒙侧的原生视图还挂在窗口层级里Dart 侧却已经把它对应的 Flutter 页面销毁了。处理方案通常有两个方向一个是为 PlatformView 自定义生命周期管理在页面销毁时主动通知鸿蒙侧释放原生视图另一个是尽量用 Flutter 自绘组件替代原生视图比如文档预览如果只是简单展示 PDF可以直接用 Flutter 的渲染方案减少对原生控件的依赖。5.2 Flutter 日志报错怎么看从 DartVM 到鸿蒙侧看到类似E/flutter [ERROR:flutter/runtime/dart_vm_initializer.cc(41)] unhandled exception这样的报错很多同学第一反应是鸿蒙适配的问题。说实话这个报错只说明一件事Dart 层有异常没被捕获至于异常原因是什么得看后面的堆栈。在鸿蒙工程里排查这类问题我建议按照这个顺序走先看 Dart 堆栈顶部的异常类型如果是 MissingPluginException说明某个 MethodChannel 没有鸿蒙实现去对照插件的 ohos 声明如果是网络请求失败去鸿蒙侧的 hilog 查网络日志确认请求有没有真正发出去如果是 JSON 解析失败检查 Proffix 返回的数据结构和 dart_proffix_rest 里定义模型是否匹配。这里放一张我做好的排查对照表你可以贴在墙上报错特征大概率原因排查方向MissingPluginException插件缺鸿蒙实现检查 pubspec 插件声明与鸿蒙侧 pluginClass网络超时、SocketException权限、代理、证书问题查鸿蒙网络权限与网络安全配置白屏、渲染异常PlatformView 内存释放问题检查原生视图生命周期401/会话过期频繁令牌刷新逻辑未生效检查 EventChannel 与重登流程模型字段解析失败Proffix 版本与库版本不匹配打印原始 JSON逐字段核对5.3 路由切换后状态丢失的问题还有一个非常经典的问题就是 Flutter Navigator 切换页面后状态丢失。这个在普通 Flutter 项目里也存在但在鸿蒙上更容易踩中原因是鸿蒙系统的窗口和页面生命周期与 Android 不完全一致某些情况下页面会被销毁重建。ERP 类应用对状态丢失尤其敏感。用户在订单列表页滑到了第 50 条点进去看了详情返回后又回到第 1 条这种体验在企业现场会被骂死。我的解决办法有两条一是用 PageStorageKey 保存滚动列表的位置二是把 ERP 查询结果提升到更上层的状态管理器里让列表页销毁重建后可以直接从状态管理器恢复数据而不是重新请求服务器。如果你用了大量页签导航比如底部 Tab 切换首页、工作台、我的这几个模块鸿蒙要注意 Tab 切换是否会导致页面重建。实测下来鸿蒙下 Tab 页切换偶发会触发 rebuild这时候除了 PageStorageKey还可以在页面归档时手动缓存关键数据。针对 dart_proffix_rest 这类需要查询 ERP 数据的页面缓存尤为重要毕竟每一次强请求在车间网速不稳定的环境下都是煎熬。6. 适配完成之后移动办公自动化的几个高价值延伸方向dart_proffix_rest 完成鸿蒙化适配只是起点。真正让我觉得这项目值得做的是打通之后移动办公能延展出来的场景。首先是离线单据处理。车间里网络覆盖不稳定的情况太常见了适配完成后你可以利用本地数据库缓存常用基础数据比如物料清单、客户资料、库存余量让业务人员在无网环境下也能开单、盘点待网络恢复后通过 dart_proffix_rest 批量同步回 ERP。这里要注意Proffix 对数据冲突有自己的版本控制机制同步时最好先查询数据版本再做更新否则容易覆盖现场数据。其次是审批和消息联动。通过 EventChannel 把 ERP 的审批待办、订单变更通知推送到鸿蒙端让业务人员不用时刻登录后台就能获知业务变化。这一块和鸿蒙系统的通知能力结合好了体验会比原来的移动端方案提升一个档次。再者是设备能力的调用。移动办公不只意味着看数据还要扫码、拍照、签名、打印。dart_proffix_rest 负责把业务数据从 ERP 里拉出来鸿蒙端原生能力负责把这些数据落地到物理世界。比如用摄像头扫码获取物料编号再从 ERP 拉取库存信息现场完成库存盘点或者拍摄送货单照片自动关联到采购订单。还有一点是我的个人经验别把所有希望都押在一个三方库上。dart_proffix_rest 这种库通常只覆盖了 Proffix REST API 的常见子集如果项目里要使用比较冷门的 ERP 模块大概率需要自己在 Dart 侧扩展调用方法。适配鸿蒙时顺便把扩展点预留好比如在网络层加统一拦截器、在模型层留自定义字段映射口子后续维护会轻松很多。我个人在完成这个项目的鸿蒙化适配后最大的体会是Flutter 三方库的鸿蒙适配本质上不是一个技术难题而是一个工程管理难题。你需要把依赖拆干净把纯 Dart 和原生通道分开把认证、会话、存储这些企业级细节理顺再逐个补齐鸿蒙侧实现。只要前期的依赖检测做到位后面每一步都是按部就班的事情。如果你手头也在做类似的 ERP 移动端鸿蒙化项目希望这些经验能帮你少走一些弯路。