给鸿蒙 App 增加广播收发能力 —— flutter_broadcasts 的鸿蒙使用指南
给鸿蒙 App 增加广播收发能力 —— flutter_broadcasts 的鸿蒙使用指南
应用跑久了要感知息屏、组件之间要解耦传消息、多个页面要对同一份数据变化做出反应——这些场景用逐层回调或事件总线都能做,但要写不少样板代码。flutter_broadcasts 把这件事收敛成一套 API:构造 BroadcastReceiver 订阅事件,调用 sendBroadcast 发布消息,语义与 Android 的广播机制一致。该库已完成 OpenHarmony 适配,仓库见 atomgit.com/oh-flutter/flutter_broadcasts(TAG 0.4.0-ohos-1.0.0-beta.1)。读完本文,你能在鸿蒙 Flutter 应用里完成广播的订阅、发送、退订,并正确处理系统开关屏事件与后台冻结等边界情况。
一、最终运行效果
下面三张图来自适配仓库自带的 example 在 HUAWEI 真机上的实拍。第一张是应用启动后的整体界面:上方是发送卡片,下方两张接收卡片分别订阅自定义事件与系统事件:

第二张是点击 Send Broadcast 后的效果:Receiver A 立刻收到消息,data 为 {from: demo},消息自带时间戳,发送侧弹出 SnackBar 确认发布完成:

第三张是订阅系统公共事件的效果:按电源键息屏再亮屏后,Receiver B 收到带 reason 参数的 SCREEN_ON 事件:

