news 2026/10/12 2:48:25

Flutter for OpenHarmony电子合同App开发:API集成与平台适配实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Flutter for OpenHarmony电子合同App开发:API集成与平台适配实战

前阵子我们项目组接到一个需求:基于 Flutter for OpenHarmony 技术栈,做一款运行在 OpenHarmony 生态设备上的电子合同签署 App,而且要求尽可能复用现有 Flutter 业务代码。项目跑完我最大的感触是,这事并没有想象中那么顺利,真正的难点不在 UI 设计,而在后端签名 API 的集成、平台差异的适配、以及各种“在 Android 上没问题、一到鸿蒙容器上就表现诡异”的坑。这篇文章就按我们的实际推进顺序梳理一遍,重点放在 API 集成实现这条主线上,环顾整个方案选型和落地过程,给想用 Flutter 在 OpenHarmony 环境中做业务型 App 的团队一个可参考的样例。

这个项目适合谁看?如果你已经有一些 Flutter 基础,同时对 OpenHarmony 的应用开发了解不深,想搞明白“Flutter 代码能不能直接跑到国产系统设备上”,或者正在做商务、法务、办公类应用,需要对接电子合同服务商的能力,那这篇文章应该能帮你少踩不少坑。我不讲空洞的概念,只讲我们实际上怎么拆业务、怎么定架构、怎么把合同列表、合同详情、手写签名、签署提交这些链路一步一步接起来。

1. 项目全貌与方案选型

1.1 电子合同签署App实际包含哪些东西

电子合同,听着好像就是个“签名”功能,真拆开之后涉及的模块一点都不少。按我们当时的需求清单,至少有这么几块:

  • 合同管理:合同列表、合同详情、合同状态流转(待签、已签、已过期、已拒签等)
  • 文件服务:合同 PDF 的在线预览、本地缓存、下载分享
  • 签署能力:手写签名板、签名图片生成、签名摘要处理、签署位置定位
  • 实名与意愿校验:短信验证码、人脸核身(可选)、签署意愿确认
  • 通知中心:签署提醒、到期提醒、撤回通知
  • 个人中心:账号信息、印章管理、证书状态、签署记录

如果是在 Android 或 iOS 平台上做这些模块,很多现成插件可以直接用。但问题是这次要跑在 OpenHarmony 容器上,部分热门 Flutter 插件没有对应的平台实现,这就决定了我们不能无脑套以前的项目结构,必须提前做技术选型评估。

1.2 为什么选 Flutter for OpenHarmony,而不是直接用 ArkUI 重写

这是项目最开始大家讨论比较激烈的一个点。当时摆在桌面上有两个方案:一是完全使用 OpenHarmony 原生 ArkUI 重写一版,二是使用社区目前比较活跃的 Flutter 适配方案,把 Flutter 引擎跑到 OpenHarmony 设备上。

关于 ArkUI 重写:成本高。我们原本在 Android、iOS 上已经有一套比较完善的 Flutter 业务代码,合同列表、PDF 渲染、手写板这些模块都有沉淀。如果全部重写,意味着三端各养一套代码,后续合同模板调整、签署逻辑升级,每次都要同步三个端,工作量直接翻倍。

而 Flutter for OpenHarmony 的适配路线,原理上相当于把 Flutter Engine 通过一个移植层跑在 OpenHarmony 的 ArkUI 容器里,上层 Dart 代码保持不变。这意味着我们大部分 UI 层、状态管理、网络封装能直接复用,只需要把跟平台强相关的能力(比如系统相机、文件权限、Toast 等)替换成适配层提供的实现。实际上跑下来,业务代码复用率大约能到 70% 到 80%,剩下 20% 主要是裁剪掉 Android/iOS 特有依赖,替换成 OpenHarmony 可用的方案。

还有一个现实原因:团队人力有限。与其带着前端团队去啃一套新 UI 框架,不如让熟手 Flutter 开发继续做业务,新系统相关的适配工作集中给一位熟悉 OpenHarmony 的同事处理,这样人员配置上更平滑。而且后续如果需要回退到 Android 版本,代码还保留着,不至于把家底全部押在一套新生态上。

