Harmony開發之公共事件與通知——應用間的溝通橋樑

引入:跨應用協同的魔法

在日常使用手機時,我們經常會遇到這樣的場景:當Wi-Fi連接成功後,多個應用會同時彈出"網絡已連接"的提示;當收到新消息時,即使應用在後台運行,也能在通知欄看到提醒。這些看似簡單的功能背後,正是HarmonyOS公共事件與通知機制在發揮作用。它們如同應用間的"神經系統",讓不同的應用能夠感知系統狀態變化,實現跨應用的協同工作。

一、公共事件與通知機制概述

1.1 核心概念

**公共事件(Common Event)**是HarmonyOS提供的應用間通信機制,允許應用訂閲系統或其他應用發佈的事件,實現後台的事件驅動通信。公共事件服務(CES)負責事件的訂閲、發佈和退訂管理。

**通知(Notification)**是應用向用户傳遞信息的主要方式,通過通知增強服務(ANS)系統服務來為應用程序提供發佈通知的能力。通知會在狀態欄、通知中心等位置顯示給用户,支持多種樣式和交互操作。

1.2 通信模式對比

特性

公共事件

通知

通信模式

訂閲/發佈:單向、匿名的後台通信

點對點/系統託管:面向用户的交互

可見性

後台執行,用户無感知

狀態欄、通知中心可見

主要目的

系統內部通信、狀態同步

人機交互、信息提醒

參與者

發佈者、訂閲者(應用或系統服務)

發佈者(應用)、系統服務、用户

二、公共事件開發詳解

2.1 公共事件類型

系統公共事件

系統預定義的事件,由系統服務在狀態變化時發佈,常見的有:

  • usual.event.SCREEN_OFF(屏幕關閉)
  • usual.event.WIFI_CONNECTED(Wi-Fi已連接)
  • common.event.DEVICE_OFFLINE(設備下線)
  • common.event.PACKAGE_REMOVED(應用包被移除)
自定義公共事件

應用為處理特定業務邏輯而定義的事件,主要用於實現跨進程的事件通信能力。

2.2 公共事件發送方式

無序公共事件

CES轉發事件時不考慮訂閲者接收順序,不保證順序一致性。

有序公共事件

根據訂閲者優先級順序傳遞,高優先級訂閲者可修改事件內容或終止事件傳遞。

粘性公共事件

支持先發布後訂閲,事件會持久化在系統中供後續訂閲者接收。

2.3 核心接口類

公共事件相關基礎類包含:

  • CommonEventData:封裝公共事件相關信息
  • CommonEventPublishInfo:封裝公共事件發佈相關屬性
  • CommonEventSubscribeInfo:封裝公共事件訂閲相關信息
  • CommonEventSubscriber:封裝公共事件訂閲者及相關參數
  • CommonEventManager:提供訂閲、退訂和發佈公共事件的靜態接口

三、公共事件實戰開發

3.1 訂閲公共事件

import commonEvent from '@ohos.commonEventManager';
import { BusinessError } from '@ohos.BasicServicesKit';

// 訂閲網絡連接變化事件
async function subscribeNetworkEvent(): Promise<void> {
  try {
    const subscribeInfo = {
      events: ["usual.event.network.CONNECTIVITY_CHANGE"]
    };
    
    const subscriber = await commonEvent.createSubscriber(subscribeInfo);
    
    commonEvent.subscribe(subscriber, (err: BusinessError, data: commonEvent.CommonEventData) => {
      if (err) {
        console.error(`訂閲失敗: ${err.code}, ${err.message}`);
        return;
      }
      
      console.info('收到網絡變化事件');
      // 處理網絡狀態變化邏輯
      this.handleNetworkChange();
    });
  } catch (error) {
    console.error('創建訂閲者失敗:', error);
  }
}

3.2 發佈自定義公共事件

import commonEvent from '@ohos.commonEventManager';
import { BusinessError } from '@ohos.BasicServicesKit';