二、flutter_broadcasts 是什么
flutter_broadcasts 是 pub.dev 上的跨平台广播插件(作者 Kevin Latusinski,MIT 协议,上游 0.4.0),在 Android 上基于 BroadcastReceiver/Intent,在 iOS 上基于 NSNotificationCenter。核心概念只有两个:
BroadcastReceiver:持有若干事件名names的订阅者,通过start()/stop()控制生命周期,收到的消息从messages流发出;BroadcastMessage:一条消息,含事件名name、可选参数data、时间戳timestamp。
鸿蒙侧已由社区完成适配并保持接口不变:事件名对应 commonEventManager 的公共事件,data 对应公共事件参数。上面提到的仓库 TAG 即适配版本。
三、环境准备
鸿蒙 Flutter 环境的搭建直接照官方指南做:
本文实测环境:
| 项 | 版本 |
|---|---|
| Flutter(ohos 版) | 3.41.10-ohos-1.0.1(Dart 3.11.5) |
| 编译 SDK | compatibleSdkVersion 5.1.0(18),runtimeOS HarmonyOS |
| 实测设备 | HUAWEI ADA-AL10U 真机(OpenHarmony 6.1.1.120 / API 24) |
运行示例需要一台已完成签名配置的鸿蒙真机或模拟器;未签名构建产物无法安装。
四、引入依赖
自建工程的标准方式是 AtomGit git 依赖,ref 锁定 TAG:
dependencies:
flutter_broadcasts:
git:
url: https://atomgit.com/oh-flutter/flutter_broadcasts.git
ref: 0.4.0-ohos-1.0.0-beta.1
执行 flutter pub get 完成拉取。TAG 与框架版本对照:
| 项 | 值 |
|---|---|
| Flutter(ohos 版) | 3.41.10-ohos-1.0.1 |
| TAG | 0.4.0-ohos-1.0.0-beta.1 |
| 分支 | feat/ohos_flutter_broadcasts_0.4.0 |
以上组合在 stable 渠道实测通过;canary 引擎对宿主工程 compatibleSdkVersion 有额外要求,构建报 SDK 版本不匹配时优先检查这里。
五、代码接入
5.1 订阅广播消息
先构造 BroadcastReceiver 声明要订阅的事件名(至少一个),监听 messages 流,再调用 start() 生效:
import 'package:flutter_broadcasts/flutter_broadcasts.dart';
final receiver = BroadcastReceiver(
names: ['com.example.app.data_changed'],
);
receiver.messages.listen((BroadcastMessage message) {
debugPrint('received ${message.name}: ${message.data}');
});
await receiver.start();
names 是事件名列表,一个 receiver 可以同时订阅多个事件;start() 返回 Future<void>,成功即完成原生侧订阅。注意 start() 重复调用会抛 StateError,重新订阅前先确认 isListening 为 false 或调用过 stop()。
5.2 发送广播
sendBroadcast 是顶层函数,构造 BroadcastMessage 传入事件名与参数即可:
await sendBroadcast(
BroadcastMessage(
name: 'com.example.app.data_changed',
data: {'action': 'refresh', 'source': 'settings_page'},
),
);
data 是 Map<String, dynamic>,支持字符串、数字、布尔值与它们的嵌套组合,无法编码的类型会被降级为字符串传输。返回的 Future 在原生侧发布完成后完成,与 Android 端不同,不需要担心 Future 永不完成的问题。
5.3 停止订阅
stop() 退订原生侧并释放流订阅:
await receiver.stop();
stop() 对未启动的 receiver 是安全的空操作。配合 isListening 可以写出状态可靠的切换按钮——注意 stop() 后要 setState 刷新界面,见 5.6 的完整示例。
5.4 订阅系统公共事件
把 OpenHarmony 的系统公共事件名放进 names 即可,用法与自定义事件完全一致。以感知开关屏为例:
final systemReceiver = BroadcastReceiver(
names: ['usual.event.SCREEN_ON', 'usual.event.SCREEN_OFF'],
);
systemReceiver.messages.listen((BroadcastMessage message) {
if (message.name == 'usual.event.SCREEN_OFF') {
debugPrint('screen off');
} else if (message.name == 'usual.event.SCREEN_ON') {
debugPrint('screen on, reason: ${message.data?['reason']}');
}
});
await systemReceiver.start();
系统事件自带的参数会出现在 data 里,如开关屏事件的 reason 字段(POWER_KEY、HARD_KEY、TIMEOUT 等)。注意锁屏后应用进程被系统冻结期间事件会延迟补发,见 5.6 与第八章。
5.5 跨平台一套代码
以上代码没有任何平台判断分支:同一份 Dart 代码在 Android、iOS、鸿蒙上行为一致。这是适配"只做加法"的直接收益——业务里不需要 Platform.isOhos 之类的判断,未来上游升级时合并成本也最低。唯一需要留意的差异在系统事件名上:usual.event.* 是鸿蒙的事件命名体系,Android 对应 Intent.ACTION_*,做跨平台系统事件感知时这一层映射需要业务自己维护。
5.6 实战场景:感知息屏自动暂停后台任务
一个贴近业务的完整例子:页面里有轮询任务,息屏时暂停、亮屏时恢复,同时界面上展示订阅状态。完整片段可整体搬运:
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_broadcasts/flutter_broadcasts.dart';
class ScreenAwarePollingPage extends StatefulWidget {
const ScreenAwarePollingPage({super.key});
State<ScreenAwarePollingPage> createState() => _ScreenAwarePollingPageState();
}
class _ScreenAwarePollingPageState extends State<ScreenAwarePollingPage> {
static const _screenOn = 'usual.event.SCREEN_ON';
static const _screenOff = 'usual.event.SCREEN_OFF';
final BroadcastReceiver _screenReceiver = BroadcastReceiver(
names: [_screenOn, _screenOff],
);
Timer? _timer;
int _tick = 0;
void initState() {
super.initState();
_screenReceiver.messages.listen((BroadcastMessage message) {
if (!mounted) {
return;
}
if (message.name == _screenOn) {
_startPolling();
} else if (message.name == _screenOff) {
_stopPolling();
}
setState(() {}); // 刷新界面上的订阅状态
});
_screenReceiver.start();
_startPolling();
}
void _startPolling() {
_timer ??= Timer.periodic(const Duration(seconds: 2), (_) {
setState(() => _tick++);
});
}
void _stopPolling() {
_timer?.cancel();
_timer = null;
}
void dispose() {
_stopPolling();
_screenReceiver.stop();
super.dispose();
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Screen Aware Polling')),
body: Center(
child: Text(
'listening: ${_screenReceiver.isListening}\n'
'polling: ${_timer != null}\n'
'ticks: $_tick',
textAlign: TextAlign.center,
),
),
);
}
}
三个要点:消息回调里先判 mounted 再动 UI;dispose 里 stop() 释放订阅;isListening 是普通 getter,状态变化后必须 setState 才会反映到界面。
六、运行与验证
构建、安装、启动 example 的完整命令:
cd example
flutter build hap --debug
cd ohos
hdc install -r ../build/ohos/hap/entry-default-signed.hap
hdc shell aa start -b de.kevlatus.flutter_broadcasts_example -a EntryAbility
各核心接口的验证步骤:
发送与接收:保持 Receiver A 订阅(isListening: true),点击 Send Broadcast,消息列表出现 {from: demo} 且带时间戳,发送侧弹出完成 SnackBar(效果见第一章第二张图)。
停止订阅:点击 Receiver A 的 stop,状态变为 false;再点 Send,消息列表不新增。重新 start 后发送恢复正常(效果见适配教程图七至图九)。
系统事件:Receiver B 保持订阅,按电源键息屏再亮屏,收到带 reason 的 SCREEN_ON/SCREEN_OFF(效果见第一章第三张图)。
受限场景如实说明:锁屏约十秒后应用进程被系统冻结,此后的系统事件不会实时送达,解锁回前台后系统集中补发。实测中锁屏 35 分钟积累的事件在解锁后全部按序到达。这是 OpenHarmony 的进程管理行为;前台场景的自定义事件不受影响。
七、工作原理
一次广播的完整链路:
Dart: sendBroadcast(BroadcastMessage)
│ MethodChannel 'de.kevlatus.flutter_broadcasts' / sendBroadcast
▼
ArkTS 插件: FlutterBroadcastsPlugin.onSendBroadcast
│ data 展开为 Record,避免跨进程序列化丢参
▼
commonEventManager.publish(name, {parameters})
│ 公共事件服务跨进程分发
▼
订阅方 ArkTS 插件: subscribe 回调收到 CommonEventData
│ 过滤系统注入的 moduleName,补毫秒 timestamp
│ invokeMethod('receiveBroadcast', message)
▼
Dart: _BroadcastChannel 单例收到回调
│ 解码为 BroadcastMessage,按 receiverId 过滤
▼
BroadcastReceiver.messages 流 → 业务回调
订阅方向对称:start() 走 startReceiver,ArkTS 侧 createSubscriber + subscribe 建立监听;stop() 走 stopReceiver,对应 unsubscribe。
三个对使用方有意义的实现细节:
- 同名双通道与平台路由:Dart 侧按
Platform.operatingSystem == 'ohos'把调用路由到鸿蒙实现,业务层完全无感,原理细节见适配教程 2.4。 - data 的类型边界:插件在原生侧把参数规整为编解码器可表示的类型,无法表示的降级为字符串,与 Android 端行为一致,业务不要往
data里塞自定义对象。 - timestamp 的语义:接收消息的
timestamp是事件到达原生侧的时刻(毫秒),由鸿蒙端插件补齐;它与发送时刻可能有微小差值,对时间敏感的业务要注意。
八、常见问题
Q1:start() 抛 StateError 提示 already started。
同一个 BroadcastReceiver 实例不允许重复 start()。先 stop() 再 start(),或者用 isListening 做守卫;UI 上建议把按钮状态与 isListening 绑定并在操作后 setState,从交互上杜绝重复启动。
Q2:多个 receiver 订阅同一个事件名,会都收到吗?
会。公共事件是广播语义,每个 receiver 在原生侧有独立订阅,消息按 receiverId 分发给各自的流,互不影响。
Q3:发送方自己也订阅了这个事件,能收到吗?
能。公共事件服务不区分发布方与订阅方,只要订阅了就会收到,包括本应用发出的事件。做"发送方排除自己"这类逻辑时,在 data 里带上来源标识由业务自行过滤。
Q4:锁屏后收不到系统事件,解锁后一次性收到一批。
进程冻结导致的延迟补发,不是插件丢消息。需要严格实时性的业务(如息屏立即暂停任务)请按 5.6 的模式处理——收到补发事件时状态最终一致;也可以在页面退后台时主动 stop() 订阅、回前台 start(),把补发窗口消除掉。机理详见适配教程 4.2。
Q5:data 里塞了一个自定义类实例,收到的却是字符串。
编解码器无法表示的类型会被降级为字符串传输,这是刻意的对齐行为。请在发送前把对象转成 Map<String, dynamic> 或 JSON 字符串,接收侧再解析。
九、结语与相关链接
flutter_broadcasts 用两个类覆盖了广播通信的全部常规需求,鸿蒙适配保持接口不变,业务接入成本接近于零。使用问题欢迎在鸿蒙仓库提 Issue,原库行为相关的讨论建议到上游仓库 Issue反馈。
欢迎加入 CPF-Flutter 鸿蒙社区,社区入口、环境搭建指南和本文相关链接统一放在这里:
更多推荐




所有评论(0)