1.3 整体架构分层设计

架构上我们没有做得太花哨,沿用传统的分层方式,只不过在原有 Flutter 工程结构上增加了一层“平台能力抽象”。关键的分层如下:

层级职责关键技术点
UI 层页面组件、状态管理Provider / Riverpod 管理页面状态
业务逻辑层合同状态机、签署流程编排、业务规则纯 Dart 逻辑,不依赖平台 API
数据层网络请求、缓存、本地存储Dio 封装 + 轻量数据库或本地文件
API 网关层请求封装、鉴权、接口签名、错误码归一统一拦截器、Token 刷新策略
平台适配层与 OpenHarmony 能力桥接平台通道、方法调用适配、引擎差异抹平

这样设计的好处是,当 OpenHarmony 适配层的某些能力不完善时,可以只替换适配层实现,上层业务代码基本不动。比如合同文件下载功能,Android 上直接用某个插件管理下载,OpenHarmony 上我们换成了基于平台通道调用系统下载能力,业务层接口签名没变,页面的进度展示代码也完全复用。

2. 环境准备与工程搭建

2.1 开发环境清单与版本对齐

Flutter for OpenHarmony 的适配分支,跟原生 Flutter 官方稳定分支不太一样。我们第一次接触时吃了不少版本匹配的亏,所以环境准备这里值得单独说一句:别随便拿最新 Flutter stable 分支直接跑,很可能编译到一半发现引擎层和 OpenHarmony SDK 不匹配。

最终我们项目组统一的环境如下,供你参考:

  • 操作系统:Windows 11 与 Ubuntu 20.04 双平台开发,日常主力用 Ubuntu
  • IDE:DevEco Studio 用于 OpenHarmony 工程构建,VS Code 用于写 Dart 逻辑
  • Flutter SDK:使用社区适配 OpenHarmony 的分支版本,当时用的适配分支版本对应 Flutter 3.x 中的某个稳定小版本
  • OpenHarmony SDK:API 9 及以上(具体以你目标设备系统版本为准)
  • JDK:OpenJDK 17
  • 编译工具链:sdkmanager、hvigor 构建工具

这里特别提醒一点:适配分支迭代很快,第一次配置前一定先去仓库看 Release 说明,确认它支持你手头 OpenHarmony 设备的系统 API 版本。我们现场就有同事拿旧适配分支配新 API 版本设备,编译一直报缺少符号,浪费了大半天。

2.2 初始化工程与目录结构

初始化过程本身不复杂,基本还是 Flutter 那套流程。重点在于创建一个壳工程,这个壳工程是用 OpenHarmony 工程形态存在的,Flutter 模块以依赖形式挂进去。实际目录大致如下:

project_root/ ├── ohos/ # OpenHarmony 壳工程 │ ├── entry/src/main/ets # 原生入口与平台通道注册 │ ├── build-profile.json5 # 构建签名配置 │ └── hvigorfile.ts ├── lib/ # Dart 业务代码 │ ├── main.dart # 入口 │ ├── core/ # 网络层、路由、状态管理基类 │ ├── modules/ │ │ ├── contract/ # 合同列表、详情、状态卡片 │ │ ├── sign/ # 签名板、签署确认 │ │ └── user/ # 登录、实名、个人中心 │ └── services/ # 业务层服务封装 ├── pubspec.yaml └── assets/ # 静态资源

我们当时犯过一个错误,把业务代码直接写在壳工程的 ets 目录下,想着一起编译,结果 Flutter 侧方法找不到入口,后来老老实实把 Dart 代码独立出来,壳工程通过依赖方式引入 Flutter Module,问题就解决了。这种结构也方便未来抽离开源。

2.3 依赖管理与插件选型策略

