最近团队接了一个需求,要在OpenHarmony设备上做一款任务管理应用,其中列表排序和快捷创建选项是两个核心交互。客户端这边选型很直接——Flutter,原因也很简单:团队本身有Flutter技术储备,ArkTS侧的能力边界还在试错,能用一套代码覆盖移动端和OpenHarmony终端,省下的时间和人力不是一点半点。
但"省时间"是理想剧本。实际开发中,Flutter × OpenHarmony 这个组合有一堆官方文档没写透的坑,尤其是排序、创建这类看起来人畜无害的基础功能,一旦涉及跨端通信、原生能力调用和平台差异,复杂度会立刻上来。这篇把我在这个项目里踩过的坑、排查过的链路、最终落地的方案完整记录下来,给要做 Flutter on OpenHarmony 的朋友一个可以照抄的参考。
1. 为什么把排序与创建选项这种基础功能,放到Flutter × OpenHarmony的组合里做
1.1 一个常规需求,落地时有三个"不确定"
先交代背景。产品端的诉求是这样的:任务列表需要支持两种排序方式——手动拖拽排序和按字段自动排序;列表底部需要提供"创建选项"的快捷面板,用户可以快速新建任务、选择模板、甚至调用系统能力(比如从相册选图、从文件管理器选附件)。这些功能放到纯Android或纯iOS上,任何一个有经验的Flutter开发者都能闭着眼做出来。但放到OpenHarmony设备上,事情就变得微妙了。
第一个不确定:Flutter官方对OpenHarmony并没有一等公民的支持。也就是说,你没法直接拿最新版的flutter SDK、跑一个flutter create就完事,你需要依赖社区维护的Flutter引擎分支,这个分支的版本滞后于上游,且部分原生插件(比如摄像头、定位、传感器)根本没有鸿蒙适配版。这意味着很多原本认为是"Flutter自带能力"的东西,在这一层要重新评估。
第二个不确定:OpenHarmony的原生开发语言是ArkTS,它基于TypeScript语法扩展,但又和Android的Kotlin、iOS的Swift完全不是一个技术栈。如果某个功能在Flutter侧实现不了,需要下沉到原生层,整个通信链路就变成了 Flutter <=> MethodChannel/EventChannel <=> ArkTS。通道本身不复杂,复杂的是你不得不在两套异步模型之间做状态同步。
第三个不确定:OpenHarmony设备的形态太杂了。有平板、有收银终端、有工业PDA,屏幕比例、系统导航方式、输入法弹起行为都不一样。排序这种高频交互,对触摸拖拽的流畅度要求很高,而创建选项这种弹层交互,对键盘避让和焦点管理要求很高。这些细节在模拟器上完全看不出来,只能在真机上一台一台调。
1.2 现阶段让Flutter跑在OpenHarmony上的可行路线
项目启动前,我做了技术可行性调研,整理了三条路线:
- 使用社区维护的Flutter引擎分支,配合OpenHarmony SDK进行hap打包。这是目前最主流的方式,Flutter代码本身改动最小,但需要接受引擎版本滞后和部分原生插件不可用的现实。
- 系统侧集成Flutter引擎组件。适合设备厂商在ROM层面预置Flutter运行时,应用侧hap只负责加载。优点是对开发者透明,缺点是需要和厂商深度合作,普通应用团队基本用不上。
- 退而求其次,Flutter只做UI层,业务逻辑全部走原生ArkTS实现。这个方案最稳,但等于放弃跨端复用的核心价值,不到万不得已不推荐。
我们选的是第一条路线。具体来说,工程结构是标准的Flutter项目,但构建产物要针对OpenHarmony平台适配,同时有一层薄薄的ArkTS代码负责承载Flutter引擎、注册MethodChannel和EventChannel。后续所有"排序"和"创建选项"功能,凡是需要调用系统能力的部分,都在这一层ArkTS里实现。
提示:确定路线之后,第一件事就是把Flutter版本、OpenHarmony SDK版本、社区引擎分支版本三者锁死,并写进团队的技术文档。版本不对齐是你后续遇到所有诡异问题的头号来源,没有之一。
2. 功能拆解:排序、创建选项在跨端场景下到底要解决哪些问题
2.1 排序的三种形态和各自技术点
动手写代码之前,我把"排序"拆成了三种形态,它们的实现路径完全不同。
第一种是手动拖拽排序。用户在列表中长按某个任务,拖动到目标位置,松手后列表重新排列。这个交互在Flutter里对应的是ReorderableListView或者ReorderableList。技术点主要集中在拖拽状态管理、列表项动画、以及拖拽结束后的索引换算。看似简单,但的的索引错位问题(onReorder回调里给的newIndex比实际插入位置大1)几乎每次都会坑到人。
第二种是按字段自动排序。比如按优先级排序、按截止时间排序、按创建时间排序。这类排序的关键不在UI,而在数据模型和排序算法的稳定性。尤其要注意的是:当排序字段相同的时候,是否保持原来的相对顺序。Flutter的ListView本身不参与这个逻辑,它只负责按给定的有序列表渲染,所以真正的难点在业务层。我们当时用Dart的List.sort配合自定义Comparator,Comparator里除了排序字段,还要加一个自增序号作为次级排序键,否则调用后端接口后列表顺序会随机跳动。
第三种是跨端持久化排序。用户拖拽完、自动排序完,这个顺序不能只活在内存里。你得把顺序同步到OpenHarmony侧的文件系统或者服务端。这里就有个经典问题:是把整个列表顺序全量上报,还是只上报变动部分?全量上报简单可靠,但数据量大时对OpenHarmony设备的老旧存储不友好;增量上报效率高,但你需要设计一个足够可靠的顺序字段(比如浮点数排序值),处理起来更复杂。
2.2 创建选项不是弹窗,是"系统能力+UI"的组合
再说"创建选项"。刚开始产品经理的描述就是"底部弹出一个面板,里面有几个按钮",听起来就是一个showModalBottomSheet的事。但深入一想,这个面板背后至少藏着三个功能层次:
- 纯Flutter层:按钮列表、图标、标签、最近使用的模板显示、新建任务的表单页。这些由Flutter完全掌控。
- Flutter发起、原生响应层:点击"从文件管理器选择附件"时,需要调用OpenHarmony的FilePicker能力;点击"拍照上传"时,需要调用Camera能力。这些能力的UI一般由系统拉起,Flutter只能等待异步结果。
- 原生事件主动推送层:比如设备插入了外部存储、某个系统资源状态变了,需要主动通知Flutter侧刷新创建面板的可用选项。这就必须用到EventChannel。
所以我在设计这个功能时,把"创建选项"定义为一个能力聚合面板,而不是一个静态弹窗。它的核心复杂度不在UI渲染,而在"Flutter侧如何安全地等待跨端异步结果"以及"原生侧如何把事件稳定地推回Flutter"。
2.3 划分Flutter侧和OpenHarmony侧的边界
基于上面的分析,我定了一个简单的边界原则:UI归Flutter,能力归OpenHarmony,状态走通道。
具体来说,所有视觉呈现、交互动画、手势处理、布局适配都放在Flutter侧。所有系统级能力——文件选择、相机、系统设置页、设备信息获取——都下沉到ArkTS侧。Flutter侧不直接依赖任何系统API,ArkTS侧不直接操作任何业务UI。两者之间只有一条通信契约:MethodChannel负责请求-响应,EventChannel负责事件推送。
这个边界确定的越早,后面开发越轻松。因为团队里Flutter开发者和ArkTS开发者是两个人,如果没有清晰的契约,很容易出现"你写的通道我找不到""我发的数据你解析不了"这类问题。
3. Flutter侧实现:列表状态管理、拖拽排序与创建弹层的完整流程
3.1 状态管理选型:为什么这次用Riverpod
Flutter侧的第一件事是状态管理选型。排序功能意味着列表顺序会频繁变化,创建面板意味着弹层状态和主列表状态需要隔离,再加上跨端通信的异步状态,整个状态模型比普通CRUD要复杂。我们最终选了Riverpod,而不是更常见的Provider或者Bloc。
原因有三:第一,Riverpod天然支持异步状态,MethodChannel的返回值可以直接包成FutureProvider或者AsyncNotifier,省去手动管理loading/error/success三态的模板代码;第二,Riverpod的组件级覆盖能力(ProviderScope的override)让我们在测试时可以轻松mock原生通道返回的数据,跨端联调没打通的时候不至于阻塞UI开发;第三,排序状态和弹层状态用的是两个不同的Provider,弹窗开关不会触发整个列表重建,性能上更稳。
如果你还在用Provider,这个场景也能做,但异步环节会比较繁琐。我不是劝你迁移,但如果项目刚起步,从Riverpod开始可以少走很多弯路。
3.2 ReorderableListView拖拽排序的实测细节
列表主体用的是ReorderableListView.builder。核心代码这样写:
ReorderableListView.builder( itemCount: tasks.length, onReorder: (oldIndex, newIndex) { if (newIndex > oldIndex) { newIndex -= 1; } final task = tasks.removeAt(oldIndex); tasks.insert(newIndex, task); }, itemBuilder: (context, index) { final task = tasks[index]; return TaskCard( key: ValueKey(task.id), task: task, ); }, )这里有个细节必须提醒:onReorder的newIndex在视觉上是"插入到该索引之前",当newIndex > oldIndex时,实际插入位置要减1,否则你会发现拖拽后项目总错一位,还能看到下面浮上来一条"幻影"顺序。这是我每次带新人都会重复一遍的常识,但真到自己手写的时候还是容易忘记。
拖拽时的视觉体验也要处理。ReorderableListView默认会给拖拽项加上上一层阴影和列表项偏移动画,但在OpenHarmony真机上,如果列表项本身比较复杂(比如带缩略图、标签、进度条),拖拽过程中的fps会明显掉到30以下。我的优化方案是:给TaskCard包一层RepaintBoundary,让每个列表项独立渲染,拖拽时Flutter只重绘被拖起的那个item,而不是整个列表。这个改动虽然只加了一个widget,但效果非常明显。
拖拽结束后还有一个更新顺序的问题:本地State里的List顺序变了,但ArkTS侧和远端数据还没同步。我的做法是拖拽动画完全结束(ReorderableListView的onReorder已经触发、新顺序已经在UI上稳定)后,才通过MethodChannel把新顺序全量上报。这样即便上报失败,UI上也能先给用户正确的结果,下次启动再走一致性校验。
3.3 创建选项弹层:BottomSheet、焦点和键盘的配合
创建选项面板我用的是showModalBottomSheet,但配置上有几个坑:
showModalBottomSheet( context: context, isScrollControlled: true, backgroundColor: Colors.transparent, builder: (context) => const CreateOptionPanel(), );isScrollControlled必须设为true,否则弹层高度超过屏幕一半时会被强行压缩,在平板上尤其难看。backgroundColor设透明是为了让面板内部自己画圆角和背景,避免默认的白色矩形看起来像一块没贴好的膜。
弹层内部有一个"新建任务表单",表单里有TextField。这里有个OpenHarmony特有的问题:系统输入法弹起后,底部弹层不会自动上移,键盘会直接盖住输入框。普通Flutter项目里你可以在Scaffold上设置resizeToAvoidBottomInset: true,但BottomSheet不是Scaffold的子节点,这个参数不生效。我的解法是监听MediaQuery.of(context).viewInsets.bottom,当键盘弹起时给弹层内容加一个对应的底部padding:
Padding( padding: EdgeInsets.only( bottom: MediaQuery.of(context).viewInsets.bottom, ), child: form, )这个方案在OpenHarmony上实测可用,但有一个延迟:键盘弹起动画和viewInsets变化的时序不完全同步,偶尔会出现1~2帧的内容跳动。如果你对这块要求很高,建议做一个自定义的弹层动画,用AnimatedPadding跟随键盘动画曲线,而不是直接静态加padding。
创建选项面板里的"最近使用"列表,我用的是简单的横向ListView加SizedBox固定高度。因为项目里最近使用数据量很小,就不上GridView了,省得滚动嵌套冲突。
3.4 排序结果的持久化策略
排序结果不能只活在内存里,我设计了两个层面的持久化。
第一层是本地快速持久化:每次拖拽结束、自动排序执行完毕后,把当前任务id的有序列表序列化成JSON,通过MethodChannel写入OpenHarmony的应用沙箱文件。读取的时候直接读这个文件,秒级恢复到上次状态。
第二层是服务端数据同步:在应用进入后台或者点击"同步"按钮时,把顺序变更上传。为了避免全量上报带来的流量开销,也为了降低OpenHarmony设备存储较差时的写入压力,我做了一个优化:顺序字段用double类型的sortOrder,两个相邻任务的sortOrder之间留出较大的间隔(比如1024的余量)。当用户把任务A拖到任务B和任务C之间时,只需要把A的sortOrder改成B和C的中间值,比如600,然后增量同步A这一条数据就够了。只有当间隔耗尽时,才触发一次整体重编号。
这个方案在理论上是标准做法,实际操作中有个注意点:浮点数的精度问题。当间隔越来越小、排序值的差趋近于浮点精度上限时,中间值可能等于边界值,导致排序失效。所以我加了一个保护逻辑:排序时如果发现两个相邻任务的sortOrder差值小于1.0,就触发本地重编号(把所有任务按顺序重新赋值sortOrder = index * 1024),并把全量数据同步到服务端。
4. 接入OpenHarmony原生能力:EventChannel与PlatformView的关键适配
4.1 通信通道设计:MethodChannel与EventChannel的分工
跨端通信我用了两种通道,分工很明确。
MethodChannel用于"一次请求、一次响应"的场景,比如Flutter侧发起文件选择、读取设备信息、写入排序数据。代码大概长这样:
// Flutter侧 const MethodChannel _channel = MethodChannel('com.example.taskapp/native'); final String result = await _channel.invokeMethod('pickFile');对应的ArkTS侧注册代码:
// ArkTS侧 import { MethodChannel } from '@ohos/flutter_ohos'; const channel = new MethodChannel('com.example.taskapp/native'); channel.setMethodCallHandler((call) => { if (call.method === 'pickFile') { // 调用系统文件选择器,返回路径 return Promise.resolve('/data/storage/xxx'); } return Promise.reject('unsupported method'); });EventChannel用于"原生主动推送到Flutter"的场景,比如外部存储卡插拔、系统壁纸变更、后台任务进度更新。Flutter侧监听:
const EventChannel _eventChannel = EventChannel('com.example.taskapp/events'); _stream = _eventChannel.receiveBroadcastStream().listen((event) { // 处理原生推送的事件 });ArkTS侧由原生主动发送事件:
const eventChannel = new EventChannel('com.example.taskapp/events'); eventChannel.setStreamHandler({ onListen: (args) => { // 开始监听系统事件,有变化时调用 eventSink.success(data) }, onCancel: (args) => { } });有两个坑要提一下。第一个是通道名称必须是完全一样的字符串,且一旦发布上线就不能随意改动,否则会闪退。我们在工程里定义了一个常量文件,Flutter侧和ArkTS侧各自引用,从源头上避免手误。第二个是ArkTS侧Promise的reject原因会被Flutter侧捕获为PlatformException,如果你的原生方法可能抛出业务错误(比如用户取消了文件选择),最好在reject里带一个错误码,Flutter侧统一用错误码判断"用户取消",而不是用错误消息字符串匹配,后者在版本迭代中很容易失效。
4.2 一个完整的创建选项数据流示例
拿"创建任务时选择附件"这个功能举例,完整的数据流是这样的:
- 用户点击创建面板里的"附件"按钮。
- Flutter侧通过MethodChannel发起
pickAttachment调用,异步等待结果。 - ArkTS侧接收调用,拉起OpenHarmony系统的文件选择器。
- 用户选择文件后,ArkTS侧把文件的uri、文件名、大小封装成Map返回。
- Flutter侧拿到Map,更新创建表单的状态,显示已选文件的卡片。
这里有个时序问题必须处理:用户在文件选择器里可能待很久,如果拖拽排序正好在这个间隙触发了数据刷新,可能导致返回结果时列表状态已经变了。我们的做法是在发起通道调用前,给操作加一层"禁止其他状态写入"的信号量(项目里用的就是一个简单的bool标志),通过await等待结果返回后再释放。
这个方法虽然土,但在跨端异步场景里非常好用,比引入复杂的响应式状态框架要直观得多。
4.3 PlatformView嵌入经验:谁负责绘制,谁负责事件
项目里还有一个模块需要嵌入原生的系统设置项预览,这就用到了PlatformView。在OpenHarmony的Flutter适配层,PlatformView的接入方式和Android类似,但有一些鸿蒙特有的限制。
核心经验是:事件响应链要理顺。PlatformView本质上是一个原生View被嵌入Flutter的渲染树里,触摸事件先到原生View,原生View消费不了的部分才透传给Flutter。如果你嵌入的是一个可滚动的原生列表,而外部又是一个Flutter的滚动视图,两个滚动手势会打架。
我的处理方案是:PlatformView内部只负责展示静态内容,所有需要滚动的区域一律不放进PlatformView,而是通过通道把数据传给Flutter侧,用Flutter自己的滚动组件渲染。这样子虽然少了一些"原生优先"的性能优势,但极大地降低了手势冲突的排查成本。
另一个PlatformView的坑是混合渲染时的透明背景问题。OpenHarmony上,PlatformView默认背景是黑色,如果你的Flutter页面背景是浅色,嵌入的View会在布局时闪一下黑块。解决方法是给PlatformView整体设置一个不透明的背景色,并且这个颜色要和Flutter页面背景保持一致,否则视觉上会看到一块明暗不同的补丁。
4.4 通道适配的常见坑
汇总一下通信层我这边的踩坑清单:
- 序列化兼容:MethodChannel传输的数据必须是JSON可序列化的,Flutter侧的Map里如果有
Uint8List,对应ArkTS侧要用Uint8Array接收,不能直接转字符串,否则图片等二进制数据会损坏。 - 线程切换:ArkTS侧MethodCallHandler的执行线程不一定是UI线程,如果你在里面操作了UI组件(比如拉起系统弹窗),要主动切换到UI线程。我们的做法是handler里只做数据准备,真正涉及UI的部分必须显式调用主线程执行器。
- 通道未注册:Flutter侧发起调用时,如果ArkTS侧还没注册对应通道(比如Flutter引擎初始化先于ArkTS插件的onLoad),会抛
MissingPluginException。处理办法是在Flutter侧对关键调用做重试包装,原生的ready事件通过EventChannel通知Flutter,Flutter收到ready后再发起第一批不再允许失败的请求。
5. 真机调试中的性能和兼容性问题:从渲染到内存的一次排查实录
5.1 首次真机跑通后,列表长滑就出问题
功能开发完成后,第一次在OpenHarmony真机上完整跑通,自我感觉良好。结果拿着平板上下滑动列表没几分钟,问题就来了:列表滚到快到底部时,偶发出现整行闪烁、类似撕裂的虚影;继续滑,还有几次直接掉帧到让人不适。
我第一时间怀疑的是Impeller。Flutter新版本默认用Impeller做渲染引擎,它比Skia在iOS上表现好很多,但在OpenHarmony的适配层并不一定成熟。排查的第一步就是做控制变量:把引擎切回Skia验证一次。
在Flutter工程的自定义引擎构建参数里,把渲染引擎改为skia,重新打包上机。跑了半小时,相同的滑动路径,闪烁和撕影确实消失了,但拖拽排序的动画又变得有点黏滞,而且首帧渲染明显变慢。这就很尴尬:Impeller流畅但偶发视觉问题,Skia稳定但拖拽体验退步。
5.2 追踪到PlatformView的混合绘制区域
我没有急着定论切引擎,而是把debugRepaintRainbow打开,给列表区域绘制了重绘热力图。观察发现,所有闪烁和虚影都集中在列表底部某个固定区域,而这个区域正好就是嵌入了一个原生系统设置预览的PlatformView。
到这里基本锁定了:这是PlatformView和Flutter列表混合渲染时的图层合成问题。PlatformView在Flutter的渲染管线里是一个独立纹理,当列表滚动、Flutter侧不断重绘时,PlatformView所在的图层合成时机和Flutter侧不完全同步,于是在OpenHarmony的适配层中产生了短暂的颜色撕裂和残影。
找到根因之后,解决方案就清晰了:规定列表滚动的可视范围内不允许出现PlatformView,把原生预览那个模块挪到列表项的末尾,并且用一个足够高的RepaintBoundary包住它,让PlatformView区域的重绘频率大幅降低。同时给PlatformView的背景色设置成不透明纯色,侧面规避了透明通道混合的问题。
切回Impeller后重新验证,闪烁消失,拖拽动画也保持流畅。整个过程最有价值的教训是:遇到渲染问题先把范围缩到最小,别一上来就全盘换引擎。
5.3 大列表排序时的内存抖动
排序功能还有一个独立的问题:当任务列表超过500条时,拖拽排序的内存曲线呈锯齿状上升,排序结束后回落缓慢,多操作几次甚至触发了OOM。
排查用的工具是DevEco Studio自带的内存分析器,和Flutter侧的flutter run --profile配合看。先看到的是垃圾回收频繁,几乎每次insert/removeAt都会触发一次GC。进一步看,问题出在两个地方:
第一,列表项里的图片资源没有统一缓存,每次列表重排都会重新加载网络图,哪怕这张图之前刚加载过。我引入了一个简单的ImageCache封装,把原始图片的字节数据按key缓存,列表项重建时直接从缓存取,磁盘缓存再交给原生侧策略管理。
第二,列表排序时使用了不可变List的复制操作,这会导致O(n)的内存开销。优化后改为直接在一个List实例上做removeAt和insert,不复制整个列表。配合前面说的RepaintBoundary,只让受影响的两个item重建,内存曲线明显平缓。
如果你也遇到类似场景,我的建议是:先把列表项内容做成纯展示组件(不要在里面发起任何网络请求),排序过程中不触发任何异步加载,全部数据都在排序完成后再统一刷新缩略图。
5.4 Navigator切换页面后状态丢失
创建选项的弹层里包含一个二级表单页,通过Navigator push进去。在OpenHarmony平板上,应用切到后台再切回来,或者系统配置变更(比如分屏、旋转),偶尔会出现Navigator堆栈被重置、表单输入内容全部丢失的情况。
这个问题的根因是Flutter引擎在后台被系统回收重建,页面State虽然尝试恢复,但表单的TextEditingController没有实现Restoration机制。解决办法是给路由和controller加上RestorationId:
MaterialApp( restorationScopeId: 'task_app', ... ) class FormPage extends StatefulWidget { ... } class _FormPageState extends State<FormPage> { final _controller = TextEditingController(); @override void initState() { super.initState(); _controller.text = ''; } }配合RestorationMixin,给每个需要记住状态的widget设置restorationId。实际操作中我只给表单页和排序页做了恢复,其他页面不处理,因为这个机制有额外序列化成本。记得在didChangeAppLifecycleState里监听后台事件,把关键表单内容额外存一份JSON到沙箱,双保险总比纯靠系统恢复机制稳当。
6. 从调试到交付:OpenHarmony XTS认证与打包上机的实战清单
6.1 搞清楚XTS认证为什么重要
我们做的应用最终要预装到某一批OpenHarmony设备上,这就要过设备厂商和系统层面的兼容性认证。很多人不知道,OpenHarmony有一套兼容性测试工具(一般叫XTS测试套件),用来验证应用或设备是否符合OpenHarmony的兼容性要求。
具体到开发者的实际影响:如果你的应用在所有标准测试项上不通过,应用市场或设备厂商那边可能直接把你的hap打回来。尤其是涉及跨端通信和平台能力的应用,XTS测试里的系统接口调用记录会成为重点检查项。排序和创建选项功能本身不复杂,但我们调用了文件选择器、系统设置预览等原生能力,这些接口的调用时机、权限声明、参数合法性都会被审计。
我的建议是开发阶段就持续跑XTS里与安装、启动、权限相关的子集,别等功能全做完了再补。我们当时就是上线前一周集中跑,结果发现权限弹窗的处理时机和规范有出入,改了不少代码。
6.2 打包签名和安装验证
OpenHarmony应用的打包产物是hap文件。打包流程大体是:先用DevEco Studio构建ArkTS侧的hap基座,再把Flutter引擎和Dart产物打进hap里,最后统一签名。签名这块需要申请应用证书和profile文件,流程不复杂但最容易被拖延——证书申请的审核比较慢,建议项目中期就准备好。
签名完之后,安装验证也不能马虎。我用一张自检清单来验收:
- 安装后首次启动,没有报
signature verify error。 - 冷启动、热启动各来一轮,确认Flutter引擎能正常初始化。
- 打开排序列表拖拽测试,确认方法通道调用无
platform channel error。 - 杀掉进程重启,验证排序持久化恢复是否正常。
6.3 我的真机自测清单
最后分享一份我的真机自测清单,适合所有Flutter × OpenHarmony项目,排序和创建选项功能也完全可以套用:
- 横竖屏切换:排序列表的布局变化、拖拽手势的坐标换算是否正确。
- 分屏模式:创建选项的BottomSheet是否被拉伸、键盘避让是否失效。
- 输入法弹出与收起:表单焦点处理、Channel调用是否被输入法打断。
- 后台恢复:排序是否有状态丢失、通道是否需要重新注册。
- 通过EventChannel推送事件后,UI是否立即刷新。
这份清单看起来琐碎,但实际操作中每一条都能对应到一个真实bug。跨端开发最忌讳的就是只在模拟器上跑"快乐路径",OpenHarmony设备形态太多,不上真机根本不知道系统级行为差别有多大。
写到这里,整个Flutter × OpenHarmony跨端实现排序与创建选项功能的链路就完整了:从为什么这么选型,到功能边界划分,到Flutter侧实现细节,到ArkTS通道适配,再到真机排查和认证打包。这个项目做完之后最深的体会是,跨端开发的技术难点往往不是Flutter本身,而是Flutter和原生侧那条"看不见的通道"——你越是忽略它,它越会在关键时刻给你上眼药。好在只要把通信契约、版本锁区、边界划分这些基础工作做扎实,后续的排查成本会比想象中低很多。