// 發佈自定義公共事件
async function publishCustomEvent(): Promise<void> {
  try {
    const options: commonEvent.CommonEventPublishData = {
      code: 1,
      data: "自定義事件數據"
    };
    
    await commonEvent.publish("com.example.MY_CUSTOM_EVENT", options);
    console.info('自定義事件發佈成功');
  } catch (error) {
    console.error('發佈事件失敗:', error);
  }
}

// 發佈帶權限的公共事件
async function publishPermissionEvent(): Promise<void> {
  try {
    const options: commonEvent.CommonEventPublishData = {
      code: 1,
      data: "帶權限的事件數據",
      subscriberPermissions: ["com.example.permission.MY_PERMISSION"]
    };
    
    await commonEvent.publish("com.example.PERMISSION_EVENT", options);
    console.info('帶權限事件發佈成功');
  } catch (error) {
    console.error('發佈帶權限事件失敗:', error);
  }
}

3.3 發佈有序公共事件

import commonEvent from '@ohos.commonEventManager';
import { BusinessError } from '@ohos.BasicServicesKit';

// 發佈有序公共事件
async function publishOrderedEvent(): Promise<void> {
  try {
    const options: commonEvent.CommonEventPublishData = {
      code: 1,
      data: "有序事件數據",
      isOrdered: true
    };
    
    await commonEvent.publish("com.example.ORDERED_EVENT", options);
    console.info('有序事件發佈成功');
  } catch (error) {
    console.error('發佈有序事件失敗:', error);
  }
}

3.4 發佈粘性公共事件

import commonEvent from '@ohos.commonEventManager';
import { BusinessError } from '@ohos.BasicServicesKit';

// 發佈粘性公共事件
async function publishStickyEvent(): Promise<void> {
  try {
    const options: commonEvent.CommonEventPublishData = {
      code: 1,
      data: "粘性事件數據",
      isSticky: true
    };
    
    await commonEvent.publish("com.example.STICKY_EVENT", options);
    console.info('粘性事件發佈成功');
  } catch (error) {
    console.error('發佈粘性事件失敗:', error);
  }
}

3.5 權限配置

module.json5中配置所需權限:

{
  "module": {
    "reqPermissions": [
      {
        "name": "ohos.permission.COMMONEVENT_STICKY",
        "reason": "發佈粘性公共事件",
        "usedScene": {
          "ability": [".MainAbility"],
          "when": "inuse"
        }
      },
      {
        "name": "com.example.permission.MY_PERMISSION",
        "reason": "自定義權限",
        "usedScene": {
          "ability": [".MainAbility"],
          "when": "inuse"
        }
      }
    ]
  }
}

四、通知開發詳解

4.1 通知類型

HarmonyOS支持六種通知樣式:

  • 普通文本(NOTIFICATION_CONTENT_BASIC_TEXT)
  • 長文本(NOTIFICATION_CONTENT_LONG_TEXT)
  • 圖片(NOTIFICATION_CONTENT_PICTURE)
  • 社交(NOTIFICATION_CONVERSATIONAL_CONTENT)
  • 多行文本(NOTIFICATION_MULTILINE_CONTENT)
  • 媒體(NOTIFICATION_MEDIA_CONTENT)

4.2 通知重要級別

NotificationSlot的級別支持:

  • LEVEL_NONE:通知不發佈
  • LEVEL_MIN:通知可以發佈,但不顯示在通知欄
  • LEVEL_LOW:通知顯示在通知欄,不自動彈出
  • LEVEL_DEFAULT:通知顯示在通知欄,觸發提示音
  • LEVEL_HIGH:通知顯示在通知欄,自動彈出,觸發提示音

4.3 核心接口類

通知相關基礎類包含:

  • NotificationSlot:設置提示音、振動、鎖屏顯示和重要級別
  • NotificationRequest:設置具體的通知對象
  • NotificationHelper:封裝發佈、更新、刪除通知等靜態方法

五、通知實戰開發

5.1 創建通知渠道

import { notificationManager } from '@kit.NotificationKit';
import { BusinessError } from '@kit.BasicServicesKit';

