ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Flutter适配OpenHarmony实战:紧急联系人模块开发全记录

Flutter适配OpenHarmony实战:紧急联系人模块开发全记录

刚把紧急联系人模块从原型做到真机验收,正好借这个项目聊聊Flutter在OpenHarmony上做智慧养老App的完整落地过程。先说结论:Flutter的跨端能力在OpenHarmony生态里是可行的,但有不少坑藏在细节里,尤其是平台通道、权限模型、后台拨号这些节点,跟Android/iOS的直觉不太一样。如果你也在做类似场景,或者刚把Flutter工程往OpenHarmony设备上迁移,这篇实战记录应该能帮你绕开我踩过的那些弯路。

这篇内容围绕一个具体的功能来展开:面向老年用户的紧急联系人模块。这个功能看起来不外乎“存几个号码 + 一键拨出去”,但真正落到适老场景里,需求会变得更重——要有一键SOS、自动附带定位短信、语音播报、大字体防误触等等。整体跑下来涉及Flutter与OpenHarmony原生能力的深度交互,也是整个项目里跨端特性最典型的样本之一。

适合正在做Flutter应用跨平台迁移、关注OpenHarmony生态的技术同学参考。即便你不做养老方向,紧急联系人的实现方式——权限申请、平台通道封装、快速拨号兜底逻辑——放到智能家居、运动健康、车载场景里也是同一套骨架,读下来不亏。

1. 功能定位与整体设计思路

1.1 紧急联系人功能的核心诉求拆解

养老场景里的“紧急联系人”,跟普通社交App里的“常用联系人”完全是两码事。普通场景点一下头像,弹个名片页,用户自己决定要不要打电话;适老场景里,用户可能是在头晕、摔倒、胸闷的状态下操作手机,视线模糊,手指发抖,这时候界面必须足够简单——一个不能再大的按钮,一个明确的语音提示,一个不会被误触取消的确认流程。

我当时把需求拆成了五条:

  • 一键呼叫:从首页任何位置都能在两步以内触达SOS入口。
  • 多方通知:不只拨打第一位紧急联系人,还要同时给备选联系人发短信,附带上当前GPS定位。
  • 防误触:必须长按或连按触发,有倒计时可取消,有语音播报当前操作状态。
  • 快速响应:从触发到真正拨号,延迟控制在2秒以内,不能有卡顿。
  • 消息兜底:如果拨号失败,至少把求助短信发出去。

前两条是功能需求,后三条基本都是适老体验和异常兜底。实际开发里,后三条才是区分“演示Demo”和“能上架产品”的分水岭。

1.2 为什么选Flutter做OpenHarmony端的实现

一开始团队内部是有争论的。原生开发在OpenHarmony生态里当然最稳,但问题也很现实:第一,团队不可能只维护一个平台;第二,现在智能设备形态太碎了,老人家里可能有OpenHarmony手机、平板、智能音箱,甚至带屏的智能手表,每个都搞一套原生逻辑不现实。Flutter本身就是为多端一致而生的,渲染引擎自绘,不依赖系统控件,一套UI代码在不同设备上的视觉效果差异很小。

更重要的一点是,Flutter的插件机制给了我们操作原生能力的入口。OpenHarmony不是Android,但Flutter的Platform Channel概念在适配层里被完整保留了下来。把拨号、短信、定位这些原生能力封装成插件后,UI层可以完全用Flutter写,平台差异被隔离在插件内部。这意味着如果以后要移植到别的OpenHarmony设备形态,UI不用动,只调插件实现就够了。

选型的时候我也对比了其他跨端方案。有些方案在应用市场生态上成熟,但对OpenHarmony的支持基本要等官方适配;Flutter这边至少已经有可用的社区适配版本,而且社区迭代速度很快。我们的判断是:生态活跃度比短期稳定性更重要,适配层如果有问题,至少有人持续修。

1.3 整体工程结构规划