pubspec 里的依赖不能照搬 Android 版项目。部分 Flutter 插件在 OpenHarmony 上没有原生实现,运行时会报 MissingPluginException。我们当时的选型原则是:

  • 纯 Dart 实现的库优先用,比如状态管理 Provider、路由 GoRouter、Dio 网络库、intl 本地化
  • 依赖平台能力的库,先查是否有人做过 OpenHarmony 适配。比如 shared_preferences 有社区适配版本,image_picker 需要仔细看支持程度
  • 拿不准的插件,用平台通道自己封装一层,保证业务层不被第三方库绑死

第三点尤其关键。签名板功能我们最开始想找现成 Flutter 库,试了其中一个手写签名库,在 Android 上正常,鸿蒙容器上画出来的笔迹容易丢点。后来干脆自己用 CustomPaint 实现了一套手写板,数据采集和图片生成都由自己控制,后续对接签名 API 反而更顺手。

3. API 集成实现核心细节

3.1 网络层统一封装与请求链路

电子合同类 App 的 API 集成,和普通资讯类 App 最大的区别在于:几乎每个接口都和敏感数据相关,请求链路里必须考虑身份凭证、时间戳、签名摘要、防重放这几个要素。所以网络层不能只是简单封装 Dio 就完事,要在拦截器层面把事情做透。

我们定义了一个 AppHttpClient,内部持有 Dio 实例,封装了这几个统一逻辑:

  • 基础地址管理:测试环境、预发布环境、生产环境通过编译环境变量注入,不写死在前端代码里
  • 公共参数注入:设备编号、应用版本号、时间戳、随机数
  • 身份凭证:从安全存储中读取 accessToken,自动放入请求头
  • 响应码归一:后端统一返回 code/message/data 结构,拦截器负责把业务码翻译成上层可识别的错误类型
  • Token 过期重试:识别 401 码后,串行刷新 Token,重新放行原请求

代码大致是这样一个思路:

class AppHttpClient { late final Dio _dio; AppHttpClient() { _dio = Dio(BaseOptions( baseUrl: EnvConfig.apiBaseUrl, connectTimeout: const Duration(seconds: 15), receiveTimeout: const Duration(seconds: 20), )); _dio.interceptors.add(AuthInterceptor()); _dio.interceptors.add(LogInterceptor(requestBody: true, responseBody: true)); _dio.interceptors.add(ErrorInterceptor()); } Future<Map<String, dynamic>> get(String path, {Map<String, dynamic>? params}) async { final response = await _dio.get(path, queryParameters: params); return _unwrap(response.data); } Future<Map<String, dynamic>> post(String path, {Map<String, dynamic>? data}) async { final response = await _dio.post(path, data: data); return _unwrap(response.data); } }

这里要小心一个细节:OpenHarmony 容器上的 Dart 网络栈对超时的处理严格程度和 Android 原生线程调度有关。我们在真机上测试时发现,部分低端设备偶现连接超时,后来把 connectTimeout 从 8 秒调到 15 秒,并加了重试机制,才比较稳定。这个值不建议照抄,看你业务接口实际响应速度调整。

3.2 登录鉴权与 Token 续期方案

电子合同 App 的登录一般不是简单的账密登录,通常要过实名认证。我们的实现链路由两步组成:

  • 第一步,账密登录拿到 authCode,这一步只代表用户在账号体系内通过校验
  • 第二步,用 authCode 换取正式 accessToken 和 refreshToken,这一步会校验设备的绑定关系、用户实名校验状态

安全存储上,我们没有用普通 SharedPreferences 存 Token,而是通过平台通道把敏感信息写入系统级加密存储。OpenHarmony 提供的安全存储能力跟 Android Keystore 类似,我们封了一个 SecureStorage 工具:

class SecureStorage { static const _channel = MethodChannel('app.channel/secure_storage'); static Future<void> write(String key, String value) async { await _channel.invokeMethod('write', {'key': key, 'value': value}); } static Future<String?> read(String key) async { return await _channel.invokeMethod('read', {'key': key}); } }