// 創建通知渠道
async function createNotificationSlot(): Promise<void> {
  try {
    const slot = {
      id: "slot_001",
      name: "默認通知渠道",
      level: notificationManager.SlotLevel.LEVEL_DEFAULT,
      enableVibration: true,
      lockscreenVisibleness: notificationManager.VisibilityType.VISIBILITY_TYPE_PUBLIC,
      enableLight: true,
      ledLightColor: Color.RED
    };
    
    await notificationManager.addSlot(slot);
    console.info('通知渠道創建成功');
  } catch (error) {
    console.error('創建通知渠道失敗:', error);
  }
}

5.2 發佈普通文本通知

import { notificationManager } from '@kit.NotificationKit';
import { BusinessError } from '@kit.BasicServicesKit';

// 發佈普通文本通知
async function publishTextNotification(): Promise<void> {
  try {
    const notificationRequest: notificationManager.NotificationRequest = {
      id: 1,
      slotId: "slot_001",
      content: {
        notificationContentType: notificationManager.ContentType.NOTIFICATION_CONTENT_BASIC_TEXT,
        normal: {
          title: "通知標題",
          text: "這是通知內容",
          additionalText: "附加信息"
        }
      },
      badgeNumber: 1,
      autoDeletedTime: Date.now() + 3000 // 3秒後自動刪除
    };
    
    await notificationManager.publish(notificationRequest);
    console.info('文本通知發佈成功');
  } catch (error) {
    console.error('發佈通知失敗:', error);
  }
}

5.3 發佈圖片通知

import { notificationManager } from '@kit.NotificationKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { image } from '@kit.ImageKit';

// 發佈圖片通知
async function publishImageNotification(): Promise<void> {
  try {
    // 獲取圖片資源
    const resourceManager = getContext().resourceManager;
    const imageArray = await resourceManager.getMediaContent($r('app.media.notification_icon').id);
    const imageResource = image.createImageSource(imageArray.buffer);
    const imagePixelMap = await imageResource.createPixelMap();
    
    const notificationRequest: notificationManager.NotificationRequest = {
      id: 2,
      slotId: "slot_001",
      largeIcon: imagePixelMap,
      content: {
        notificationContentType: notificationManager.ContentType.NOTIFICATION_CONTENT_PICTURE,
        picture: {
          title: "圖片通知",
          text: "這是一張圖片通知",
          bigPicture: imagePixelMap
        }
      }
    };
    
    await notificationManager.publish(notificationRequest);
    console.info('圖片通知發佈成功');
  } catch (error) {
    console.error('發佈圖片通知失敗:', error);
  }
}

5.4 發佈進度條通知

import { notificationManager } from '@kit.NotificationKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { request } from '@kit.NetworkKit';

// 發佈進度條通知
async function publishProgressNotification(): Promise<void> {
  try {
    const notificationRequest: notificationManager.NotificationRequest = {
      id: 3,
      slotId: "slot_001",
      content: {
        notificationContentType: notificationManager.ContentType.NOTIFICATION_CONTENT_BASIC_TEXT,
        normal: {
          title: "文件下載",
          text: "正在下載文件..."
        }
      },
      template: {
        name: 'downloadTemplate',
        data: {
          title: 'PDF文件下載',
          fileName: 'test.pdf',
          progressValue: 0
        }
      }
    };
    
    // 發佈初始通知
    await notificationManager.publish(notificationRequest);
    
    // 模擬下載進度更新
    let progress = 0;
    const interval = setInterval(async () => {
      progress += 10;
      
      if (progress > 100) {
        clearInterval(interval);
        return;
      }
      
      // 更新通知
      notificationRequest.template!.data!.progressValue = progress;
      notificationRequest.template!.data!.fileName = `test.pdf 下載進度: ${progress}%`;
      
      await notificationManager.publish(notificationRequest);
    }, 1000);
    
  } catch (error) {
    console.error('發佈進度通知失敗:', error);
  }
}

5.5 帶操作按鈕的通知

import { notificationManager } from '@kit.NotificationKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { WantAgent } from '@kit.AbilityKit';