整个App不是只做紧急联系人一个功能,还有健康数据看板、用药提醒、语音交互、亲情视频等模块。但紧急联系人模块在设计上独立成一个Feature包,这很重要:养老App的生命周期通常很长,紧急联络这样的核心安全功能必须能单独升级、单独测试。

工程上我用了标准的Feature-first目录结构:

  • lib/core/:公共能力,路由、主题、工具类。
  • lib/features/emergency_contact/:紧急联系人模块,内部再分data、domain、presentation三层。
  • ohos/:OpenHarmony原生工程,插件代码、权限声明都在这里。

数据流用的是单向数据流:UI事件 --> 状态管理 --> 仓储层 --> 平台插件。紧急联系人功能涉及的异步操作很多(拨号、发短信、拿定位),单向数据流能避免多个回调交叉修改状态导致的界面错乱。

2. 开发环境与技术底座准备

2.1 Flutter适配OpenHarmony的环境搭建

先说一个很多人容易忽略的点:Flutter官方SDK默认是不认识OpenHarmony的。好在社区有维护一套适配方案,本质上是把Flutter引擎的Linux/GPU抽象层映射到OpenHarmony的图形栈上,同时补齐了Platform Channel的原生侧实现。

环境配置大概这么几步:

  1. 准备OpenHarmony的SDK。我用的某标准系统版本,从SDK Manager里下载ets和ohos-sdk两个组件,注意targetSdkVersion要跟设备的系统版本匹配。
  2. 拉取适配版的Flutter SDK。这里有个小坑:一定要用适配版的SDK,而不是官方原版。具体渠道可以找开源社区发布的分支,会带额外的ohos目录。
  3. 配置环境变量。把适配版Flutter的bin目录加到PATH里,启动flutter doctor会看到多出一个OpenHarmony的检测项。
  4. 准备好IDE的OpenHarmony插件。Flutter侧代码用IDE编辑,OpenHarmony原生侧用官方IDE打开,两边同时开工。

我实机跑的是某厂商的OpenHarmony开发板,API Level 10。首轮构建花了20多分钟,主要时间是下载依赖和编译引擎,后面增量编译会快很多,基本在1分钟到2分钟就能出一个可安装的HAP包。

2.2 工程初始化与依赖治理

工程创建用flutter create之后,要手动加一个ohos目录。适配版工具链提供一个初始化命令,生成OpenHarmony原生工程骨架。目录结构出来后,重点检查三处:

  • ohos工程里的module.json5,这里面声明应用包名和入口Ability。
  • Flutter插件如何注册到OpenHarmony侧。适配版在原生工程里会注册一个FlutterAbility作为入口,跟Android的FlutterActivity对应。
  • 三方依赖是否都支持OpenHarmony。可以跑flutter build hap --debug试试,编译不过的依赖就要考虑替换或降级。

依赖治理是我在这类项目里吃过亏最多的地方。很多在Pub上热门的Flutter库,内部用到了Android的SDK类或iOS的框架,OpenHarmony环境里根本编译不过。紧急联系人模块里用到的Hive本地存储是纯Dart实现,能用;但有些位置服务库内部走了Android定位的Service逻辑,直接替换成自己封装的插件。

实践建议:第一版先跑通核心链路,不要去追花哨的第三方库。把拨号、短信、定位三个原生能力封装成自己的插件,再加路由管理、状态管理、本地存储这三个纯Dart或不依赖原生特性的库,整个紧急联系人模块的依赖面就能收得很干净。

3. 紧急联系人核心功能实现

3.1 联系人数据模型与本地存储方案

老年人紧急联系人名单一般不会太长,3到5个就够用。但有一个细节:老人的紧急联系人不仅包含家人,还可能有社区医生、物业值班室,甚至是楼下的药店。每个联系人需要区分的不是“关系”,而是“优先级”——第一个号码一定是直系亲属,接着是社区服务,再往后是医疗资源。

