原创

在HarmonyOS 6购物比价或电商类应用中,有时需要通过 CES(Common Event Service,公共事件服务)​ 向其他应用/进程发自定义事件——比如"订单支付成功通知插件"、"门店POS端刷新待配货列表"。但公共事件是系统级广播,默认所有经 addCommonEventSubscriber订阅同事件名的应用都能收到,存在信息泄漏与不必要的唤醒风险。

官方行业FAQ明确给出两种限制手段:① 权限约束(subscriberPermissions/ publisherPermission)② 包名定向(bundleName指定收/发方)。本文将完整说明两种方案及代码实战。


一、现象:自定义公共事件被无关应用收到

1. 问题现场

// 发送方——发布自定义公共事件
commonEventManager.publish('com.shop.ORDER_PAID', {
  data: '{"orderId":"O8848"}'
});

// 接收方——任意应用订阅同名事件
const subscriber = commonEventManager.createSubscriberSync({
  events: ['com.shop.ORDER_PAID']
});
commonEventManager.subscribe(subscriber, (err, data) => { /* 收到 */ });

结果:设备上其他无关App若也订阅 'com.shop.ORDER_PAID'同样能收到——不符合"仅指定应用可收"的预期。

2. 根因揭秘

HarmonyOS 公共事件默认不做应用级隔离,仅靠事件名匹配。要约束接收范围须显式声明:

约束方式

做法

适用场景

权限约束

发送方 CommonEventPublishData.subscriberPermissions声明所需权限 + 订阅方需在其 module.json5中申请该权限(或订阅方 CommonEventSubscribeInfo.publisherPermission反向约束发送方权限)

同一生态内多应用共享事件、有统一自定义权限

包名定向

发送方 CommonEventPublishData.bundleName指定接收方 bundleName;或订阅方 CommonEventSubscribeInfo.publisherBundleName只收指定包发来的事件

明确点对点(如主应用→Widget/卡片、或 POS端→中台App)


二、方案A(推荐定向收发):bundleName 指定接收包名

最适合 主应用 → 桌面卡片/服务卡片 / 门店端App​ 这种明确一对一或一对少数的场景。

1. 发送方——限定接收 bundleName

// utils/OrderEventPublisher.ets
import { commonEventManager } from '@kit.BasicServicesKit';
import { BusinessError } from '@kit.BasicServicesKit';

/**
 * 发布"订单支付成功"定向公共事件
 * @param targetBundleName 接收方包名,如 'com.example.shopwidget'
 * @param orderId           支付成功的订单ID
 */
export function publishOrderPaid(targetBundleName: string, orderId: string): void {
  const publishData: commonEventManager.CommonEventPublishData = {
    bundleName: targetBundleName,   // ✅ 关键:仅此 bundleName 的应用可收到
    data: JSON.stringify({ orderId, ts: Date.now() }),
    isOrdered: false                 // 无序事件即可
  };

  commonEventManager.publish(
    'com.shop.ORDER_PAID',
    publishData,
    (err: BusinessError) => {
      if (err) {
        console.error(`publish ORDER_PAID err: ${err.code} ${err.message}`);
      }
    }
  );
}

调用示例(支付回调中):

// 支付成功后
publishOrderPaid('com.example.shopwidget', 'O8848');

2. 订阅方——普通订阅即可(包名匹配自动放行)

// widget/ServiceCard 内
import { commonEventManager } from '@kit.BasicServicesKit';

const subscribeInfo: commonEventManager.CommonEventSubscribeInfo = {
  events: ['com.shop.ORDER_PAID'],
  // 可选:若想反过来只收指定主应用发的事件可加
  // publisherBundleName: 'com.example.shopmain'
};

const subscriber = commonEventManager.createSubscriberSync(subscribeInfo);

commonEventManager.subscribe(subscriber, (err, data) => {
  if (err) return;
  const payload = JSON.parse(data!.data! '{}');
  console.log(`[Widget] 收到订单支付: ${payload.orderId}`);
  // 触发卡片刷新...
});

若发送方 bundleName与订阅方自身 bundleName 不匹配 → CES 直接丢弃该事件,订阅回调不触发。


三、方案B(生态多应用共享):权限约束 subscriberPermissions

适合同一厂商多条产品线(主App、Lite版、商家版)均需监听同一事件,且不想逐个列 bundleName。

1. 定义自定义权限(所有相关模块统一)

module.json5(发送方 & 接收方均需声明)

{
  "reqPermissions": [
    {
      "name": "com.shop.permission.RECEIVE_ORDER_EVENT",
      "reason": "接收订单支付公共事件",
      "usedScene": { "abilities": ["EntryAbility"] }
    }
  ],
  "defPermissions": [
    {
      "name": "com.shop.permission.RECEIVE_ORDER_EVENT",
      "grantMode": "system_grant",   // 或 user_grant 视安全等级
      "availableScope": ["bundle"]
    }
  ]
}

2. 发送方声明 subscriberPermissions

const publishData: commonEventManager.CommonEventPublishData = {
  subscriberPermissions: ['com.shop.permission.RECEIVE_ORDER_EVENT'],
  data: JSON.stringify({ orderId: 'O8848', ts: Date.now() })
};
commonEventManager.publish('com.shop.ORDER_PAID', publishData, (err)=>{ /*...*/ });

3. 订阅方(已申请该权限即可收到)

订阅方 CommonEventSubscribeInfo可不额外配置(只要应用已获该权限)——若想反向限定只接收带此权限发布的事件,可设:

const subInfo: commonEventManager.CommonEventSubscribeInfo = {
  events: ['com.shop.ORDER_PAID'],
  publisherPermission: 'com.shop.permission.RECEIVE_ORDER_EVENT'
};

权限方式要求所有参与应用预装同一定义权限,适合企业分发/同厂商多App;对外部三方插件推荐用 bundleName定向。


四、避坑指南

问题

原因

修复

设了 bundleName订阅方仍收不到

订阅方自身 bundleName与发送方填的不一致(含大小写/后缀)

AppScope/app.json5 → bundleName核对完全一致;bm dump -n 包名确认

权限约束事件收不到

订阅方 module.json5 未声明 reqPermissions或用户未授权(user_grant)

确认 reqPermissions含自定义权限且运行时已授权

想同时限定包名+权限

可同时设 bundleName+ subscriberPermissions→ 需两者同时满足

通常二选一即可

动态订阅后台收不到

应用进程后台默认暂停动态订阅回调;需改用静态订阅(json5配 staticSubscriber或前台触发

卡片/ServiceExtension 内用静态订阅保活接收

hidumper 查订阅者权限

hidumper -s 3299 -a -e可看 subscriber 详情(含 permission 字段)

用于联调验证


五、总结:公共事件限定订阅者 SOP

  1. 明确场景选方案

    • 点对点 / 主应用↔卡片 → CommonEventPublishData.bundleName = 接收方bundleName(最简单安全)

    • 同厂商多应用共享 → 自定义权限 subscriberPermissions+ module.json5 声明

  2. 发送方publish(event, { bundleName / subscriberPermissions, data })

  3. 订阅方:普通 createSubscriberSync({events});如需反向限定发送方包名或权限可设 publisherBundleName/ publisherPermission

  4. 校验:用 hidumper -s 3299 -a -e确认 subscriber 属性生效

核心法则:HarmonyOS 6 中公共事件限制接收者 = "定向用 bundleName,群体用自定义权限 subscriberPermissions",默认不约束则系统内所有同名订阅者均可收到。

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