尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Flutter for OpenHarmony实战:从零构建任务看板的全记录

Flutter for OpenHarmony实战:从零构建任务看板的全记录 刚把一个完整的任务看板模块跑通在 OpenHarmony 真机上Flutter for OpenHarmony 这个组合的实际开发体验和我之前在 Android 上用 Flutter 完全不是一个量级的。如果你正打算在鸿蒙设备上做软件开发助手这类工具型应用或者已经在集成 flutter_flutter 仓库但卡在某个环节这篇实战记录应该能帮你省掉不少弯路。这里先交代一下背景。我做的这个软件开发助手 app核心功能之一是面向研发团队的任务看板卡片分列展示、拖动流转、状态实时同步。技术选型上Dart 负责 UI 和业务逻辑ArkTS 负责鸿蒙侧系统能力两边通过 Platform Channel 通信。整套东西在 OpenHarmony 4.0 设备上跑起来之后我最想先说的是Flutter 在鸿蒙上能跑但别把它当成 Android 的平替适配层的差异和生态的成熟度决定了你必须自己掌握几个关键桥接点而这正是本文想展开的。1. 项目拆解一个看板模块背后的真实需求1.1 为什么非要在 OpenHarmony 上跑 Flutter先说结论如果团队只有前端资源又想同时覆盖 Android、iOS、OpenHarmony 三个平台Flutter 是当前投入产出比最高的方案。但选择 Flutter for OpenHarmony并不意味着所有 Flutter 插件都能直接用这是很多新手第一个栽跟头的地方。OpenHarmony 的设备层底座是 ArkUI 和 HarmonyOS NEXT 的运行时Flutter 引擎通过 OpenHarmony SIG 维护的 flutter_flutter 仓库被移植到这套系统上。听起来就是一套代码多处运行实际开发中你会发现核心 UI 逻辑确实复用但只要涉及系统能力震动、传感器、定位、数据库等flutter pub 仓库里那些默认走的 Android/iOS 插件通道全部失效必须以 MethodChannel 走 ArkTS 侧的接口重新实现。我当时做这个看板模块任务数据本地持久化就是一个坎。默认的 shared_preferences 插件适配版在 OpenHarmony 上并不稳定sqflite 更是完全走不通。最终方案是自己在 ArkTS 侧封装了一层偏好存储接口通过 channel 暴露给 Dart任务数据序列化成 JSON 后落盘。这个路数适用于大多数系统能力算是 flutter_flutter 工程集成的标准姿势。1.2 软件开发助手的最小可用闭环软件开发助手这个 app 的定位其实不是一个大而全的 IDE而是面对日常研发流程的轻量工具。我给它划定了三个模块任务看板、缺陷录入、版本记录。任务看板是第一优先级因为它是整个团队的协作入口卡片从待办 → 进行中 → 已完成的流转是否流畅直接决定了工具的留存率。看板模块的最小闭环拆成四层数据层任务模型标题、描述、优先级、负责人、截止时间、状态、本地存储、通道桥接状态层看板三列的任务列表、筛选条件、排序规则交互层卡片拖拽、点击编辑、下拉刷新、状态切换展示层三栏栅格布局、卡片复用、骨架屏加载这里面最容易翻车的是交互层和状态层的耦合。比如拖拽结束后列表要即时重排但数据持久化是异步的如果 UI 先更新而后端写入失败就会出现界面和真实数据不一致。我最终的策略是乐观更新 失败回滚拖拽完成后先刷新内存中的任务状态同时异步写库写库失败时通过 SnackBar 提示并回滚到之前的状态。这个模式后续成为整个 app 处理所有异步写操作的标准范式。2. 环境搭建与工程创建绕过那些隐藏的坑2.1 工具链选型与版本匹配环境搭建是整个项目最先踩坑的地方而且坑都藏在版本匹配里。我一开始拿官方标准的 Flutter SDK 去跑编译都过不了。后来查阅 OpenHarmony SIG 仓库才发现flutter_flutter 的适配版 SDK、Dart SDK、OpenHarmony SDK 三者是有严格对应关系的。我整理出的可用版本组合如下这是我实测通过的后续版本可能会有更新但思路一样组件版本说明OpenHarmony SDK4.0 Release对应 API 10flutter_flutter SDK3.7.12-ohos基于 Flutter 3.7.x 的鸿蒙适配版Dart SDK2.19.6随 flutter_flutter 附带DevEco Studio4.0 正式版用于编译 HAPhvigor4.xDevEco 内置构建工具设置环境变量时除了常规的 ANDROID_HOME关键是还要配置 OHOS_SDK_HOME指向 DevEco Studio 自带的 OpenHarmony SDK 目录。我当时漏了这一步导致 flutter doctor 一直识别不到鸿蒙工具链白白浪费了半天排查时间。2.2 手动创建项目而不是命令生成这里要提醒一个跟热词如何 as 创建 flutter 项目相关的经验。在 Android Studio 里通常用 flutter create 生成工程但 Flutter for OpenHarmony 的工程结构不太一样。flutter_flutter 仓库提供的模板虽然能跑但实际开发中我更推荐手动创建因为项目根目录需要核验 flutter_module 和 hap 两层结构的并存。具体步骤是这样创建纯 Flutter 模块只写 Dart 层代码用flutter create --templateapp生成基础工程用 DevEco Studio 创建一个空的 OpenHarmony 工程作为宿主 App把 Flutter 模块作为依赖工程引入宿主工程而不是直接在一个工程里混合源码这种分层方式的好处在于Dart 侧代码可以独立单测ArkTS 侧也可以单独出包后期 CI 流水线搭建时能并行构建。代价是每次调试都要先编译 flutter 产物再编 HAP构建速度会比较慢我开发机上全量构建一次大约需要 5 分钟所以一定要用增量编译。提示flutter_flutter 适配版的构建产物不是 Android 的 AAR而是 OpenHarmony 的 HAR 包。所以网上搜flutter aar拿到的集成方案在鸿蒙工程里基本不适用别浪费时间在 Android Gradle 配置上。2.3 原生工程与 Flutter 模块的集成方式在宿主工程里集成 Flutter 模块官方推荐的是使用 DevEco 的 ohos 插件机制。打开宿主工程的 oh-package.json5 文件把 flutter 模块加到 dependencies 里然后在 hvigorfile 中引入 flutter 的构建插件。关键的是 Ability 的生命周期处理。需要在 MainAbility 的 onCreate 里初始化 FlutterEngine// ets/entryability/EntryAbility.ets import { FlutterAbility } from ohos/flutter_ohos; export default class EntryAbility extends FlutterAbility { onWindowStageCreate(windowStage: window.WindowStage): void { windowStage.loadContent(pages/Index, (err) { if (err.code) { console.error(load content failed); return; } }); } }在 Flutter 侧MainActivity 变成了 FlutterAbility 的子类FlutterEngine 默认就绑定到了这个 Ability 上。然后通过windowStage.loadContent把 Flutter 的 UI 挂到窗口上后续 Dart 侧写的 widget 就会渲染出来。这个过程中最容易犯的错是忘记在 module.json5 里配置 FlutterAbility 需要的权限和元数据导致引擎启动后找不到入口直接白屏。3. 任务看板数据层与状态管理3.1 任务模型与看板数据结构任务模型看起来简单但设计得好不好直接决定后面状态管理的复杂度。我最终采用的是一个不可变类加 copyWith 模式的组合这是 Flutter 社区里处理复杂状态最常见也最稳的做法enum TaskStatus { todo, doing, done } enum TaskPriority { low, medium, high } class Task { final String id; final String title; final String description; final TaskStatus status; final TaskPriority priority; final String assignee; final DateTime? dueDate; final DateTime updatedAt; const Task({ required this.id, required this.title, this.description , this.status TaskStatus.todo, this.priority TaskPriority.medium, this.assignee , this.dueDate, required this.updatedAt, }); Task copyWith({TaskStatus? status, String? title, TaskPriority? priority}) { return Task( id: id, title: title ?? this.title, description: description, status: status ?? this.status, priority: priority ?? this.priority, assignee: assignee, dueDate: dueDate, updatedAt: DateTime.now(), ); } MapString, dynamic toJson() { id: id, title: title, description: description, status: status.name, priority: priority.name, assignee: assignee, dueDate: dueDate?.toIso8601String(), updatedAt: updatedAt.toIso8601String(), }; factory Task.fromJson(MapString, dynamic json) { return Task( id: json[id] as String, title: json[title] as String, description: json[description] as String? ?? , status: TaskStatus.values.firstWhere((e) e.name json[status]), priority: TaskPriority.values.firstWhere((e) e.name json[priority]), assignee: json[assignee] as String? ?? , dueDate: json[dueDate] ! null ? DateTime.parse(json[dueDate] as String) : null, updatedAt: DateTime.parse(json[updatedAt] as String), ); } }这里把状态枚举和任务实体分开定义好处是所有任务列表的排序、筛选都基于枚举而不是用字符串判断。一开始我偷懒用字符串存状态后来发现每次筛列表都要写 if (status doing)一旦后端返回的状态命名不统一整个看板就乱了。改成枚举后所有逻辑走 type safe 的匹配维护成本低很多。3.2 本地持久化的三套方案任务数据落盘在 Android 上选择很多但在 OpenHarmony 上我实际调研下来有三条路第一shared_preferences 的 OpenHarmony 适配版插件。能用但只适合存简单配置比如看板主题、列表排序方式。如果存几百条任务记录每次读写全量 JSON性能和稳定性都堪忧。第二ArkTS 侧实现用户首选项Preferences通过 Platform Channel 暴露 putString/getString 给 Dart。这套方案适合中等规模数据我最终采用的就是它。ArkTS 侧代码量不大核心是把字符串通过通道回传// ets/entryability/TaskChannel.ets import { preferences } from kit.ArkData; function getTaskStore(context: Context) { return preferences.getPreferencesSync(context, { name: task_store }); } function putTasks(context: Context, jsonStr: string): void { const store getTaskStore(context); store.putSync(task_list, jsonStr); store.flushSync(); } function getTasks(context: Context): string { const store getTaskStore(context); const value store.getSync(task_list, []) as string; return value; }Dart 侧通过 MethodChannel 调用把 JSON 字符串传入 ArkTS 侧ArkTS 侧返回结果。一套很朴素的 RPC但胜在直接可控。要注意的是MethodChannel 的通信时延比 Android 的 JNI 调用会高一些批量写入时最好合并成一次调用比如整列任务一次序列化传递而不是每条任务单独 put。第三关系型数据库。OpenHarmony 的 RDB 能力也可以走通道封装但开发成本较高适合任务数量上万、需要复杂查询的场景。我这个软件助手第一版的数据量撑死几百条用 RDB 属于过度设计。3.3 组件通信回调、通知与共享状态热词里有flutter 组件通信这确实是看板实现里绕不开的核心问题。我在 Dart 侧同时用了三种通信方式对应不同场景父子组件传值用回调函数。比如看板卡片组件 TaskCard 内部点击编辑按钮时通过onEdit回调把任务 id 抛给父级列表组件父组件再调底层更新逻辑。这是 Dart 里最直观的通信方式没有学习成本。兄弟组件协作用 ChangeNotifier ListenableBuilder。三列看板其实都有更新自己列数据的需要拖动任务跨列时源列要把任务移除目标列要插入新卡片。如果只在各自组件里维护 setState跨列数据同步就非常难写。我抽了一个 TaskBoardViewModel 继承 ChangeNotifier持有三个状态列表和对应的增删改方法所有列组件通过 ListenableBuilder 监听同一个 ViewModel状态一变三列同步刷新。跨模块通信用事件总线。比如任务详情页修改了任务标题列表页需要感知这个变化。如果一层一层回调会把 props 传递弄得很难看我直接订阅了一个全局的TaskUpdatedEvent列表页监听事件后重新拉取数据。这里说一个实际心得Flutter 的组件通信本质上是在父子直接传参和全局共享状态之间找平衡点。我一开始想直接用 Provider 做全部状态管理后来发现 OpenHarmony 适配版的 Provider 性能没问题但 debug 模式下热重载偶尔会丢失状态排查起来很头疼。后来干脆精简了状态管理的层次只在看板这种强交互场景用 ChangeNotifier其他页面的局部状态继续用 setState整体反而更稳。4. UI 渲染与交互实现4.1 看板布局三栏设计背后的交互逻辑看板的经典三栏布局背后其实遵循着一个认知负担递减的原则用户永远只需要做三种判断——我接下来要做什么待办、我正在做什么进行中、我完成了什么已完成。把任务卡片按照这三类分栏展示信息熵最低。布局实现上我用的是一个水平的 PageView 加三列定宽容器。竖屏时每列约占屏幕宽度的 85%用户左右滑动切换列横屏或平板时三列同时展示。这个方案比 Row 三列同时展示好在适配小屏设备手机竖屏下三列同时出现会让卡片宽度被压缩到不可读。每列内部的卡片列表用 ListView.builder不要用 ListView(children: [...])。看板列上的任务可能从 10 条涨到 100 条前者只在视口内实例化可见项后者无论有多少条都会全部构建一次性能差距非常明显。列表项的 key 必须稳定。我直接拿 Task.id 作为 Key保证拖动时 Flutter 的 element 能正确复用不会因为列表顺序变化而重建整个卡片子树。这一点在很多入门的看板教程里被忽略但实际操作中如果 key 不稳定拖拽动画会出现明显的闪烁和抖动。4.2 拖拽任务的实现细节拖拽是看板最有交互感的部分也是 Bug 重灾区。我实现跨列拖动用的是 Draggable DragTarget 的组合这也比 ReorderableListView 更适合跨列场景。核心逻辑分三步第一步源卡片包裹在 Draggable 里设置data为任务的 id拖拽时用feedback参数绘制一个半透明的卡片浮层DraggableString( data: task.id, feedback: Opacity( opacity: 0.85, child: TaskCard(task: task, scale: 1.05), ), childWhenDragging: Opacity( opacity: 0.3, child: TaskCard(task: task), ), child: TaskCard(task: task), );第二步每一列的空白区域和目标列表自身都包上 DragTargetonAcceptWithDetails里拿到任务 id 后调用 ViewModel 的 moveTask 方法DragTargetString( onWillAcceptWithDetails: (details) { // 可以在这里做校验比如任务不能拖到自己的列 final task viewModel.taskById(details.data); return task ! null task.status ! targetStatus; }, onAcceptWithDetails: (details) { viewModel.moveTask(details.data, targetStatus); }, builder: (context, candidateData, rejectedData) { final isActive candidateData.isNotEmpty; return AnimatedContainer( duration: const Duration(milliseconds: 150), decoration: BoxDecoration( color: isActive ? Colors.blue.withOpacity(0.08) : Colors.transparent, borderRadius: BorderRadius.circular(12), ), child: _TaskList(status: targetStatus), ); }, );第三步moveTask 里执行乐观更新。先把任务从当前状态列表移除插入目标状态列表同时更新任务的 status 字段再通知持久层异步写回。这里有一个非常容易踩的坑拖拽结束的落点判定。DragTarget 的 builder 里返回的是整个列的列表区域但任务卡片本身也可能成为 DragTarget 的命中目标导致拖拽到卡片上时触发两次 accept。我的处理方式是给卡片区域加上 IgnorePointer让命中区域只落在列的空白处和列表容器上彻底规避重复触发。4.3 下拉刷新与列表复用热词里有flutter 下拉刷新这个功能看起来基础但我做完之后有几个发现。默认的 RefreshIndicator 在 OpenHarmony 的 Flutter 适配版上下拉动画的时机和原生 HarmonyOS 的手势有点冲突下拉到一半经常被系统的手势识别抢走事件。解决办法是给看板页面套一个手势竞技场GestureArena的路由调整手势识别顺序把自定义的纵向拖动手势优先级提高。刷新逻辑本身也有讲究。下拉触发后我先从本地缓存立即加载数据渲染再异步通过 channel 请求远端增量数据。这样用户感知是秒开网络慢时也能先看到旧内容。这种本地先行 远端异步覆盖的策略比单纯转圈等待体验好很多也是任务看板这种需要快速反馈的界面的常规做法。列表复用方面除了前面说的 key 稳定性我还在卡片尺寸上用 itemExtent 做了固定高度。看板卡片如果内部文字长短不一高度不可控ListView 每个 item 都需要重新测量复用效率会下降。我选择了卡片高度固定为 132文字超长省略号截断。这样 Flutter 在滚动时能直接估算滚动范围GPU 光栅化阶段不需要反复 layout实测滑动流畅度提升了一个档次。4.4 平台视图的嵌入考量热词里还有flutter platformview这个在 OpenHarmony 上是一个微妙的话题。Flutter 的标准 PlatformView 机制在 Android 上是通过虚拟显示或者混合合成实现的在鸿蒙适配版里它被映射成了 Flutter 侧的 UiContent 嵌入 ArkUI 的组件树。我当时在这个看板模块里用到一个场景点击任务卡片里选择负责人时希望弹出鸿蒙原生的联系人选择器。这个选择器如果用 Flutter 自绘要写一大串列表和搜索逻辑直接调 ArkUI 的原生组件则代码量骤减。我实际通过 PlatformView 加载了一个原生的搜索框组件调用流程是Dart 侧通过 MethodChannel 通知 ArkTS 侧创建一个原生联系人页面再把这个页面的 surface 绑定到 Flutter 的 PlatformView 上。跑下来稳定性尚可但有一个限制PlatformView 和 Flutter 普通控件之间不能做复杂的混合变换比如旋转和透视缩放可能在部分机型上出现绘制错位。所以我的建议是能用 Flutter 自绘完成的交互尽量不要用 PlatformView。只有数据源强依赖系统能力、自绘成本极高的场景地图、视频、系统设置项列表才值得引入。开发者工具的看板模块最终几乎全部是 Flutter 自绘PlatformView 只出现在一个场景调用鸿蒙的文件管理器选择附件。5. 引擎机制与异步编程5.1 Flutter 在 OpenHarmony 上的架构适配聊到这里应该把 Flutter 系统架构在鸿蒙上如何分层讲清楚。其实技术储备不足时你会在排查问题时无从下手。标准的 Flutter 架构是三层Framework 层Dart 写的 UI 组件、动画、手势、路由Engine 层C 实现负责渲染Skia/Impeller、Dart 运行时、文本排版Embedder 层对接具体操作系统负责窗口、线程、生命周期、输入事件在 Android 上Embedder 层由 Flutter 官方对接 Android Surface。在 OpenHarmony 上OpenHarmony SIG 用 ArkUI 的组件树实现了自己的 Embedder把 Flutter view 嵌入到 Ability 的窗口里。这套 Embedder 实现的质量直接决定了你遇到的渲染问题。比如我实测发现 ArkUI 侧的自定义转场动画和 Flutter 内部路由动画同时进行时会出现一段时间的掉帧这就是两个引擎的渲染时序没有完全同步导致的。理解了这层结构之后排查问题的思路就清晰了凡是渲染异常、动画掉帧这类问题先看是不是 Embedder 与 ArkUI 的时序冲突凡是 Dart 层逻辑异常就去查 Framework 层代码是否调用了不兼容的 API。这两类问题不能混着排查否则半天找不到根因。5.2 Future 与微任务队列热词里有flutter future 的 then 回调 是放入微任务队列吗这是 Dart 异步编程的一个经典问题也直接影响看板数据加载的正确性。结论是如果 future 已经完成那么调用 then 注册的回调会被放入微任务队列在当前的同步代码执行完成后立即执行如果 future 尚未完成回调会被注册到 future 的完成回调列表里等 future 完成后再进入微任务队列执行。这个机制和看板有什么关系我踩过的一个实际坑是加载任务列表时我先用 async 方法读取通道数据然后又立刻遍历任务列表做某种状态校验。由于 Dart 的单线程模型和微任务调度await后的代码并不一定在同一个同步帧内执行。如果列表数据还没加载完成就继续后续逻辑就会出现空列表校验这种隐性 Bug。解决方法是把数据从 Platform Channel 返回后统一走一个异步门控FutureListTask loadTasks() async { final jsonStr await platform.invokeMethod(loadTasks); final Listdynamic jsonList jsonDecode(jsonStr); return jsonList.map((e) Task.fromJson(e)).toList(); }然后所有调用方都await loadTasks()不要用loadTasks().then()同时又访问后续共享变量。微任务队列是一个强大的调度机制但在跨通道异步场景下最简单的模型反而是同步等待结果避免复杂的竞态问题。5.3 渲染管线选择Impeller 还是 Skia热词里出现的flutter impeller在 OpenHarmony 场景下尤其值得多说一句。Impeller 是 Flutter 团队为了替代 Skia 的着色器编译卡顿问题而设计的新渲染引擎它底层通过 Vulkan 或 Metal 后端实现预编译渲染管线。在 Android 上Impeller 已经逐步成为默认选项。但 OpenHarmony 适配版的 Flutter我实测下来仍然以 Skia 为主。原因是 Impeller 对 GPU 后端有强依赖而 OpenHarmony 设备的 GPU 驱动适配并不统一部分国产芯片的 Vulkan 驱动支持不完整强行启用 Impeller 反而会导致应用无法启动或渲染花屏。如果你机器上调试时看到 Skia 的后备绘制路径fallback频繁触发说明当前设备的 GPU 能力不足以支撑某些高级渲染效果。这时优先降低 Flutter 层的光栅复杂度减少过度绘制、避免大面积的半透明叠加、合理使用 RepaintBoundary 隔离重绘区域。看板卡片列表就非常适合用 RepaintBoundary 隔离因为拖动某个卡片时不至于让整个列表重新光栅化。6. 常见问题与排查技巧实录6.1 引擎启动闪退unhandled exception热词里那串 e/flutter (31173): [error:flutter/runtime/dart_vm_initializer.cc(41)] unhandled 几乎每个 OpenHarmony 的 Flutter 开发者都见过。大多数情况下这不是 Flutter 代码的问题而是 Dart 层抛出了一个未被捕获的异常通常来自 MissingPluginException。最典型的是某个插件调用了invokeMethod但这个 MethodChannel 在 ArkTS 侧并没有对应的实现。比如 Flutter 默认的 clipboard 插件在 Android 和 iOS 有系统实现但在 OpenHarmony 适配版里就可能会缺调用时直接抛 MissingPluginException导致整个引擎主 Isolate 崩溃。排查手法是先用 adb/HDC 抓取完整的 stacktrace关注异常抛出的 channel 名称。然后去 ArkTS 侧注册对应的 handlerReact Native 里叫 NativeModuleFlutter 里就是setMethodCallHandler。如果没有现成实现就自己在 ArkTS 侧写一个同名 handler不要轻易屏蔽异常否则看板功能只是看起来能用一旦真正的异常被吞掉后续调试会非常痛苦。6.2 Gradle 插件冲突热词里 you are applying flutters main gradle plugin imperatively using the apply script method 这条报错常见于把 Android 工程里的 flutter_plugin 配置误迁移到 OpenHarmony 工程里。OpenHarmony 工程的构建工具链是 hvigor不是 Gradle所以任何在 settings.gradle、build.gradle 里 apply Flutter Gradle 插件的做法都是不适用的。这个报错还经常出现在用 Android Studio 打开 OpenHarmony 工程时IDE 自动识别 Gradle 结构导致误配置。我的解决方式是OpenHarmony 宿主工程中只保留 hvigor 配置不要混入任何 Android Gradle 文件。flutter 模块作为独立工程通过 oh-package.json5 依赖进入构建流程这样两者各司其职互不干扰。6.3 XTS 认证与 HDI 调试热词里有 openharmony xts 认证和 openharmony hdi这两个跟应用开发者的关系要分清楚。XTSX Test Suite是 OpenHarmony 兼容性测试套件它确保应用和系统调用符合官方规范。如果你的应用要上架到 OpenHarmony 的应用市场必须通过 XTS 认证测试否则会在签名校验或行为检测阶段被拦下来。在开发调试阶段我建议先关掉签名校验使用 DevEco 自动生成的调试证书签名这样可以只关注功能问题。但提交 XTS 之前所有动态权限申请必须走系统 DashBoard 的权限模板不能绕过弹窗直接静默授权否则测试必然失败。HDI 则是硬件设备接口层面向驱动开发者和设备适配厂商普通应用开发者一般用不到。但有个间接场景如果你的应用依赖某些特定硬件能力比如扫码、NFC而设备驱动不符合 HDI 规范上层 API 会直接报错。调试这类问题时不要只看 Flutter 侧日志要先确认底层驱动是否正常挂载。我以前排查一个 NFC 读取问题最后发现是设备厂商的 HDI 实现返回了错误的数据类型跟应用代码完全无关。6.4 平台通道参数传递类型不匹配MethodChannel 在跨语言传递参数时Dart 侧和 ArkTS 侧的参数类型映射如果不匹配会直接抛异常。常见的坑是Dart 侧传 null 给 ArkTS但 ArkTS 侧参数声明为非空类型或者 Dart 侧传 intArkTS 侧收到 number 后拿来当 string 处理。我最后的做法是在 ArkTS 侧为每个 channel 方法做参数二次校验先判断类型再执行具体逻辑if (typeof param.id string param.id.length 0) { // 执行业务逻辑 } else { Logger.error(invalid param: id must be non-empty string); return { code: -1, message: invalid id }; }这算是用防御式编程来规避通道通信的天然盲区。加一层校验后通道异常从崩溃级降级为错误码级看板运行过程中的稳定性提升非常明显。一点额外的心得实际做下来我的整体感受是Flutter for OpenHarmony 的看板项目难度不在 UI 本身而是在于你得像一个系统工程师一样同时理解 Flutter 的渲染架构和 OpenHarmony 的组件生命周期。很多人劝退是因为拿它当 Android Flutter 来用然后被各种插件缺失、通道异常打懵。老实说如果只看框架成熟度Flutter for OpenHarmony 的坑确实比 Android 多一些但它在多端复用的价值面前这些代价是可以接受的。我的建议是在项目启动的第一周就先花时间把 Platform Channel 的调试脚手架搭好——Dart 侧写统一的通道封装ArkTS 侧写日志打印和参数校验。基础打好之后任务看板这种强交互应用写起来和普通 Flutter 开发已经没有什么两样了。
返回列表