数据模型设计成这样:

class EmergencyContact { final String id; final String name; final String phone; final String relation; // 家属/社区/医疗 final int priority; // 呼叫顺序 final bool voiceEnabled; // 是否播报该联系人 }

存储方案我最终选了Hive。原因很简单:Hive是纯Dart实现,TypeAdapter可在代码里生成,不依赖反射,也不依赖平台原生能力,OpenHarmony上跑得毫无压力。SharedPreferences在Flutter里是插件实现,适配层如果做得不完整会有不稳定风险,Hive就避开了这个隐患。

非关系型数据用Box就够了。

class EmergencyContactRepository { final Box<EmergencyContact> _box; Future<void> saveContact(EmergencyContact contact) async { await _box.put(contact.id, contact); } Future<List<EmergencyContact>> getOrderedContacts() async { final contacts = _box.values.toList() ..sort((a, b) => a.priority.compareTo(b.priority)); return contacts; } }

注意priority字段的排序发生在读取时,而不是写入时。因为在紧急呼叫场景里,用户可能临时把某个联系人置顶,如果写入时排序,会频繁触发Box的写事务,性能有损耗。读时排序是零成本操作。

3.2 平台通道封装:拨号与发短信

这是整个模块技术含量最高的部分。Flutter侧代码只是发起一个方法调用,真正的拨号动作发生在OpenHarmony原生侧。

Flutter端通过MethodChannel发起调用:

class EmergencyCaller { static const _channel = MethodChannel('sos/caller'); static Future<bool> call(String phone) async { try { final result = await _channel.invokeMethod('dial', {'phone': phone}); return result == true; } on PlatformException catch (e) { return false; } } }

OpenHarmony原生侧,适配方案提供了插件注册的能力,在ets文件里实现call接口的dial方法。拨号本质是拉起系统电话能力,OpenHarmony通过AbilityContext的startAbilityByCall或者StartOptions配合指定Uri来实现。

短信用同样的思路,走sms的发送接口。但与拨号有一个关键区别:发送短信必须携带权限声明,在module.json5里注册:

{ "module": { "requestPermissions": [ { "name": "ohos.permission.CALL_PHONE", "reason": "用于紧急呼叫联系人", "usedScene": { "abilities": ["EntryAbility"], "when": "used" } }, { "name": "ohos.permission.SEND_MESSAGES", "reason": "用于发送包含位置信息的求助短信", "usedScene": { "abilities": ["EntryAbility"], "when": "used" } }, { "name": "ohos.permission.LOCATION", "reason": "用于定位老人当前位置并发送给紧急联系人", "usedScene": { "abilities": ["EntryAbility"], "when": "used" } } ] } }

这里有个OpenHarmony和Android很不一样的地方:OpenHarmony的权限模型虽然也分普通权限和敏感权限,但敏感权限的授权弹窗时机、触发条件跟Android很不相同。有些权限只能在特定时机弹窗,不能在App刚启动时一口气全要。定位权限尤其讲究——需要在用户进入SOS页面、明显要使用位置服务时再请求,否则用户拒绝率会非常高。

3.3 一键SOS呼叫流程设计

一键触发的“一键”,在适老场景里必须有明确的防误触机制。我的实现是“长按3秒触发 + 倒计时10秒可取消”,UI上配震动反馈和语音播报。

具体流程:

