问题背景:通知误拦与多端状态脑裂
在HarmonyOS 7.0(API 26)之前,免打扰模式对所有通知一视同仁。夜间服务器宕机告警和外卖推荐可能都被拦截。开发者要么让用户错过重要告警,要么用非合规方式强行唤醒屏幕,带来稳定性与续航风险。多设备场景下还有“多端通知幽灵”:手机、平板、手表同时震动,用户在一端划掉通知,其他端仍保留;实况窗状态也容易不同步,例如手机显示司机已到达,手表仍显示两公里外。
HarmonyOS 7.0 对系统底层 NotificationManagerService(NMS)做了深度架构重构。Notification Kit 引入情境感知通知分发、基于意图的免打扰智能穿透,以及多端实况窗强一致状态同步,将跨设备通知管理从松散广播提升为分布式强一致状态机。
需要特别注意:ContextualIntent 智能穿透和 DistributedNotificationSyncMode 强一致同步依赖 HarmonyOS 7.0(API 26)及以上版本。API 25及以下调用相关接口会抛出不支持异常。跨端同步还要求测试设备登录同一华为账号,并开启分布式软总线与蓝牙/Wi-Fi。
工程结构:职责分离的通知同步模块
示例工程把通知管理拆成通知服务、路由策略、常量和实况窗状态模型,便于高内聚低耦合。核心目录如下:
- entry/src/main/ets/
- ├── entryability
- │ └── EntryAbility.ets
- ├── pages
- │ └── Index.ets
- └── service
- ├── notification
- │ ├── NotificationSyncService.ets
- │ ├── ContextualRouter.ets
- │ └── Constants.ets
- └── model
- └── LiveViewState.ets
复制代码
其中 NotificationSyncService.ets 负责分布式实况窗状态流转,ContextualRouter.ets 处理 DND 智能穿透与意图评估,LiveViewState.ets 维护实况窗状态枚举与校验逻辑。
核心API:动态优先级与意图穿透
旧版 NotificationRequest 的优先级在创建时静态绑定。API 26 中,通过 notificationManager.publish 下发通知时,NMS 会拦截并进入规则引擎,再根据 ContextualIntent 动态重算优先级。关键参数有两个:
- intentCategory:定义通知业务意图。API 26 新增 SYSTEM_ALARM、EMERGENCY_CONTACT、CRITICAL_SERVICE 等高权级枚举。
- penetrationLevel:穿透等级。配合 intentCategory,系统决定是否在 DND 开启甚至专注模式下强提醒。
如果使用 CRITICAL_SERVICE 级穿透,必须在 module.json5 声明 ohos.permission.NOTIFICATION_CRITICAL_PENETRATION,并在上架审核时提供严格的业务必要性证明。滥用该权限发送营销通知,可能被阻断通知通道甚至下架。
权限声明示例如下:
- "requestPermissions": [
- {
- "name": "ohos.permission.NOTIFICATION_CONTROLLER"
- },
- {
- "name": "ohos.permission.DISTRIBUTED_DATASYNC"
- },
- {
- "name": "ohos.permission.NOTIFICATION_CRITICAL_PENETRATION",
- "reason": "$string:critical_notification_reason"
- }
- ]
复制代码
发布严重告警时,核心请求对象会配置 CRITICAL_SERVICE、HIGH 穿透和 LEVEL_HIGH 优先级:
- const request: notificationManager.NotificationRequest = {
- id: this.CRITICAL_ALARM_ID,
- intentCategory: notificationManager.IntentCategory.CRITICAL_SERVICE,
- penetrationLevel: notificationManager.PenetrationLevel.HIGH,
- priority: notificationManager.NotificationPriority.LEVEL_HIGH,
- deliveryTime: new Date().getTime()
- };
复制代码
如果抛出 201 权限错误,应检查 module.json5 是否已正确声明 CRITICAL_PENETRATION 权限。
多端实况窗:SYNC_MODE_STRONG 与版本号
实况窗具有持续更新、状态流转特征。API 26 为 NotificationRequest 引入 distributedSyncMode,可选枚举包括:
- SYNC_MODE_NONE:不跨端,仅本地设备显示。
- SYNC_MODE_WEAK:弱一致性广播,状态更新可能乱序。
- SYNC_MODE_STRONG:强一致性同步,基于 Vector Clock 解决跨端脑裂。
多端实况窗应选用 SYNC_MODE_STRONG,并配合 version 数据版本号做并发控制:
- const liveViewRequest: notificationManager.NotificationRequest = {
- id: this.LIVE_VIEW_BASE_ID,
- distributedSyncMode: notificationManager.DistributedSyncMode.SYNC_MODE_STRONG,
- version: version,
- isFloatingIcon: true
- };
复制代码
version 的维护非常关键。真实业务中建议使用服务端事务时间戳或本地持久化的单调递增序列号,切忌每次传 0 或随机数,否则会破坏 NMS 底层的向量时钟仲裁逻辑,导致多端状态无法收敛。
取消实况窗时,调用 notificationManager.cancel 取消本地通知后,由于此前配置了 SYNC_MODE_STRONG,NMS 会自动发送 Tombstone 消息同步到平板、手表,彻底抹除记录。
底层逻辑:路由树、向量时钟与CRDT
NMS 收到通知请求后,不再简单放入队列,而是送入基于策略的路由树。通知从应用层 publish 到多端亮屏展示,会经历一套处理流水线。多端状态同步的核心是 Vector Clock 与 CRDT。
当手机和平板在极短时间内同时操作同一个实况窗,例如手机更新进度、平板执行清除,网络延迟可能导致操作在不同设备上的到达顺序不一致。向量时钟用于识别事件发生的因果关系;对于并发冲突,则依赖 CRDT 合并策略,通常是高权级操作如“清除”覆盖“更新”,保证所有设备最终收敛到一致状态。
避坑一:弱网下的向量时钟异常与状态退化
极端弱网环境中,例如用户佩戴手表进入地下车库,手机留在车内,两端连接断开。如果手表端因超时终止实况窗,手机端又收到服务端更新,连接恢复后可能产生状态冲突。
处理原则是以产生核心业务数据的那一端,通常是负责联网的手机主设备,作为权威数据源。通过 DistributedDeviceManager 检测到网络重新连接时,主动推送一个包含极高 version 增量(例如当前 Unix 毫秒时间戳)的覆写包,强制拉齐周边从属设备状态,防止状态机退化回旧状态。
避坑二:穿透权限动态降级不会抛异常
即使代码声明了 CRITICAL_SERVICE 且上架审核通过,系统仍保留动态降级权力。如果用户在系统设置中心关闭“允许重要提醒打扰”,NMS 的 Context Rules Engine 会在评估时把 penetrationLevel 强制重写为 NORMAL。
降级发送不会抛出 BusinessError,系统会正常返回成功,只是把通知放入静默列表。因此关键告警业务不能只靠 try-catch。应通过系统事件总线监听用户通知权限设置状态,检测到高优通道被关时,在应用内弹窗强引导用户重新授予高级权限。
避坑三:Notification ID 跨端碰撞
开启分布式同步后,NotificationRequest.id 的作用域从“本机应用内”放大到“跨端同应用内”。如果手机和平板分别生成相同 ID=100 的不同通知,并且都开启同步,会导致对端设备发生非预期交叉覆盖。
构建带 distributedSyncMode 的通知 ID 时,应引入端设备标识,或使用雪花算法生成全局唯一的长整型哈希作为 ID 的一部分,阻断不同设备生成同名 ID 的可能。
总结
HarmonyOS 7.0 的 Notification Kit 把跨端通知从文本广播推向带路由策略和强一致性保障的分布式状态机。基于意图的免打扰穿透,将打扰权交还上下文和场景;软总线上的向量时钟同步,则用于解决多端脑裂。落地这些特性不仅考验 API 熟练度,也考验对 CRDT 与状态机管理等分布式一致性架构的理解。在全场景设备生态中,把通知精准、安全、一致地送达正确设备屏幕,会成为企业级应用体验差异的重要来源。 |