Token 续期这块用了一个比较实用的策略:拦截器里发现 401 时,用一个 AsyncLock 保证同时只有一个刷新请求在跑,其他请求进入等待队列,等新 Token 出来后自动重放。简单说,就是避免多个业务请求同时触发刷新,造成刷两次的两个 Token 互相覆盖。

说一下踩过的坑:OpenHarmony 的加密存储能力在 API 版本偏低的设备上,返回值可能是空。我们加了降级策略,低版本设备允许将 Token 写入受限权限文件目录,但提醒用户设备系统版本过低。实际上企业客户设备基本都能保持系统升级到指定版本,这个坑最后影响面不大。

3.3 合同文件上传与下载的实现要点

合同签署流程中,文件服务是绕不开的。整个链路大概是:

  • 上传:用户本地做好一份草稿合同(PDF),调上传接口获取文件 ID
  • 下载:合同发起方签署完,接收方要在 App 内预览 PDF
  • 归档:签署完成后,服务端生成最终的合同归档版本

文件上传我们采用分片上传方案,因为 OpenHarmony 设备里面存的大体积 PDF 不少,一个几十 MB 的附件如果直接 binary 上传,失败率会很高。分片大小我们定为 2MB,每片上传后服务端返回合并凭证,最后再调 merge 接口触发服务端合并。

Future<String> uploadFile({required String path, required String fileName}) async { final file = File(path); final totalSize = await file.length(); const chunkSize = 2 * 1024 * 1024; int offset = 0; List<String> chunkIds = []; while (offset < totalSize) { final bytes = await file.readBytes(offset: offset, length: chunkSize); final chunkId = await _api.uploadChunk( fileName: fileName, fileBytes: bytes, ); chunkIds.add(chunkId); offset += chunkSize; } return await _api.mergeChunks(fileId: fileName, chunkIds: chunkIds); }

这有一个非常实际的问题:Dio 默认的 MultipartFile 在内存中创建时,会直接把整个文件读进内存。像我们这种大文件场景,手机内存本来就不宽裕,所以上传分片时特意用了 onSendProgress 回调,并且手动控制每次只读一个 chunk 的字节,避免一次性加载整个文件导致的 OOM。关于断点续传我们暂时没做服务端支持,但如果你的服务商支持 Range 请求,这个逻辑可以往后扩展。

下载合同文件的时候,我们原来是直接下载到临时目录,再手动拷到应用文档目录。后来发现 OpenHarmony 对用户可见目录的权限管控和 Android 不完全一致,如果你用系统相册或者文件管理器打开下载目录,可能找不到你的合同文件。正确做法是走系统文件保存能力,经由平台通道把 PDF 写到用户可访问的公共目录,同时返回一个 URI。这块各版本 API 差异较大,建议你们在目标设备上提前验证。

3.4 电子签署 API 对接:签名数据如何提交

电子合同签署 API 的对接是整个项目的核心环节。这里需要注意,签署 API 和普通表单提交不是一回事,它一般要求你提交四个层面的数据:

  1. 签署位置信息:合同中的页码、坐标域、签署区尺寸
  2. 签名图形信息:手写签名位图转 Base64 后的数据
  3. 签署摘要:对原始合同 PDF 内容做摘要计算,防止正文被篡改
  4. 时间戳与意愿确认标识:用户确认签署动作的时间点与校验凭证

签名板模块我们自研后,生成的是一张透明背景的 PNG,白底会遮住合同原文,影响布局。所以签名数据采集时,注意把图片背景设为透明,压缩成 PNG 再转 Base64。

ByteData? byteData = await _signatureController.toImage(pixelRatio: 2.0); Uint8List pngBytes = byteData!.buffer.asUint8List(); String base64Str = base64Encode(pngBytes);

然后调签署提交接口:

final res = await _api.submitSign( contractId: widget.contractId, pageNo: currentPage, signX: signX, signY: signY, signWidth: signWidth, signHeight: signHeight, signImageBase64: base64Str, digest: contractDigest, timestamp: serverTime, );