  1. 用户长按SOS按钮,3秒内连续震动三次,语音播报“即将呼叫紧急联系人,松手可取消”。
  2. 长按动作完成,进入10秒倒计时,页面全屏变红,顶部大字显示“正在呼叫 + 联系人姓名”。
  3. 依次拨打紧急联系人列表中的号码,第一个接通后停止继续拨打。
  4. 拨号的同时,后台向列表里所有联系人发送短信,内容包含当前位置的链接。
  5. 拨号结束或失败,界面引导用户确认是否挂断。

触发链路的Flutter侧逻辑:

class SosController extends GetxController { Timer? _cancelTimer; Future<void> onLongPressConfirmed() async { _cancelTimer = Timer(const Duration(seconds: 10), () async { final location = await _locationService.getCurrentPosition(); await _caller.dialFirstAvailable(); await _messenger.sendSosSms(location); _cancelTimer?.cancel(); }); } void cancelSos() { _cancelTimer?.cancel(); // 恢复界面状态 } }

实际开发里有一个很隐蔽的坑:dialFirstAvailable()不是一次调用就能完成的。第一个联系人可能也没接电话,需要等待振铃超时后继续拨下一个。这个逻辑不能简单写在Flutter侧的Timers里,因为App可能被电话页面抢占到后台,Flutter侧Timer会被系统挂起。正确姿势是让OpenHarmony原生侧管理拨号队列,原生侧跑一个循环,按优先级依次拨号,只在状态变化时通知Flutter侧更新UI。

这是跨端开发最关键的一个思维转换:凡是需要保证时序可靠性的逻辑,尽量下沉到原生侧;UI层只做展示和反馈。养老场景不能依赖Flutter侧Timer来保证“一定会继续拨下一个号码”。

3.4 定位信息获取与短信格式化

牵涉到定位就有第二个难点:OpenHarmony的定位接口在原生侧是异步的,且首次定位往往需要较长秒级时间。如果用户已经触发SOS才去拿定位,短信就发得太晚了。

所以我在进入SOS页面时就开始预取定位,每5分钟更新一次缓存。触发SOS时,直接交互使用缓存值,如果缓存超过10分钟,顶多再请求一次定位更新,用2秒超时兜底。

短信内容不是干巴巴的坐标数字,老人家属收到短信后可能根本不知道这是哪,所以在短信里拼了一个地图链接:

[求助信息] 您的家人(张三)在 2025年xx月xx日 14:32 发起紧急求助。当前位置获取成功,可点击链接查看 https://map.example.com/position?lat=xx&lng=xx 。请尽快回复联系。

地图链接域名用的是统一服务入口,业务侧只需要传坐标,由服务端把坐标反算成地址描述。把地址转换放在服务端而不是客户端,是因为OpenHarmony设备离线和低内存环境下,地址解析引擎容易内存溢出——这是测试阶段真机压测发现的,所以后来刻意砍掉了客户端本地逆地理编码。

3.5 适老化的UI细节:大字号、强对比、防误触

适老UI最核心的设计原则是“信息极大化、操作极小化”。整个紧急联系人页面不追求信息密度,反而要让页面看起来“空”。

字号我统一用textScaleFactor放大到系统默认的1.3倍,按钮最小高度压到64逻辑像素,比Material默认的48要高,避免老人手指点偏。颜色对比度至少在4.5:1以上,SOS按钮是深红底白字,不能为了好看用浅粉色。

防误触还有一个容易被忽略的点:紧急联系人页面不能支持普通的列表左滑删除或拖拽排序。老人如果无意中长按列表项,触发了编辑模式,很可能就误删了一个号码。我把联系人的增删改全部放到了“设置 - 紧急联系人管理”里,SOS界面里只能查看和拨打,不能编辑。

4. 实操过程与关键代码解析

4.1 完整构建流程与产物说明

整个项目的构建链路由Flutter侧编译产物 + OpenHarmony原生侧HaP打包构成。执行顺序有讲究:

# 1. 构建Flutter的产物(libflutter.so + assets等) flutter build hap --debug # 2. 打开IDE加载ohos目录,执行原生构建 # 3. 得到entry-default-signed.hap签名包

如果直接用IDE构建,IDE会先自动执行Flutter侧的build命令吗?不会。需要在工程配置里指定Flutter的SDK路径和构建产物目录,或者在命令行先手动跑一次Flutter构建,再切换到IDE做原生打包。

我按推荐做法来:命令行统一执行flutter build hap,完成Flutter产物生成和打包整个HAP两个动作,输出在build/ohos目录下。

真机安装用hdc命令:

hdc install build/ohos/entry-default-signed.hap

这里的hdc是OpenHarmony的设备连接工具,类比Android的adb。安装完成后用hdc shell aa start -b 包名 -a EntryAbility启动应用。

4.2 插件开发全流程:从接口定义到原生实现

开发一个紧急联系人调用的插件,完整分五步走:

第一步:定义Flutter侧接口。这里不止是方法名,还要明确入参和回参类型。Platform Channel只支持基础类型,自定义的Contact对象要么拆成Map传,要么用JSON字符串。我选择了JSON字符串,因为后面要加字段,兼容性更好。

第二步:实现Flutter侧发送。

class SosCallerPlugin { static const MethodChannel _channel = MethodChannel('smart_care/sos'); static const EventChannel _statusStream = EventChannel('smart_care/sos_status'); static Future<bool> startSos(SosRequest request) async { final result = await _channel.invokeMethod('startSos', request.toJson()); return result; } }

这一步要加超时保护。invokeMethod默认没有超时时间,如果原生侧逻辑卡死,Flutter侧会一直等。我给它包了一个2秒的Timeout,超时就直接返回false,让UI走短信兜底方案。

第三步:实现OpenHarmony侧注册。适配版方案提供了一个插件注册的入口,在原生侧定义SosPlugin类,实现MethodCallHandler接口,并把它注册到引擎上。

第四步:原生逻辑实现。拨号队列要在这里实现。

// 伪代码示例 class SosCallHandler { private contacts: Array<string>; private callStatus: CallStatus; async handleCall(call: MethodCall, result: MethodResult) { if (call.method === 'startSos') { const request = JSON.parse(call.arguments as string); this.contacts = request.contacts; this.activeSos(); result.success(true); } } private async activeSos() { for (let phone of this.contacts) { const callResult = await this.dialPhone(phone); if (callResult.connected) { this.callStatus = CallStatus.CONNECTED; this.notifyFlutter('connected', phone); break; } } } }

原生侧拨号是同步还是异步?OpenHarmony的电话服务接口本身是异步的,startAbility返回一个Promise,直到用户接通或拒绝才resolve。但原生侧可以用一个队列把这些异步串起来,这是最关键的设计。

第五步:Flutter侧监听状态。

单向的状态推送用EventChannel。原生侧从拨号队列逐一向Flutter推送当前状态:正在拨打XXX、已接通XXX、全部无人接听。Flutter侧收到后更新UI。

_statusStream.receiveBroadcastStream().listen((event) { final Map<String, dynamic> status = event as Map; switch (status['type']) { case 'calling': ... case 'connected': ... case 'no_answer': ... } });

这里有个UI层面的特殊处理:如果原生侧正在全屏拨号,Flutter侧UI可能被系统电话界面完全遮挡,所以状态推送不能只依赖Flutter侧更新——原生侧还要在拨号失败、无人接听时直接弹出系统级别的Toast或通知,保证用户一定能感知到结果。

4.3 紧急联系人列表页面实现

列表页本身其实是标准的Flutter页面,但有几个细节和普通App不同:

列表项不能太长。我用卡片而不是传统ListTile,每张卡片显示联系人姓名、关系、电话、优先级。卡片的点击区域是整个卡面,用于拨号;如果需要修改,需要点击卡片右上角的小图标进入管理页。

Widget buildContactCard(EmergencyContact contact) { return Card( child: InkWell( onTap: () => _caller.dial(contact.phone), child: Padding( padding: EdgeInsets.all(16), child: Row( children: [ Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(contact.name, style: TextStyle(fontSize: 20)), Text(contact.relation, style: TextStyle(fontSize: 14)), Text(contact.phone, style: TextStyle(fontSize: 18)), ], ), ), IconButton( icon: Icon(Icons.edit), onPressed: () => _editContact(contact.id), ), ], ), ), ), ); }