// 發佈帶操作按鈕的通知
async function publishActionNotification(): Promise<void> {
  try {
    // 創建WantAgent(點擊通知後要觸發的意圖)
    const wantAgentInfo: WantAgent.WantAgentInfo = {
      wants: [
        {
          bundleName: 'com.example.myapp',
          abilityName: 'MainAbility'
        }
      ],
      actionType: WantAgent.OperationType.START_ABILITIES,
      requestCode: 0
    };
    
    const wantAgent = await WantAgent.getWantAgent(wantAgentInfo);
    
    const notificationRequest: notificationManager.NotificationRequest = {
      id: 4,
      slotId: "slot_001",
      content: {
        notificationContentType: notificationManager.ContentType.NOTIFICATION_CONTENT_BASIC_TEXT,
        normal: {
          title: "帶操作的通知",
          text: "點擊查看詳情或執行操作"
        }
      },
      wantAgent: wantAgent,
      actionButtons: [
        {
          title: '打開應用',
          wantAgent: wantAgent
        },
        {
          title: '取消',
          wantAgent: {
            wants: [
              {
                action: 'com.example.CANCEL_ACTION'
              }
            ],
            actionType: WantAgent.OperationType.SEND_COMMON_EVENT
          }
        }
      ]
    };
    
    await notificationManager.publish(notificationRequest);
    console.info('帶操作按鈕的通知發佈成功');
  } catch (error) {
    console.error('發佈帶操作通知失敗:', error);
  }
}

5.6 取消通知

import { notificationManager } from '@kit.NotificationKit';
import { BusinessError } from '@kit.BasicServicesKit';

// 取消指定通知
async function cancelNotification(): Promise<void> {
  try {
    await notificationManager.cancelNotification(1);
    console.info('通知取消成功');
  } catch (error) {
    console.error('取消通知失敗:', error);
  }
}

// 取消所有通知
async function cancelAllNotifications(): Promise<void> {
  try {
    await notificationManager.cancelAllNotifications();
    console.info('所有通知取消成功');
  } catch (error) {
    console.error('取消所有通知失敗:', error);
  }
}

5.7 權限申請

在發佈通知前,需要申請通知權限:

import { notificationManager } from '@kit.NotificationKit';
import { BusinessError } from '@kit.BasicServicesKit';

// 請求通知權限
async function requestNotificationPermission(): Promise<void> {
  try {
    await notificationManager.requestEnableNotification();
    console.info('通知權限申請成功');
  } catch (error) {
    if (error.code === 1600004) {
      console.info('用户拒絕了通知權限');
    } else {
      console.error('請求通知權限失敗:', error);
    }
  }
}

六、綜合實戰:跨設備文件傳輸

6.1 場景描述

用户在手機上啓動文件發送操作,將大文件發送到同一賬號下的平板電腦。平板上的文件傳輸應用在後台訂閲自定義公共事件,當文件傳輸完成後,手機應用發佈公共事件,平板應用接收事件並處理文件,最後通過通知告知用户。

6.2 代碼實現

手機端(發送方)
import commonEvent from '@ohos.commonEventManager';
import { notificationManager } from '@kit.NotificationKit';
import { BusinessError } from '@kit.BasicServicesKit';

// 文件傳輸完成後發佈公共事件
async function publishFileTransferComplete(filePath: string): Promise<void> {
  try {
    const options: commonEvent.CommonEventPublishData = {
      code: 200,
      data: JSON.stringify({
        filePath: filePath,
        fileName: "test.pdf",
        fileSize: "10MB",
        transferTime: new Date().toISOString()
      }),
      isOrdered: true
    };
    
    await commonEvent.publish("com.example.FILE_TRANSFER_COMPLETE", options);
    console.info('文件傳輸完成事件發佈成功');
    
    // 發送本地通知
    await publishTransferCompleteNotification();
  } catch (error) {
    console.error('發佈文件傳輸事件失敗:', error);
  }
}

// 發佈傳輸完成通知
async function publishTransferCompleteNotification(): Promise<void> {
  try {
    const notificationRequest: notificationManager.NotificationRequest = {
      id: 1001,
      slotId: "slot_transfer",
      content: {
        notificationContentType: notificationManager.ContentType.NOTIFICATION_CONTENT_BASIC_TEXT,
        normal: {
          title: "文件傳輸完成",
          text: "文件已成功發送到平板設備"
        }
      }
    };
    
    await notificationManager.publish(notificationRequest);
  } catch (error) {
    console.error('發佈通知失敗:', error);
  }
}
平板端(接收方)
import commonEvent from '@ohos.commonEventManager';
import { notificationManager } from '@kit.NotificationKit';
import { BusinessError } from '@kit.BasicServicesKit';