关于签名摘要,我们一开始理解有偏差,以为前端自己计算摘要后传给后端就行。后来服务端同学提醒,摘要算法必须和服务端约定统一,否则你算出来的摘要跟服务端验签的摘要对不上。而且摘要计算不能基于整张图片,要按照服务端规定的字段拼接规则来做。最简单的对接方式是让服务端提供一个“获取待签摘要”的接口,前端传合同 ID 和用户 ID,服务端返回拼接后的摘要字符串,前端直接把它作为参数传给签署接口。这样做能避免两端摘要逻辑不一致的问题。

如果项目合规性要求比较高,可能还要考虑国密算法支持,比如用 SM3 替代 MD5 做摘要,用 SM2 做签名。这个一般要看合同服务商的后端能力。我们项目里因为服务端框架本身已经支持了国密体系,所以前端只需要按照它 SDK 文档约定的算法来生成前期签名数据即可,并不复杂,就是注意别用错 Base64 的编码格式,比如标准 Base64 和 URL-safe Base64 不要混。

4. 核心功能落地:从合同列表到签署完成

4.1 合同列表分页与状态管理设计

合同列表这类页面,业务逻辑本身门槛不高,但在 OpenHarmony 平台上做分页加载有个容易忽视的点:列表组件和 Flutter 默认的 ListView 在大量节点渲染时,性能表现和 Android 平台有明显差异。特别是合同卡片里带缩略图、状态标签、时间等多个组件的情况下,快速滑动时在低端设备上会有掉帧。建议直接使用 ListView.builder + 固定 item 高度,避免动态高度带来的重复测量。

分页我们采用传统的 page/pageSize 模型,下拉刷新重置第一页,触底加载下一页。这里有一个业务规范:电子合同的列表是有状态约束的,后端不会把所有合同一次性吐出来,要么按用户身份过滤,要么按签署状态过滤。所以前端在设计筛选标签时,请求参数里要带上 status 字段,别全量抓取再本地筛选,那样既消耗流量又容易出权限问题。

代码上我用了一个简单的分页状态类:

class ContractListController extends ChangeNotifier { int _page = 1; bool _hasMore = true; bool _loading = false; List<ContractModel> _items = []; Future<void> loadFirstPage() async { _page = 1; _hasMore = true; final data = await _api.getContracts(page: _page, pageSize: 20, status: _status); _items = data.items; _hasMore = data.hasMore; notifyListeners(); } Future<void> loadNextPage() async { if (!_hasMore || _loading) return; _page++; final data = await _api.getContracts(page: _page, pageSize: 20, status: _status); _items.addAll(data.items); _hasMore = data.hasMore; notifyListeners(); } }

这里想提醒一点:OpenHarmony 上列表的滚动事件回调在快速滑动时可能出现偶发丢事件,如果你用 ScrollController 的 listener 来触发加载更多,建议加一个 200ms 的防抖,否则一次快速滑动可能触发多次网络请求,虽然不是致命的 bug,但是后端同学会看到大量重复请求。

4.2 合同详情页与 PDF 预览方案

合同详情页主要有两块内容:合同元信息展示和合同正文预览。合同正文是 PDF 格式,手机上最常见的预览方案是加载到 WebView 里渲染。Android 上 Flutter 官方的 webview_flutter 插件可以直接用,但 OpenHarmony 容器上,我们需要通过 MDC(Multi-Device Container)的 Web 组件来做预览。

实际做法是:在壳工程的 ets 页面里放一个 Web 组件,通过平台通道把合同文件的 URI、标题、页码等信息传过去,然后由原生侧加载 PDF。Flutter 侧只需要占据一个占位区域,等原生侧回调预览准备完毕后加载 WebView。

这里有一个实际体验问题:如果直接把 WebView 覆盖在 Flutter 视图上,触摸事件会被原生控件抢过去,导致 Flutter 的返回手势失效。我们处理办法是把 WebView 放进一个单独的全屏路由页面,打开合同预览时直接跳到原生页面,返回时再切回 Flutter 页面。从用户视角看,它就是一次普通页面跳转,但开发上省掉了很多手势争夺的麻烦。