优先级展示上,我用了三个小的圆点图标,第一优先级显示实心红点,第二优先级显示实心黄点,第三个及之后显示灰点。这个设计后来被用户调研反馈非常直观——老人可能不认识“优先级”这三个字,但能理解“第一个红点的人是最先被呼叫的人”。

5. 常见问题与踩坑记录

5.1 高频问题速查表

症状根因解决方案
flutter build hap卡在编译引擎OpenHarmony SDK版本与Flutter适配版不匹配统一SDK版本,用同一个包管理器安装API 10
编译报错缺少ets-loaderIDE的ets插件没启用在IDE的插件市场安装OpenHarmony插件后重启
真机安装成功但启动白屏Flutter产物与HAP内嵌路径不匹配检查libs目录下是否包含libflutter.so和libapp.so,确认flutter build hap成功后再安装
联系人读出来是空的Hive的Box路径没初始化首次启动调用Hive.init,并检查path_provider是否正常返回可写目录
调用拨号返回失败false权限未在module.json5标注检查requestPermissions里是否包含CALL_PHONE,且用户已授权

第一个问题是最容易遇到的。某个版本的OpenHarmony SDK在hap打包时会检查IDE升级状态,如果IDE版本太老,就报一个跟ets-loader有关的错。我一开始以为是Flutter配置问题,折腾了一整天,后来发现是IDE插件缓存太脏,清理后重装插件就好了。

