1. 项目背景与选型思路
1.1 为什么在 OpenHarmony 上做 Flutter 对话框
做 OpenHarmony 北向应用的开发者,多少都经历过选型的纠结。纯 ArkUI 声明式开发确实足够原生,但问题是团队里如果已经有一套 Flutter 代码库,或者团队本身更熟悉 Dart 生态,那面对 OpenHarmony 的设备(尤其是 RK3568 这类开发板)时,最务实的方案其实是复用 Flutter 的跨端能力,把业务层尽可能多地沉淀下来。
提示对话框这个需求,看着不起眼,却是任何 App 都绕不开的基础交互组件。登录确认、删除二次确认、版本更新提示、权限申请说明,全是靠它撑起来的。正因为它足够基础,反而最适合作为 Flutter for OpenHarmony 的首个实战切入点——既能验证 Flutter 在鸿蒙设备上的渲染链路是否顺畅,又能把工程化配置、依赖管理、原生插件桥接这一套流程走通。把这一关过了,后面的页面跳转、数据展示、状态管理全部可以直接照搬这套基建。
1.2 基于标题热词的技术环境确认
写这篇文章前,我顺手翻了下相关热词,有几个值得注意的信号:大量开发者在问 Flutter 安装与配置、Flutter 版本不对导致依赖包下不下来、Flutter 热重载后浏览器没更新,以及 OpenHarmony 的 RK3568 设备树到底怎么选。能看得出,目前真正卡住大家的往往不是业务代码怎么写,而是环境搭建和工具链适配。
选择 RK3568 作为目标设备,是社区里最常见的做法。这块板子性能虽然不算强,但好在 OpenHarmony 的适配比较完善,跑 Flutter 的 Dart VM 和 Skia 渲染管线性能勉强够用。设备树选择的问题其实不复杂,OpenHarmony 官方发行的版本通常会内置多套 dts(设备树文件),对应不同厂商的 RK3568 板卡,你只需要在编译或烧录前确认自己板子的型号是 EVB1、EVB2 还是第三方定制的,再通过 boot 镜像里的 u-boot 参数或 config 文件指定对应的 dts 即可,多数情况下默认配置就能正常开机。真正容易翻车的反而是 Flutter SDK 与 OpenHarmony SDK 的版本对齐问题,这个我会在后面环境章节仔细讲。
2. 环境准备与工程搭建
2.1 开发环境完整配置清单
在开始写任何一行 Dart 代码之前,先把环境踩平。这里以 OpenHarmony 4.0 Release 版本为例,因为我实测这套组合最稳。
必备的组件包括:
- DevEco Studio 4.0(北向集成开发环境,用于编译 OpenHarmony 的 HAP 包)
- Flutter SDK(建议 3.7.x 以上,因为 OpenHarmony 的 Flutter 适配分支基于这个版本起才开始稳定)
- OpenHarmony Flutter 适配仓库(社区维护的 flutter_flutter 分支,或者通过 gitee 上搜索 OpenHarmony SIG 的 flutter 适配工程)
安装过程本身不复杂,但有一个关键点必须提醒:不要用 Flutter 官方主分支,也就是 https://github.com/flutter/flutter 这个仓库,直接用官方 SDK 编译 OpenHarmony 目标时是拿不到 ohos 这个 target platform 的。你需要先配置 Flutter 的 custom fork,也就是 OpenHarmony 社区维护的那条分支,然后在该分支下执行 flutter doctor,才会看到 ohos 设备的支持选项。
具体配置方式,在 Flutter 根目录下通过 git remote -v 确认当前 remote 是否指向 OpenHarmony 仓库,然后执行:
git remote set-url origin https://gitee.com/openharmony-sig/flutter_flutter.git git fetch origin git checkout openharmony-4.0-release之后在 pubspec.yaml 里同样要把 sdk 相关的依赖源切到社区镜像,否则拉取 flutter sdk 自带的包时容易 404。
2.2 创建 Flutter 工程并打通鸿蒙编译链路
环境就绪后,用命令行的方式创建工程,尽量别用 IDE 的图形化向导,因为 Flutter 创建的默认目录结构在 OpenHarmony 工程里需要做一点调整,命令行控制起来更明确:
flutter create --org com.example --project-name dialog_demo ohos_dialog_demo cd ohos_dialog_demo接着需要在工程目录下手动新增一个ohos目录,这是 OpenHarmony 的工程壳。目前 Flutter 官方模板不会自动生成这个目录,需要从社区示例工程里拷贝ohos文件夹,或者通过 DevEco Studio 对 Flutter 工程执行 "New Module" 来生成 HarmonyOS 模块。
这里有个很容易搞错的点:OpenHarmony 的 UIAbility 入口文件里必须显式配置 Flutter 的容器视图。打开ohos/entry/src/main/ets/entryability/EntryAbility.kt,核心代码是:
import ohos.stage.ability.adapter.StageAbility import ohos.stage.ability.adapter.StageAbilityContext import ohos.stage.ability.adapter.IFlutterAbility class EntryAbility : StageAbility(), IFlutterAbility { override fun getFlutterRoute(): String { return "main" } }同时,module.json5里的 abilities 节点下需要把 EntryAbility 注册为 stage 模型,并配置"srcEntry": "entry/src/main/ets/entryability/EntryAbility.kt"。
这些做完后,回到工程根目录执行:
flutter build hap --debug如果没报错,说明 Flutter 到 OpenHarmony 的编译链路已经打通。首次构建会花很长时间,因为要下载 OpenHarmony 的 native 依赖,得有点耐心。
3. 提示对话框核心实现
3.1 用 showDialog 和 AlertDialog 完成基础弹窗
接下来是正文了。Flutter 的对话框体系核心就两个 API:showDialog负责把一个 Dialog 组件压入路由栈,AlertDialog负责定义对话框的视觉与结构。在 OpenHarmony 上,这两个 API 的行为与 Android 上基本一致,因为 Flutter 框架层的代码是跨平台通用的,真正不一样的地方在下层的 Platform Channel 实现。
我先给出一个最精简可用的代码:
Future<void> _showSimpleDialog(BuildContext context) async { await showDialog<void>( context: context, barrierDismissible: true, builder: (BuildContext context) { return AlertDialog( title: const Text('确认操作'), content: const Text('该操作会清理本地缓存,是否继续?'), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(), child: const Text('取消'), ), TextButton( onPressed: () { // 执行实际业务逻辑 Navigator.of(context).pop(true); }, child: const Text('确定'), ), ], ); }, ); }这里有三个细节值得展开说。
barrierDismissible控制点击遮罩层时是否关闭弹窗。对"破坏性操作"(删除、覆盖、退出)建议改成 false,同时配上PopScope拦截返回键,防止用户误触直接关掉。这个习惯在 RK3568 这类带物理按键的开发板上尤其重要,用户很可能随手按返回键。
Navigator.of(context).pop()的返回值是动态类型的,可以传任意对象。也就是说,_showSimpleDialog可以返回一个用户的选择结果,这个特性为后面做页面级状态联动打好了基础。
actions数组里的按钮顺序在 Material 规范下是有视觉层级的:确认按钮放右侧、取消按钮放左侧,但如果你做的是国际化 App,需要注意 RTL(从右到左)地区的习惯,阿拉伯语和希伯来语下按钮顺序会镜像翻转。Flutter 的 Directionality 控件会自动处理,前提是你所有的文字都用了Text而不是硬编码TextDirection。
3.2 定制对话框主题色与样式
Flutter 生态里showLicensePage这种系统级页面是可以全局改主题色的,但AlertDialog的定制需要单独做。整体有两种改法:一种是全局统一,改的是ThemeData.dialogTheme;另一种是局部单个对话框定制。
全局改法是在MaterialApp的theme里增加配置:
MaterialApp( theme: ThemeData( dialogTheme: DialogThemeData( backgroundColor: const Color(0xFFF5F5F5), titleTextStyle: const TextStyle( fontSize: 18, fontWeight: FontWeight.w600, color: Color(0xFF333333), ), contentTextStyle: const TextStyle( fontSize: 14, color: Color(0xFF666666), ), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), ), ), home: const HomePage(), )这种做法适合绝大多数业务场景,特别是当你的 App 有多套皮肤(比如暗色模式)时,只需要额外配置darkTheme下的dialogTheme,对话框就会跟随系统自动切换。
局部定制更灵活,但容易写出代码异味。我的建议是,超过三个地方用到相同样式的对话框时,就必须抽成公共组件,不要每次都写一遍RoundedRectangleBorder。比如包装一个AppConfirmDialog,内部封装标题、内容、按钮文案与回调类型,业务层只需要传入数据,不需要关心视觉效果。
这里要专门提醒一下字体问题。OpenHarmony 默认字体是 HarmonyOS Sans,但 Flutter 渲染时并不会自动读取系统字体,需要你在pubspec.yaml中手动引入字体文件,或者通过ThemeData.fontFamily指定。否则中文文字在某些设备上会回退到系统自带兜底字体,显示效果不稳定,可能出现数字与汉字宽度不协调的情况。我在 RK3568 上实测,回退字体的粗体显示存在明显锯齿,把字体文件导入后清晰度立刻上来了。
3.3 对话框中的输入与表单验证
实际项目里,对话框不止是确认和取消,很多时候还要承担输入任务,比如"输入密码确认身份""填写服务器地址""重命名文件"。这种情况下AlertDialog内部可以直接塞一个TextField,但要注意 IME(软键盘)的避让问题。
在 Flutter 里,TextField会自动处理键盘弹出,但会在对话框被顶起时出现布局溢出。常见的解决方案是确保AlertDialog放在SingleChildScrollView里,并配合contentPadding调整间距。AlertDialog有一个scrollable属性,可以直接设为true,Flutter 会自动在 content 区域启用滚动,这样键盘弹出就不会挤压按钮区域。
输入场景下的表单校验,我建议用Form+TextFormField而不是手动监听TextEditingController,理由是两个:Form自带的validate()方法可以把所有校验一次性触发,用户体验更连贯;错误提示文案的颜色和动画由框架统一管理,不需要自己维护状态。
AlertDialog( title: const Text('请输入服务器地址'), content: Form( key: _formKey, child: TextFormField( controller: _controller, validator: (value) { if (value == null || value.isEmpty) { return '地址不能为空'; } return null; }, ), ), actions: [ TextButton( onPressed: () { if (_formKey.currentState!.validate()) { Navigator.of(context).pop(); } }, child: const Text('提交'), ), ], )需要注意,在showDialog的 builder 里创建Form时,GlobalKey的作用域必须谨慎。如果这个 key 在对话框外部也声明了,而对话框还没弹出时_formKey.currentState就会是 null。我习惯把校验逻辑封装在对话框组件内部,由组件自己持有 key,外部只接收结果,这样职责清晰,也不容易踩空指针。
4. 状态交互与异步回调
4.1 如何把对话框结果传递到业务层
对话框最核心的价值,不是展示,而是"用户点了什么"。所以把用户的决策准确地回传给业务层,是衡量一个对话框组件是否好用的分水岭。
先看最基础的场景:简单确认框。通过Navigator.pop(value)传回 true/false,调用方用 await 接收返回值即可:
final bool? result = await _showSimpleDialog(context); if (result == true) { // 用户点了确定 }看上去很简单,但实际项目里会有两个坑。
第一,showDialog返回的类型是Future<T?>,当用户点击返回键或者点击遮罩层关闭时,返回值是 null,而不是 false。如果你的代码写成if (result == false),那么 null 分支就会被忽略,逻辑就漏处理了。正确写法是明确判断result == true或使用空安全??提供默认值。
第二,在对话框还未关闭前,如果页面已经被销毁(比如用户快速连续按返回键退出了页面),await之后的回调仍然会执行。此时如果你在回调里直接setState更新页面状态,就会触发setState() called after dispose()的异常。处理办法是在异步结果返回后先检查mounted属性:
final bool? result = await _showSimpleDialog(context); if (!mounted) return; if (result == true) { setState(() {}); }这个检查不是可选的,是必须的。我在 OpenHarmony 上做低配设备适配时发现,RK3568 由于内存有限,Activity 重建相对频繁,这类异步后置操作更容易踩到生命周期问题,养成mounted检查的习惯能少踩很多坑。
4.2 异步任务中的对话框处理
除了简单的确认框,还有一类常见场景:点击按钮后,发起网络请求或数据库操作,期间弹出"加载中"对话框,结束后自动关闭。这种对话框不能用AlertDialog,因为它没有交互按钮,应该用Dialog+CircularProgressIndicator组合。
我提供一个可以反复使用的封装思路:
Future<T?> showLoadingDialog<T>(BuildContext context) { return showDialog<T>( context: context, barrierDismissible: false, builder: (context) { return const PopScope( canPop: false, child: Dialog( child: Padding( padding: EdgeInsets.all(24), child: Row( mainAxisSize: MainAxisSize.min, children: [ CircularProgressIndicator(), SizedBox(width: 16), Text('处理中...'), ], ), ), ), ); }, ); }调用时:
showLoadingDialog(context); try { final result = await _apiService.doSomething(); if (!mounted) return; Navigator.of(context).pop(); // 关闭加载框 // 处理结果 } catch (e) { if (!mounted) return; Navigator.of(context).pop(); // 无论成功失败都要关闭 // 展示错误提示 }这里有一个体验上的细节,加载框虽然不能通过遮罩关闭,但如果页面里同时存在其他异步操作(比如同时发起多个请求),每个请求都把自己的 loading 弹窗 push 到路由栈里,关闭时只 pop 一次,就会导致弹窗残留。解决方式是给showDialog传入一个固定的 RouteSettings 名称,关闭时精确匹配,或者在业务层保证同一时间只会发起一个加载型弹窗。
4.3 状态管理选型对对话框交互的影响
如果你在项目里用了状态管理库,对话框和全局状态之间的联动需要特别注意。以最常用的 Provider 为例:
class AppState extends ChangeNotifier { bool _isLoggedIn = false; bool get isLoggedIn => _isLoggedIn; void login() { _isLoggedIn = true; notifyListeners(); } }在对话框内直接读取 Provider 是可行的,因为对话框的 context 依然在MaterialApp的 Provider 作用域下。但更推荐的做法是把对话框的逻辑放到独立的 ViewModel / Controller 中,这样对话框内部不需要关心业务状态的来源。以"退出登录"为例,对话框只负责弹窗和收集用户的确认动作,拿到 true 之后才去调用 LoginController 的方法,而不是在当前页面的 setState 里直接改状态。
针对 OpenHarmony 场景,状态管理还有一个性能层面的考虑。RK3568 的 CPU 是四核 Cortex-A55,主频最高 2.0GHz,渲染复杂界面时压力不小。如果用一个全局大 Store,任何一处状态变化都会触发局部setState,整棵 widget 树都 rebuild,卡顿感会很直观。多级拆分状态、让对话框这种局部组件自己持有局部状态,比统一塞进全局 Store 的体验要好得多。
5. 多设备适配与兼容问题
5.1 RK3568 开发板上的布局避坑
OpenHarmony 的设备形态跨度极大,从手机到平板再到 RK3568 这种开发板,屏幕尺寸和 DPI 差异非常大。对话框最常见的适配问题就是宽度溢出。
Flutter 的AlertDialog默认宽度是 280dp,在竖屏手机上看起来正好,但在平板或开发板接的高分辨率显示器上会显得很小。解决方式是限制最大宽度:
ConstrainedBox( constraints: const BoxConstraints( maxWidth: 380, maxHeight: 500, ), child: AlertDialog(...), )反过来,在小屏低分辨率设备上,对话框内部文字过多时会出现溢出。排查溢出问题的核心工具是 Flutter 的 "Debug" 模式红黄条纹,它会直接标出溢出的边界。不过这个可视化提示仅 debug 模式可见,release 包上不会显示,因此我通常会额外挂一层LayoutBuilder或者用FittedBox对长文本做缩放处理。
另一个 RK3568 上的肉眼可见问题,是对话框弹出动画的掉帧。showDialog默认动画是FadeTransition+ScaleTransition,由AnimationController驱动,在低端 GPU 上弹窗的缩放动画能明显感到卡顿。建议在不追求华丽动效的业务场景下,把对话框动画改成简单的淡入淡出,或者直接设置为空动画Duration.zero,对流畅度改善非常明显。
5.2 Flutter 与 ArkUI 原生对话框的边界
一个现实的问题是,有些项目是混合开发:部分页面用 Flutter 写,但主框架是 OpenHarmony 的 ArkUI。这种情况下,对话框的呈现出现了两个阵营,要么 Flutter 自己实现一套,要么通过 Platform Channel 调用原生 ArkUI 的promptAction.showDialog。
我的建议是,能用 Flutter 自己的对话框就尽量用 Flutter 的。原因有三:
- 视觉一致性:Flutter 绘制的对话框不受原生组件主题影响,在跨页面保持统一外观时优势明显。
- 上下文共享:Flutter 对话框的
context可以直接使用上层 Provider 的依赖,不需要做数据桥接。 - 状态同步:如果你的 Flutter 页面被 dispose 了,对话框也会自动跟随路由栈销毁,不用手动管理生命周期。
只有在需要调用系统级能力(比如 Touch Screen 上的权限申请对话框、系统升级确认框)时才建议走 Platform Channel。OpenHarmony 的@ohos.promptAction是一个常用的原生对话框接口,通过 Flutter MethodChannel 调用时,需要自己在原生侧实现方法:
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) { when (call.method) { "showNativeDialog" -> { val title = call.argument<String>("title") val message = call.argument<String>("message") promptAction.showDialog({ title: title, message: message, buttons: [ { text: "取消", color: 0XFF999999 }, { text: "确定", color: 0XFF0A59F7 } ] }) result.success(true) } else -> result.notImplemented() } }这种混合方案,我一般只在必须用到系统级样式或系统能力时才考虑,日常业务弹窗全部走 Flutter 自绘,性能和一致性都更好。
5.3 深色模式与系统字体缩放
OpenHarmony 系统的深色模式覆盖逻辑与 Android 类似,系统切换时会触发 Flutter 框架的Theme.of(context).brightness变化。但有一个坑:如果你在showDialog的 builder 里直接返回AlertDialog,它默认会使用MaterialApp的theme而不是darkTheme。也就是说,即使系统切到了深色模式,对话框依然是浅色。
这个问题在实际效果上很容易被忽略。在弹出的瞬间,背景变暗了,但对话框内容还是亮色底,一眼就能看出没适配好。解决办法有两种:
第一种是给AlertDialog显式设置backgroundColor和textStyle,让它根据Theme.of(context).brightness动态选择:
final isDark = Theme.of(context).brightness == Brightness.dark; AlertDialog( backgroundColor: isDark ? const Color(0xFF1E1E1E) : Colors.white, titleTextStyle: TextStyle( color: isDark ? Colors.white : Colors.black, ), ... )第二种更优雅,是在MaterialApp上配置darkTheme和themeMode,并确保dialogTheme在两个主题下各有一份。Flutter 框架会自动切换组件样式,而不需要在业务代码里手动判断。
系统字体缩放是另一个容易忽略的点。OpenHarmony 设置里允许用户调整字体大小,当字体缩放比例达到 1.3 倍以上时,对话框里的固定高度组件(比如按钮区域)很容易溢出。建议在自定义对话框组件里,按钮区域的高度使用FittedBox包一下,或者直接使用TextButton默认的高度,不要手动指定SizedBox(height: 48)这类固定值,给文字缩放留够余地。
6. 常见问题与排查技巧实录
6.1 常见问题速查表
把我在实际调试中踩过的坑整理成表,按出现频次排序:
| 问题现象 | 根因 | 解决方案 |
|---|---|---|
| 对话框弹出后背景不暗 | barrierColor被全局主题覆盖 | 在showDialog中显式设置barrierColor: Colors.black54 |
| 点击遮罩层无法关闭 | barrierDismissible为 false | 按业务需要改为 true,或在actions中提供取消按钮 |
| 对话框按钮被键盘顶出屏幕 | AlertDialog未开启滚动 | 设置scrollable: true,或把 content 包在SingleChildScrollView |
showDialog多次调用导致抽屉多层弹窗 | 用户重复点击触发 | 用isDialogShowing标志位或RouteSettings名称管理弹窗状态 |
| 对话框背景透明,文字看不清 | AlertDialog的backgroundColor与文字颜色对比度不够 | 使用预定义色板,确保对比度高于 4.5:1 |
关闭对话框后页面setState崩溃 | 异步回调时页面已销毁 | 在异步回调里加if (!mounted) return |
| 深色模式下对话框仍为白色 | 未配置darkTheme.dialogTheme | 在MaterialApp.darkTheme下同步配置dialogTheme |
| 弹窗动画卡顿 | 低端设备 GPU 渲染压力大 | 设置transitionDuration: Duration.zero或改为FadeTransition |
| 中文文字显示为方块/乱码 | 字体文件未正确加载 | 在pubspec.yaml引入 HarmonyOS Sans 字体并设置fontFamily |
6.2 排查流程与日志定位
遇到对话框相关的问题,我的排查顺序一般是这样:
第一步,先在桌面端或模拟器跑同样的代码,排除是否是 Flutter 层本身的问题。如果 PC 上也复现,那九成是代码逻辑问题,与 OpenHarmony 无关;如果 PC 上正常,再转入真机 Debug。
第二步,查看真机日志。Flutter 的 debug 模式在flutter run控制台会输出 Dart 层的异常栈,这是定位 index 越界、null 安全、setState 崩溃最快的手段。如果看不到日志,大概率是调试连接断开,重新执行flutter attach就行。
第三步,用 Dart 层的断言定位 UI 溢出。在AnalysisOptions里开启enable-experiment相关性检查后,开发阶段 Flutter 会明确打印溢出区域的位置和约束信息,直接把日志贴给 AI 工具或翻 Flutter 源码就能快速定位。
这里分享一个我常用的技巧:在对话框的关键生命周期方法里打日志,比如initState、dispose,以及在Navigator.pop前输出参数。这样一旦弹窗关闭后行为不对,你能清楚知道是 dialog 内部的问题还是外部接收回调的问题:
debugPrint('Dialog dismissed, result: $result');6.3 性能优化的几个实测技巧
低配设备上的对话框体验,优化空间其实挺大的,不需要上 profiler,光靠几个经验值就能收到明显效果。
首先,尽量避免在对话框 builder 内部直接创建重量级组件,比如图片加载、长列表。showDialog的 builder 会在路由构建时执行,如果要展示的图片是网络图,建议先加载到内存再弹窗,否则弹窗会一直卡在加载中的状态。
其次,对话框内如果有TextField,尽量设置autofocus: true而不是让用户点一下输入框才弹出键盘。免去一次点击是小事,但对键盘弹出动画和布局重排的调用可以减少一次,这在低端机上能减少一次明显的卡顿。
最后,如果你的AlertDialog里包含的只是静态文本和按钮,完全可以用showDialog的useRootNavigator: true参数,直接把弹窗挂到根 navigator 上。好处是弹窗可以覆盖到所有页面之上,包括底部的 tab 栏,不会因为当前页面嵌套了Navigator而只显示在局部区域。这在 OpenHarmony 分屏或嵌入场景下特别有用,因为你无法预知宿主页面到底嵌套了多少层 navigator。
7. 项目扩展与工程化沉淀
7.1 把对话框封装成统一组件库
对话框用的多了,直接在每个页面里写showDialog就会产生大量重复代码。我在实际项目里会做一层封装,把常用的对话框类型(确认框、输入框、加载框、列表选择框)统一抽象成静态方法,放进一个DialogHelper.dart文件。
class DialogHelper { static Future<bool?> showConfirm( BuildContext context, { required String title, required String message, String confirmText = '确定', String cancelText = '取消', }) { return showDialog<bool>( context: context, builder: (context) { return AlertDialog( title: Text(title), content: Text(message), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(false), child: Text(cancelText), ), TextButton( onPressed: () => Navigator.of(context).pop(true), child: Text(confirmText), ), ], ); }, ); } static Future<void> showLoading(BuildContext context) { return showDialog<void>( context: context, barrierDismissible: false, builder: (context) => const PopScope( canPop: false, child: Dialog(...), ), ); } }这样做的价值在于,后续如果要统一替换对话框风格或者增加埋点统计、日志上报,只需要改这一个文件,全 App 的对话框行为都会联动生效。可维护性比在每个页面复制粘贴高得多。
7.2 从对话框到完整 Flutter on OpenHarmony 项目结构
对话框做完,意味着你已经跑通了一个 Flutter 页面在 OpenHarmony 上的完整生命周期。这时候可以做两件事:
第一件,把你现有工程的pages目录按业务模块拆分,每个模块对应一个独立的 Feature 包,包含页面、组件、状态管理和接口请求的各自文件。OpenHarmony 的设备资源有限,按需加载比全部打包到入口更实际,Flutter 的 deferred import 机制在这里能派上用场。
第二件,把开发模式切到 release 做一次全量性能验证。用flutter build hap --release打出正式包,通过 hap 安装工具装到 RK3568 上,重点检查首屏渲染时间、页面切换帧率和内存占用。Flutter 的flutter run --profile模式能拿到真机上的帧率曲线,对话框的弹出动画如果掉到 20 帧以下,就说明你的渲染链路里有重量级操作被弹窗触发了,需要回到代码层面排查。
7.3 后续扩展方向
对话框只是起点。做完这一步,你搭起来的 Flutter on OpenHarmony 开发链路已经具备向更多业务模块延伸的条件。
比较顺延的扩展方向有这么几个:
- 列表页 + 下拉刷新 + 加载更多,这是任何带数据的 App 最常用的形态,Flutter 的
ListView和RefreshIndicator在 OpenHarmony 上都有对应实现,实测性能可接受; - 图片加载与缓存,借助
cached_network_image这类成熟插件,但要注意插件的依赖版本需要对齐适配分支的 Flutter SDK; - 真机调试下的热重载优化,掌握
hot reload和hot restart的区别,前者打热补丁、后者重建整棵 widget 树,在 OpenHarmony 上混用可以显著提升调试效率。
我在实际项目中,初期只用 Flutter 写了不到十个核心页面,后面陆续把设置页、扫码页和报表页也迁移了过来,一年的时间里逐步形成了完整的 Flutter on OpenHarmony 开发闭环。回看第一个跑通的对话框功能,它真正的价值不在于弹窗本身,而在于验证了整套技术栈的可行性——跨平台代码复用是真的能做起来的,OpenHarmony 生态也确实是 Flutter 开发者可以认真投入的方向。