PDF 预览的加载进度也需要处理。PDF 文件比较大的时候,Web 组件首次加载白屏时间会超过 3 秒。我们做了一个 loading 状态页,在原生侧监听加载完成事件后,通过回调通知 Flutter 关闭 loading 蒙层。千万别小看这个细节,用户如果在签署过程中看到白屏,第一反应就是重新打开 App,容易造成重复进件的问题。

4.3 手写签名板:从轨迹采集到图片生成

签名板是电子合同 App 的核心交互组件。自研方案总体分三步:

  • 第一步,CustomPaint 绘制笔迹路径,记录每个点的时间戳和坐标
  • 第二步,手指抬起时,把路径序列转换成 Canvas 绘图指令,生成透明背景 PNG
  • 第三步,把签名图片压缩到服务端要求的分辨率范围,转 Base64 参与签署请求

签名的关键不只是图片大小,还有笔迹的平滑度。原始采集的坐标点如果不做处理,生成的线条会有明显折线感,尤其是 OpenHarmony 设备触摸采样率参差不齐。我们加了一个轻量级贝塞尔插值,在两个触摸点之间补出一段曲线,笔画观感会好很多。

final path = Path(); for (int i = 0; i < points.length - 1; i++) { final p0 = points[i]; final p2 = points[i + 1]; final p1 = Offset((p0.dx + p2.dx) / 2, (p0.dy + p2.dy) / 2); if (i == 0) { path.moveTo(p0.dx, p0.dy); } path.quadraticBezierTo(p1.dx, p1.dy, p2.dx, p2.dy); }

还有签名板尺寸适配问题。OpenHarmony 平板和手机的屏幕比例差异很大,如果签名板宽度固定,平板上会出现两个手指宽度的签名区域,用户还需要挪动手腕。我们最后采用按屏幕宽度比例的动态尺寸:手机占宽 0.9,平板占宽 0.5,高度统一占宽比例。这个细节看起来小,但实际对签署体验提升非常明显。

4.4 签署确认流程与异常中断恢复

签署不是一个动作,而是一条流程。我们的流程是:

  1. 用户点击签署按钮
  2. 弹出确认弹窗,展示合同名称、签署时间、签署份数
  3. 输入短信验证码进行意愿校验
  4. 调签署提交接口
  5. 显示签署成功状态并刷新合同列表

这里最容易出事的环节是第 4 步。如果用户点击“确认签署”后网络闪断,前端超时重试,而后端其实已经成功落库了,那用户这边会看到一次失败提示,但合同实际已经签了。再点击签署时,后端返回“合同已完成签署”,用户会误以为系统有问题。

针对这个问题,我们做了一版幂等化处理:每次签署请求在进入流程时,先生成一个客户端请求流水号 requestId,带着这个流水号去调签署接口。后端如果检测到相同流水号已经处理过,就直接返回成功结果,不会重复签署。如果你们对接的合同服务商不支持这种幂等机制,至少要做一个本地草稿状态机:发起签署后先把本地状态置为“签署中”,网络失败后提供“继续签署”入口,而不是让用户重新走整个流程。

我们用 OpenHarmony 的分布式数据库能力做了一个轻量任务持久化,签署请求发出前,先把携带的参数序列化存入本地,收到成功回调后再删除记录。下次 App 启动时,扫描未完成的签署任务,弹窗提醒用户“您有一份合同签署尚未完成”,这样能在很大程度上降低断网重签的客诉量。

5. 常见问题排查与避坑清单

5.1 OpenHarmony 容器上 Flutter 运行问题速查