5.2 平台通道的序列化坑

Platform Channel的Map传递机制有一个隐性限制:原生侧的HashMap不是按插入顺序保证遍历顺序的。如果我没在原生侧做处理,直接把一个无序Map传回Flutter侧,那么UI读出来的时候key顺序可能跟插入时不一致。

踩过一次:我传联系人优先级的时候,原生侧返回了一个Map<String, Object>,Flutter侧从Map里按固定key读取倒是没问题,但有一次我用entries.toList()把Map转成列表再取第一个,结果发现取出来的不是第一个联系人,因为Map的遍历顺序变了。

教训:跨平台传递数据时,凡是需要保序的数据一律用数组,不要用Map;Map只能用来传无序的键值对,比如状态枚举和附加信息。

5.3 后台运行限制与保活策略

养老App的紧急联系人功能有个前提:App要能在后台活着,这样老人在锁屏时按实体键触发SOS才有效。OpenHarmony对后台应用有限制,普通应用进后台几分钟后可能被挂起。

我在项目中采用两个策略来保活:

第一是申请长时任务权限。OpenHarmony允许特定的前台服务场景(如接打电话、导航、播放录音)申请长时任务,在module.json5里声明ohos.permission.KEEP_BACKGROUND,并且使用对应的长时任务类型启动服务。

// 原生侧启动长时任务 try { const longTaskId = await abilityFeatureAbility.startBackgroundRunning({ context: this.context, bgMode: 'location', wantAgent: wantAgentInfo, }); } catch (error) { // 用户拒绝授权时降级处理:提示用户手动设置 }

第二是提供可选的锁屏快捷入口。有些设备支持在锁屏页面显示自定义的卡片控件,我把SOS按钮做成了卡片形式,锁屏时直接点击卡片触发求助,就不用担心App被挂起。

但我必须说清楚:跨设备的后台保活方案依赖具体硬件和系统版本,没有一劳永逸的办法。真机上需要把厂商自带的省电策略白名单加进去,否则即使代码层面申请了长时任务,系统级省电策略照样把App掐掉。这块要跟设备厂商的适配文档逐一核对。

6. 性能优化与稳定性加固

6.1 内存占用与启动速度优化

