Flutter在OpenHarmony上实现WiFi详情页:双端通信与权限合规实战

发布时间:2026/10/3 9:36:50
Flutter在OpenHarmony上实现WiFi详情页:双端通信与权限合规实战 做移动数据使用监管助手这类 App 的时候WiFi 详情页是那种“看起来平平无奇、做起来到处是坑”的模块。产品经理在需求里可能只写一句“显示当前 WiFi 信息”但真正落到 Flutter for OpenHarmony 的双端开发里你需要处理的是一整套数据采集、双端通信、权限合规、UI 刷新和边界情况处理的问题。我这次在 OpenHarmony 设备上用 Flutter 完整落地了一个移动数据监管助手的 WiFi 详情页覆盖当前连接的 SSID/BSSID、信号强度与频段、实时上下行速率、本次会话流量、WiFi 与移动数据用量的对照以及历史 7 天流量趋势。整个过程中Flutter 的 MethodChannel 和 EventChannel 在 OpenHarmony 上的行为、系统接口的差异、XTS 兼容性要求都实打实验证了一遍。这篇文章把完整实现路径和踩坑排查链路写出来适合正在用 Flutter 开发 OpenHarmony 系统信息类、网络监控类 App 的开发者参考。1. 项目立项移动数据监管助手为什么必须做 WiFi 详情页1.1 监管类 App 的核心场景拆解移动数据使用监管助手的本质是帮助用户回答一个问题我的流量到底去哪了。这里的“流量”要拆成两条线看——移动数据蜂窝网络对应的是资费成本用户最在乎“本月还剩多少、哪个应用在偷跑”WiFi 对应的是使用体验用户更在乎“当前连接质量怎么样、在这个 WiFi 下刷掉了多少量”。两条线之间不是割裂的很多用户会发现“我明明开着 WiFi为什么还在扣移动数据”这正是监管助手最典型的痛点场景。所以 WiFi 详情页不是独立的网络信息展示页它要承担两个职责一是把当前 WiFi 连接状态讲清楚二是把 WiFi 维度的用量数据纳入整个统计闭环。在这个定位下WiFi 详情页需要回答的具体问题就清晰了当前连的是哪个热点是不是自家的路由器有没有被“蹭网”信号为什么差现在落在 2.4GHz 还是 5GHz 频段当前实际上下行速度大致是多少和宽带套餐是否匹配这次连接 WiFi 期间累计收发多少流量最近一段时间的 WiFi 用量趋势哪天异常偏高。这五个问题直接推导出页面需要采集的数据字段也推导出后续双端通信的方案。凡是把 WiFi 详情页做成了“只显示一个 SSID 和信号格”的基本都没想清楚它在监管场景里的真实价值。1.2 为什么选 Flutter 而不直接写 ArkTS/Java这个项目在立项时就有明确的跨端诉求同一个 App 需要同时覆盖 Android 设备和 OpenHarmony 设备。团队里已有的 Flutter 技术栈占了相当比重如果 Android 端用 Kotlin 写一套、OpenHarmony 端用 ArkTS 再写一套两套 UI 代码的维护成本在监管类 App 这种信息密度高的页面上会非常可观。Flutter for OpenHarmony 在这两年已经具备工程化落地的条件OpenHarmony 基金会侧维护了适配分支配合 DevEco Studio 可以从同一份 Dart 代码构建出可在 OpenHarmony 设备上安装的 hap 包这是选它最核心的理由。当然这不是说 Flutter 在 OpenHarmony 上完全没有代价。最大的代价是生态差异Flutter 插件的原生部分需要针对 OpenHarmony 的 API 重新实现社区里现成的网络插件基本都指望不上。凡是涉及系统能力获取的功能都要自己补一层 OpenHarmony 插件。所以我的建议是如果只做 OpenHarmony 单端、团队又够人盯 ArkTS那直接用 ArkTS 开发更稳但只要你有多端复用的诉求Flutter 的收益立刻大于成本。我们最终的技术栈是 Flutter 做 UIOpenHarmony 系统 API 靠自有插件桥接本地数据存储用纯 Dart 方案尽量不引入带原生依赖的第三方包。1.3 模块架构与 WiFi 详情的数据流整个 App 的模块我切成四块总览页负责本月移动数据与水流量总览WiFi 详情页负责连接质量与 WiFi 用量应用排行页负责分应用流量后台服务负责超限提醒。页面之间的数据流动是单向的OpenHarmony 插件层定时/事件驱动地获取系统数据通过 MethodChannel 和 EventChannel 把结构化数据送回 Flutter 层Flutter 层统一转换成不可变的 Model 对象UI 层根据状态刷新页面历史统计数据落到本地数据库供趋势图取用。这样的好处是 WiFi 详情页本身不直接碰系统能力只依赖一个定义好的数据仓库接口。后续如果要换系统版本、换接口只改插件层即可页面层不用动。这个架构在后来的 XTS 合规调整里帮了大忙这个后面细说。2. WiFi 数据采集的两条通道MethodChannel 与 EventChannel 的职责切分2.1 先列清楚要采集哪些数据动手写代码之前务必先把数据清单列完整。我在第一版里就吃了“想到什么采什么”的亏导致页面做了一半发现还缺网关信息又回去改插件。最终清单如下数据字段系统侧来源页面用途SSIDWiFi 连接信息显示当前热点名称BSSIDWiFi 连接信息识别是否为已保存热点RSSI 信号强度WiFi 连接信息信号格、弱网提示信号等级WiFi 连接信息简化展示 1-4 格频段WiFi 连接信息2.4GHz/5GHz 标识链路速率WiFi 连接信息理论速率显示IP/网关/DNSIP 信息接口网络故障排查辅助连接状态连接事件断开/重连状态展示本次会话 rx/tx网络统计接口会话流量卡片累计 rx/tx网络统计接口当日/本周统计历史 7 天量本地数据库聚合趋势柱状图OpenHarmony 上 WiFi 连接信息主要通过ohos.wifiManager模块的getLinkedInfo()获取IP 相关信息走getIpInfo()。这里必须提醒一句不同 API Level 的字段命名有差异比如有些版本返回signalLevel有些版本只有rssi需要客户端自行换算不能假设一次写完就永不过时。2.2 Dart 侧 MethodChannel 与 EventChannel 的划分逻辑我在设计通道时做了一个明确切分MethodChannel 管“一次性查询”EventChannel 管“持续事件流”。这是两个完全不同的语义硬塞到一起后面一定出问题。一次性查询包括获取当前 WiFi 信息、获取会话流量、获取历史统计数据。这类操作调用频率低、请求和响应是严格一对一的用 MethodChannel 最直观。而连接状态变化、信号强度突变这类事件天然是异步推送用 EventChannel 能让 Flutter 侧以 Stream 的方式消费语义上跟系统回调一一对应。Dart 侧封装大致是这样的结构class WifiInfoRepository { static const _methodChannel MethodChannel(flutter_open_harmony/wifi_info); static const _eventChannel EventChannel(flutter_open_harmony/wifi_event); FutureWifiDetail fetchCurrent() async { final raw await _methodChannel.invokeMapMethodString, dynamic( getCurrentWifiInfo, ); if (raw null) return WifiDetail.empty; return WifiDetail.fromMap(raw); } StreamWifiStateSnapshot stateStream() { return _eventChannel .receiveBroadcastStream() .map((event) WifiStateSnapshot.fromMap(event as Map)); } }这里有两个细节值得注意。第一invokeMapMethodString, dynamic的泛型一定要写清楚如果 OpenHarmony 侧某个字段返回了null泛型不匹配会导致整个调用抛MissingPluginException之外的诡异异常。第二MethodChannel 返回的 Map 建议立刻转换成强类型 Model不要在页面里到处map[ssid]散弹式取值字段一旦拼错编译期不会报错运行期全是硬伤。2.3 OpenHarmony 侧插件实现与权限声明OpenHarmony 侧的插件实现我按 Flutter for OpenHarmony SDK 的插件规范来写。整体思路是实现插件入口在引擎附加时注册 MethodChannel 和 EventChannel然后在回调里调用系统 API。伪结构大概是// 示例结构具体类名与包签名以你接入的 Flutter for OpenHarmony SDK 为准 export class WifiInfoPlugin { onAttach(engine) { this.registerMethodChannel(engine); this.registerEventChannel(engine); } getCurrentWifiInfo(call) { // 调用 ohos.wifiManager.getLinkedInfo() // 组装成 Map 返回 } }真正容易翻车的是权限声明。WiFi 信息采集涉及ohos.permission.GET_WIFI_INFO网络状态涉及ohos.permission.GET_NETWORK_INFO如果要访问网络还要声明ohos.permission.INTERNET。这些权限必须在module.json5中声明部分权限还需要在运行时通过动态授权弹窗获取。千万不要图省事把不用的权限一起声明了后面 XTS 合规检查会非常被动。第一个版本我只在配置里声明了GET_WIFI_INFO结果真机上getLinkedInfo()直接报错错误码暗示权限不足。排查了很久才发现GET_NETWORK_INFO也必须要带上因为连接信息接口在部分系统版本上依赖网络信息权限。建议你拿到一台真实设备后第一时间把插件里每个系统接口对应的权限试一遍形成自己的权限对照表。3. WiFi 详情页的界面实现、图表选型与刷新策略3.1 页面结构实时状态区、会话统计区、历史趋势区WiFi 详情页我按信息层级切成三个区域用户从上往下浏览正好是“先看当前状态再看本次用量再看历史趋势”的递进关系。顶部实时状态区是一张卡片左侧显示 SSID 和 BSSID右侧用自绘的信号格展示当前强度下面一行小字标注频段、链路速率和 IP 信息。信号格我用CustomPaint画了 4 格柱状条根据 RSSI 值映射到 0-4 档比直接贴一张静态图片可维护性高得多信号强度变化时只要重绘即可。中部会话统计区放两张卡片WiFi 本次连接上行流量、下行流量附带实时速率。实时速率不是系统直接给的需要自己算——读取某个时间点的累计字节数等 2 秒再读一次差值除以间隔就是速率final deltaBytes (currentRx - lastRx) (currentTx - lastTx); final speedKbps deltaBytes * 8 / 1000 / intervalSeconds;底部历史趋势区用 7 天柱状图展示 WiFi 使用量。我把它和移动数据使用量做了双色对比这样用户能一眼看出“这两天是不是 WiFi 用量异常高”。柱状图本身不需要花哨清楚最重要。3.2 图表方案对比纯 Dart 图表库 vs 自绘图表选型上我做了两轮对比。第一轮想直接用fl_chart它功能全、社区活跃在 Android 上表现没问题。但在 OpenHarmony 上要格外谨慎这类图表库如果带了平台相关依赖插件适配 OpenHarmony 之前大概率跑不起来。我检查了它的依赖树确认它主要是 Canvas 绘制风险较低但斟酌之后还是放弃了原因是这个场景的柱状图实在太简单不值得引入一个大依赖去赌兼容性。最终我用最简单的办法实现7 根竖柱用Row排开每根柱高度按百分比归一化计算final barHeight maxBarHeight * (value / maxValue);这段代码不到 30 行零依赖渲染引擎底层走的是 Flutter Canvas跨端都稳定。这里想说的是一个原则在 OpenHarmony 这种生态还没完全成熟的平台上能自绘就自绘能少依赖就少依赖越是基础功能越不要给第三方包留机会。3.3 刷新节奏管理前台定时轮询 后台暂停WiFi 详情页最忌讳的是“无脑定时刷新”。如果每 500 毫秒查一次全量数据插件层要连续走系统接口页面还要频繁 rebuild在低端设备上很快就卡顿。我的节奏是这样连接状态和信号强度靠 EventChannel 事件驱动不主动轮询会话流量和速率用Timer.periodic每 2 秒拉一次历史趋势数据每次进入页面加载一次离开页面不刷新。定时器还需要配合应用生命周期管理。页面切到后台时停止定时器回到前台再恢复否则后台空转几个小时白白耗电。实现上让页面State混入WidgetsBindingObserveroverride void didChangeAppLifecycleState(AppLifecycleState state) { if (state AppLifecycleState.resumed) { _startTimer(); } else { _stopTimer(); } }事件流用StreamBuilder消费页面退出时在dispose里一定要取消监听和销毁定时器。这个“取消订阅”的细节在热重启场景下尤其重要后面专门讲。4. 实测阶段最磨人的四个坑XTS 合规、数值单位、断网回调与热重启4.1 XTS 兼容性认证对权限申请的约束先说结论XTS 认证不是“可选项”它是 OpenHarmony 设备/系统发行方做兼容性验证时跑的那套测试。你开发的 App 虽然没有直接参加 XTS 测试但你的目标设备如果通过了 XTS意味着设备对权限管控、API 调用的行为更规范更严格违规调用会被直接拦下。我遇到的实际问题是插件侧某个版本为了调试方便临时加了日志输出到外部存储的代码顺手在配置里声明了一个存储权限。结果在一台经过严格兼容性测试的设备上系统在安装时直接把这个 App 的非必要权限项列为高风险某些接口的返回开始变得不稳定。排查两天后把多余权限删掉、重新构建 hap 包立刻恢复正常。这个教训翻译成可执行操作就是三条权限声明遵循最小化原则每个权限要在代码里能找到对应的实际调用点发版前用配置最严格的设备做一遍全功能回归。XTS 环境下的行为偏差往往比开发机上更隐蔽别只在开发机测完就自信发版。4.2 流量数值单位不一致引发的显示事故这是我在开发中真正踩过的“数据格式对齐”大坑。有段时间详情页显示的 WiFi 会话流量总是比实际用量大几百倍一度怀疑是系统接口读错了后来才发现是我在单位换算上双重除以了 1024。排查过程是这样的插件层从系统拿到的是一个无符号长整型的字节数为了让日志直观我在 ArkTS 侧先把它转成了 MB 字符串输出。Dart 侧收到后模型代码里又做了一次/ 1024 / 1024等于把已经是 MB 的值再当成字节转了一次。日志里插件层打印的是12.6 MBDart 层打印的却是12902 MB两边一对齐立刻定位到问题。这类单位问题在流量统计场景特别容易犯因为系统各个接口返回的单位并不统一有的返回字节有的返回千字节甚至同一个模块不同 API Level 都在变。我的解决方案是插件层统一返回字节数字段名里明确带Bytes后缀Dart 侧只在 UI 展示的那一刻做一次单位换算。全链路只允许一个单位标准杜绝“到哪一步再转一次”的模糊操作。4.3 WiFi 断开与弱信号下的异常防护WiFi 是出了名不稳定的连接。实测中我发现断网瞬间去调用getLinkedInfo()部分系统版本会直接抛异常错误码各不相同信号极弱时返回的字段会出现大量null。如果代码不做防护页面会频繁闪错误页或者直接白屏。我最终的处理框架是三层兜底所有系统接口调用包在try/catch里异常时返回上一次缓存的数据对返回 Map 里可能为null的字段设置默认值空数据也渲染“未连接”占位状态断网或信号异常时不弹 Toast 轰炸用户只在状态区静默显示“信号弱”或“已断开”标志。缓存上一次数据这个决策很关键。用户拿着手机在家里走动信号从满格掉到一格再恢复过程可能只有几秒如果每个异常都清空页面视觉上就是白屏闪烁。保留旧数据、只更新状态标志体验平滑很多。4.4 EventChannel 在热重启后的重复订阅问题Flutter 开发中最常用的热重启Hot Restart在 OpenHarmony 插件上是把双刃剑。热重启会重新附加引擎、重新走一遍插件注册流程但旧的事件流可能没有完全释放。结果就是页面每次热重启后订阅 EventChannel旧订阅还在新订阅又加上同一个连接状态事件被回调两次、三次甚至更多。排查时我在事件回调里加了计数器发现同样的wifiConnectionChange事件在页面停留 10 分钟后就出现过 4 次重复回调。解决办法有两层Dart 侧在dispose里必须cancel()订阅插件侧不要每次onAttach都新建广播流而是做一个单例通道重复附加时先移除旧监听再注册新监听。这里给一个测试建议每改一次插件代码都做“热重启 → 切走页面 → 切回 → 观察回调次数”的固定操作。很多类似问题只在热重启后暴露冷启动反而一切正常。5. 数据边界与后续扩展监管类 App 的诚实设计5.1 应用层能拿到的流量统计到底有多完整做监管类 App 必须面对一个诚实的问题应用层永远拿不到运营商侧的完整详单。你能统计到的数据是系统开放给你这个 App 的数据主要包括系统网络统计接口给出的接口级累计字节数以及你自身应用产生的流量。想精确到“系统里每个 App 分别在移动数据/WiFi 下用了多少”依赖的是系统级使用统计能力或者特权权限普通第三方 App 拿不到。所以我把产品定位成“使用提醒 本地记录”提醒用户当前连接状态、估算用量、帮用户建立流量使用习惯而不是号称“绝对精确到 KB 的账单”。这个边界在产品文案和隐私说明里都写得很清楚。技术上能做到的合理上限是统计当前网络接口的整体收发量再叠加应用自身模块的用量记录给用户一个可靠的参考值。这个定位想清楚之后很多不切实际的需求就可以直接砍掉开发成本也降下来不少。5.2 权限与隐私披露的实操细节监管类 App 天然会索取较多权限隐私合规必须前置。我的做法是第一进入 WiFi 详情页时系统授权弹窗出现之前先展示一页“为什么需要这些权限”的说明页每个权限对应一条用途说明用户同意后才会触发系统授权第二所有采集到的 WiFi 信息和用量记录只保存在设备本地不做云端上传第三隐私政策里列全数据字段清单包括 SSID、BSSID、连接时间戳和用量数据并说明这些数据不会被用于画像或共享。隐私设计还有一个实操细节应用市场审核时权限申请必须和功能强绑定。我见过不少同类 App 因为“申请了定位权限却说不清用途”被卡在 OpenHarmony 生态的合规审核里同样严格。宁可把权限拆细一点申请也不要贪多。5.3 可继续扩展的路线WiFi 详情页跑通之后我列的后续扩展优先级是这样的分应用流量排行基于系统使用统计能力按应用维度拆出移动数据与 WiFi 用量用量的超额提醒在用户接近移动数据阈值时推送通知多设备家庭模式把家里几台设备的用量集中到一台主设备上看。这三个方向都建立在已有的数据仓库和权限框架之上不需要推倒架构。最后分享一个从这次开发里沉淀下来的小习惯插件层每一条系统接口的返回值我都会打一条带固定 tag 的日志Dart 侧收到回调后再打一条。出问题时先翻这两条日志就能快速区分到底是系统行为异常还是我自己的代码写错。表面看多打了几行日志但这类双端联调场景里它比任何调试器都直接。希望你做同样的事情时能少走我踩过的这些弯路。