// 訂閲文件傳輸完成事件
async function subscribeFileTransferEvent(): Promise<void> {
  try {
    const subscribeInfo = {
      events: ["com.example.FILE_TRANSFER_COMPLETE"]
    };
    
    const subscriber = await commonEvent.createSubscriber(subscribeInfo);
    
    commonEvent.subscribe(subscriber, async (err: BusinessError, data: commonEvent.CommonEventData) => {
      if (err) {
        console.error(`訂閲失敗: ${err.code}, ${err.message}`);
        return;
      }
      
      // 解析事件數據
      const eventData = JSON.parse(data.data as string);
      console.info('收到文件傳輸完成事件:', eventData);
      
      // 處理文件
      await handleFileTransfer(eventData);
    });
  } catch (error) {
    console.error('創建訂閲者失敗:', error);
  }
}

// 處理文件傳輸
async function handleFileTransfer(eventData: any): Promise<void> {
  try {
    // 模擬文件處理邏輯
    console.info(`開始處理文件: ${eventData.fileName}`);
    
    // 模擬文件處理耗時
    await new Promise(resolve => setTimeout(resolve, 2000));
    
    console.info('文件處理完成');
    
    // 發送處理完成通知
    await publishFileProcessedNotification(eventData.fileName);
  } catch (error) {
    console.error('處理文件失敗:', error);
  }
}

// 發佈文件處理完成通知
async function publishFileProcessedNotification(fileName: string): Promise<void> {
  try {
    const notificationRequest: notificationManager.NotificationRequest = {
      id: 2001,
      slotId: "slot_transfer",
      content: {
        notificationContentType: notificationManager.ContentType.NOTIFICATION_CONTENT_BASIC_TEXT,
        normal: {
          title: "文件接收完成",
          text: `${fileName} 已保存至下載文件夾`
        }
      }
    };
    
    await notificationManager.publish(notificationRequest);
    console.info('文件處理完成通知發佈成功');
  } catch (error) {
    console.error('發佈通知失敗:', error);
  }
}

七、調試與優化

7.1 調試工具

HarmonyOS提供了調試助手工具,幫助開發者調試公共事件和通知:

  • CEM調試助手:用於調試公共事件
  • ANM調試助手:用於調試通知

7.2 性能優化建議

  1. 合理使用公共事件:避免頻繁發佈不必要的事件,減少系統負擔
  2. 及時退訂事件:在不需要時及時退訂公共事件,避免內存泄漏
  3. 優化通知頻率:避免過於頻繁的通知,影響用户體驗
  4. 使用適當的重要級別:根據通知內容的重要性選擇合適的通知級別
  5. 處理權限拒絕:優雅處理用户拒絕通知權限的情況

八、總結與最佳實踐

8.1 核心要點回顧

  1. 公共事件機制:實現應用間後台通信,支持系統事件和自定義事件
  2. 通知機制:向用户提供可視化的消息提醒,支持多種樣式和交互
  3. 跨設備協同:通過公共事件實現設備間的數據同步和狀態感知
  4. 權限管理:合理申請和使用所需權限,確保應用正常運行

8.2 最佳實踐

  1. 場景選擇: 需要後台通信時使用公共事件 需要用户交互時使用通知 需要跨設備協同時結合使用兩者
  2. 權限申請: 在module.json5中聲明所需權限 運行時動態請求用户授權 優雅處理權限拒絕的情況
  3. 性能優化: 避免頻繁發佈事件和通知 及時清理不再需要的訂閲和通知 使用適當的事件類型和通知級別
  4. 用户體驗: 提供清晰的通知內容 支持通知操作按鈕 合理控制通知頻率

通過合理運用公共事件與通知機制,可以構建出響應迅速、體驗流暢的HarmonyOS應用,實現真正的跨設備協同體驗。