OpenHarmony设备的硬件性能差异很大。我测试用的某款开发板内存只有2G,第一版App启动后内存占用冲到500M,直接被系统杀进程。后来做了一轮优化:Hive Box加了compaction策略,避免频繁写入导致Box体积膨胀;列表页图片全部懒加载,联系人头像只用本地资源,不走网络加载。

启动速度上用了一个笨但有效的方法:把SOS按钮的首页所在的Route作为启动后的默认首页,而不是先展示一个品牌闪屏页再跳转。切掉闪屏动画后,从点击图标到看到SOS按钮的时间缩短了约300毫秒。对年轻人来说300毫秒无所谓,但对一个可能正在摔倒的老人来说,每0.1秒都可能是救命时间。

6.2 容灾与降级方案

做紧急联系人功能必须默认一件事:任何环节都可能失败。定位失败、拨号失败、短信发送失败、App自身崩溃——都需要有降级路径。

我的策略是三层降级:

第一层:定位失败时,短信不附带位置链接,只发文本“紧急求助,请回复电话”。位置丢就丢了,但求助信息必须送达。

第二层:拨号失败且短信发送也失败时,启动系统级通知。OpenHarmony原生侧可以直接发一条需要立即处理的高优先级通知,即使App进程被杀,通知也能正常弹出。

第三层:如果连通知权限都没有,原生侧做一个Activity级别的兜底页面——直接以全屏红色页面覆盖当前应用,上面用最大字号显示“请帮忙拨打120”。这一步留到最后,是保命的设计。

每一层降级不能只是简单判断失败后继续往下走,还要做重试。比如短信发送失败,我设置了两次重试,间隔分别是3秒和10秒;超过第三次还失败,才走第三层兜底。

6.3 多设备联动的扩展思路

紧急联系人不止是手机App的功能。整套方案设计时考虑过和OpenHarmony生态里的其他设备联动:

  • 智能手表检测到老人摔倒,向手机App发起SOS信号。
  • 带屏音箱在收到SOS请求后,语音播报求助信息。
  • 门锁联动:SOS触发后,如果老人在家,智能门锁自动开锁,方便救护人员进入。

这些联动在OpenHarmony里通过分布式软总线的能力实现。当前版本我只做了手机App和手表端的信号同步,通过DataShare把SOS状态共享出去。这个扩展点很值得关注,后续版本会持续完善。

7. 写在最后的经验与建议

整个紧急联系人模块从设计到真机验收,最深的体会不是“Flutter能不能适配OpenHarmony”,而是跨端开发里最危险的部分永远在边界处。UI层、状态层、业务层都是可控的,真正出问题的地方就是Flutter和OpenHarmony原生交互的那条通道——序列化丢序、权限弹窗时机不对、后台保活被系统掐掉,这些都不在Flutter的代码审查范围内,一旦出问题定位成本极高。

建议做类似项目的朋友,第一版收敛范围,就做好三个原生插件:拨号、短信、定位。不要一上来就想把所有系统能力都封装一层,把一个模块做到极致再往下一个模块扩张。

另外,紧急联系人功能一定要做足够的真机老化测试。不能只在模拟器里验证通过就上线,模拟器不会模拟系统省电策略,不会模拟弱网环境,更不会模拟用户操作到一半系统弹权限框的打断场景。我在测试时专门写了一个自动化脚本,连续触发SOS一百次,统计拨号成功率、短信发送延迟、定位超时率。不跑这种压力测试,你根本不知道某个低概率的异常路径会在什么时候突然冒出来。

最后再分享一个小技巧:把整个SOS触发链路做成可回放的日志系统。每条关键路径(用户触发、权限检查、定位缓存、拨号队列、短信发送、降级兜底)都在原生侧打点,写进本地Hive的LogBox。线上出问题时,拉取日志就知道整个链路到底卡在哪一环。这个功能本身开发成本不高,但排查问题时的收益可以节省几十个小时的调试时间。

如果这篇内容能帮你少踩几个坑,那就值了。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进