问题现象可能原因解决思路
编译报错找不到某原生符号Flutter 适配分支与 OpenHarmony SDK 版本不匹配检查适配分支 Release 说明,对齐版本再编译
运行后 UI 正常但调用某个插件直接崩插件没有对应 OpenHarmony 平台实现改用平台通道自封装,或用社区专门适配插件
页面偶现黑屏,杀掉重进恢复引擎层内存回收或进度帧丢失清理页面缓存、减少复杂动画、检查路由恢复逻辑
本地文件路径读不到OpenHarmony 文件路径规则与 Android 不同不要硬编码路径,用平台通道获取系统目录
WebView 手势与 Flutter 冲突原生控件叠在 Flutter 视图上层将 WebView 独立为原生全屏页,避免同屏叠放
签名图片生成后边缘锯齿未开启抗锯齿或像素密度不足Canvas 绘制时设置 isAntiAlias,toImage 提高 pixelRatio

5.2 API 集成中的高频问题与调试经验

API 集成这个过程,我们遇到的另一个大坑是签名摘要的计算时机。最开始我们放在用户点击“确认签署”的时候才去请求摘要,这样如果用户在签署预览页停留了较长时间,后端生成的摘要可能已经过期。后来参考了服务商文档,把摘要在进入签署页面的时候就提前拉取,同时做 5 分钟有效期的本地缓存,超时后再重新获取。这样既缩短了确认签署到提交的间隔,也兼容了用户在签名板上反复书写的情况。

调试过程中,最容易忽略的是 Base64 字符串的编码差异。Java 层默认的 Base64 和 Dart 的 base64Encode 出来的结果,在某些特殊字符的处理上可能是兼容的,但如果你在服务端用了 URL-safe Base64,前端就必须对应切换。我们当时因为签名图片 Base64 里出现 +、/ 字符,被服务端网关拦截过一次,后来统一改成 URL-safe 编码才解决。真机上调试时,建议字符签名接口的请求响应日志里把敏感字段做掩码处理,避免现场排查问题的时候泄露测试数据。

5.3 平台通道使用与生命周期管理

OpenHarmony 场景下,平台通道的注册和注销要特别关注页面生命周期。Android 上 Flutter 插件生命周期管理相对规范化,而嵌入式容器场景下,native 页面销毁时如果通道没注销,后续调用会报 MissingPluginException。我们做了一个简单通道管理器,在所有原生页面 onPageHide 时统一释放未完成通道调用:

class ChannelGuard { static int _channelToken = 0; static int acquire() => ++_channelToken; static void release(int token) { // 通知原生侧释放资源 MethodChannel('app.channel/guard').invokeMethod('release', {'token': token}); } }

另外,调原生能力前先判断 isMethodImplemented,不然 API 版本低一点的设备上会直接抛异常。这一条越是老设备越重要。

5.4 性能与内存优化心得

OpenHarmony 容器跑 Flutter 引擎,内存开销比原生的 ArkUI 应用要高一些,我们在低内存设备上遇到过几次 OOM 前的卡顿。主要优化落在三个地方:

  • 列表图片用缩略图,而不是将大图直接载入 cell
  • 合同详情页的 WebView 进程在离开页面时主动销毁,而不是等系统回收
  • 签名板手写轨迹存储用轻量点集,避免在内存里保留整个笔画的对象

合同列表的缩略图我们是请求服务端返回 200x280 规格的封面图,不再在客户端用高分辨率 PDF 渲染第一页。特别是列表里如果同时展示几十个合同,每个都渲染 PDF 封面,对低端设备的压力非常明显。

6. 写在最后的几点心得

整个项目做下来,如果让我总结一个最重要的经验:在 OpenHarmony 生态里做 Flutter 应用,第一优先级是做好平台能力边界清单,先把哪些插件能用、哪些不能用在纸上列清楚,再开始写业务代码,能省掉至少一周的无用功。

第二点心得是关于 API 集成的。电子合同这类业务,后端接口往往不是一套内部自研服务,而是接的第三方法律合规服务商的开放平台,所以接口文档需要逐字去对。我们遇到过服务商文档写明“签名图片 base64 传参”,实际上要求的是去掉头部的纯 base64 数据,流程图上没提,只有在联调的时候才能发现。所以建议你们拿到 API 文档后,先写一个小工具把每个接口的入参出参打出来,逐一核对字段格式,再往界面上接。

第三点是团队协作上的体会。Flutter 开发和 OpenHarmony 原生开发的同事需要共同维护一个能力映射表。举例来说,Flutter 侧一个请求文件保存的功能,在 Android 上就是一个插件调用,在 OpenHarmony 上却涉及原生页面跳转、权限申请、URI 转换、回调管理,整个链路复杂得多。我们每周例会都过一遍这个映射表,谁动了底层能力,另一方及时感知,避免双方接口假设不一致。

最后再分享一个后续可以扩展的方向:我们现在的签署流程还是在线联动的,所有签署行为都必须实时连接服务端。下一步准备做离线签署能力,在无网环境下用设备本地安全区域缓存待签合同和签名密钥,等联网后批量提交。这个能力在部分工厂、园区现场场景里很有价值,也是 OpenHarmony 这类系统在行业应用上的一个典型需求。如果有团队已经在做类似方向,欢迎多交流,这条路值得一起趟。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/12 2:47:55

多语言微服务统一认证与权限管理:JWT、网关与Token生命周期设计实战

我们团队上一个项目从单体拆成微服务时&#xff0c;第一周线上事故不是数据库慢查询&#xff0c;而是用户登录掉得稀里哗啦。A 服务认一种 Token 格式&#xff0c;B 服务认另一种&#xff0c;用户在一个服务里改了密码&#xff0c;另一个服务还拿着旧身份继续干活。最头疼的是&…

作者头像 李华
网站建设 2026/10/12 2:47:22

鸿蒙Flutter插件适配实战:video_thumbnail视频缩略图从原理到落地

接手这个需求的时候&#xff0c;我心里其实是有点发怵的。公司的知识付费App要出鸿蒙版本&#xff0c;视频课程列表需要显示封面缩略图&#xff0c;这个功能在Android和iOS上早就稳定跑了大半年了——用的是Flutter生态里最常用的video_thumbnail插件&#xff0c;调用方早就写好…

作者头像 李华
网站建设 2026/10/12 2:46:48

SpringBoot+Vue+MySQL校园一卡通系统开发实战与论文答辩全指南

校园一卡通这类系统&#xff0c;算是毕业设计里最经典的那一档题目了。你说它难吧&#xff0c;其实CRUD为主&#xff1b;你说它简单吧&#xff0c;真要把这套前后端分离的项目跑起来、写进论文里、顺利通过答辩&#xff0c;坑一点都不少。我见过太多人选了类似题目&#xff0c;…

作者头像 李华
网站建设 2026/10/12 2:46:28

网络安全入门:学习路线、核心概念与首个抓包实验

写这个系列&#xff0c;是因为我发现很多想入门安全的朋友&#xff0c;卡住的地方真不是资料少&#xff0c;而是资料太乱。我当年最开始的那两个月&#xff0c;基本就是在收藏夹里反复横跳&#xff0c;今天看一篇讲Web漏洞的文章&#xff0c;明天刷一个讲密码学的视频&#xff…

作者头像 李华
网站建设 2026/10/12 2:45:56

Flutter在OpenHarmony上的实战:从零构建书籍列表模块

Flutter 和 OpenHarmony 这两个词放到一起&#xff0c;听起来新潮&#xff0c;但真正做起来才知道坑在哪。我最近用 Flutter 给某图书馆管理系统做移动端&#xff0c;第一个完成的功能模块就是书籍列表。这个模块看着简单&#xff0c;无非是把几十本书排成列表&#xff0c;可背…

作者头像 李华
网站建设 2026/10/12 2:45:33

游戏引擎基础架构:运行时协作协议与内存边界设计

1. 为什么“引擎基础架构”不是一张静态框图&#xff0c;而是一套动态协作协议刚入行那会儿&#xff0c;我被安排参与一个跨平台渲染模块的重构。当时手头只有一份标着“Unity Engine Architecture v2021.3”的PDF——三页A4纸&#xff0c;画着Input、Core、Rendering、Audio、…

作者头像 李华