# 通知订阅扩展能力开发步骤

#### 接口说明

| 接口名 | 描述 |
| --- | --- |
| onDestroy(): void | 通知订阅扩展被销毁时的回调。 |
| onReceiveMessage(notificationInfo: NotificationInfo): void | 收到通知时的回调。 |
| onCancelMessages(hashCodes: Array): void | 取消通知时的回调。 |

#### 前提条件

申请ohos.permission.SUBSCRIBE\_NOTIFICATION权限。

#### 开发步骤

开发者在实现NotificationSubscriberExtensionAbility提供方时，需在DevEco Studio工程中新建一个NotificationSubscriberExtensionAbility。具体步骤如下。

1.  在entry/src/main/ets/创建目录extensionability。
    
2.  在entry/src/main/ets/extensionability目录下创建NotificationSubscriberExtAbility.ets，其内容如下。
    
    ```
    import { hilog } from '@kit.PerformanceAnalysisKit';
    import { notificationExtensionSubscription, NotificationSubscriberExtensionAbility } from '@kit.NotificationKit';
    // ...
    
    const DOMAIN = 0x0000;
    // ...
    export class NotificationSubscriberExtAbility extends NotificationSubscriberExtensionAbility {
      // ...
      onDestroy(): void {
        hilog.info(DOMAIN, 'testTag', 'onDestroy');
        // ...
      }
      // ...
      onReceiveMessage(notificationInfo: notificationExtensionSubscription.NotificationInfo): void {
        hilog.info(DOMAIN, 'testTag', `on receive message ${JSON.stringify(notificationInfo)}`)
        // ...
      }
      // ...
      onCancelMessages(hashCodes: Array<string>): void {
        hilog.info(DOMAIN, 'testTag', `on cancel message ${JSON.stringify(hashCodes)}`)
        // ...
      }
      // ...
    }
    ```
    
3.  使用蓝牙模块接口与穿戴设备配对（蓝牙处于配对状态）并获取地址，然后通过subscribe/unsubscribe接口订阅或取消订阅通知。
    
4.  实现NotificationSubscriberExtensionAbility后，还需要在合适的时机调用openSubscriptionSettingsWithResult接口，打开通知扩展订阅设置页面，引导用户授予获取本机通知的权限，该页面以半模态弹窗显示，并在弹窗关闭时返回授权的结果。建议在设备管理页面提供一个通知授权的按钮，用户点击按钮则调用openSubscriptionSettingsWithResult接口。
    
5.  在应用的module.json5文件中配置extensionAbilities。
    
    ```
    {
      "name": "NotificationSubscriberExtAbility",
      "srcEntry": "./ets/extensionability/NotificationSubscriberExtAbility.ets",
      "type": "notificationSubscriber",
      "description": "$string:NotificationSubscriberExtAbility_desc",
      "icon": "$media:layered_image",
      "label": "$string:NotificationSubscriberExtAbility_label",
      "exported": true
    }
    ```
    
6.  在应用的string.json文件中添加
    
    ```
      {
        "name": "NotificationSubscriberExtAbility_desc",
        "value": "description"
      },
      {
        "name": "NotificationSubscriberExtAbility_label",
        "value": "ThirdPartyWearableApp"
      }
    ```
    
7.  示例仅为传统蓝牙连接示例，开发者也可选用低功耗蓝牙连接方式。
    
8.  用户收到消息后，假如蓝牙连接是无效的，则建立蓝牙连接。
    
9.  假如蓝牙连接已经存在，则直接使用这个连接发送消息。
    
10.  如果使用该连接发送消息失败，则重新建立连接，如果连接能建立成功则发送消息。
     
11.  需要申请权限ohos.permission.ACCESS\_BLUETOOTH。如何配置和申请权限，具体操作请参考声明权限和向用户申请授权。
     
     ```
     import { hilog } from '@kit.PerformanceAnalysisKit';
     import { notificationExtensionSubscription, NotificationSubscriberExtensionAbility } from '@kit.NotificationKit';
     import { BusinessError } from '@kit.BasicServicesKit';
     import { socket } from '@kit.ConnectivityKit'
     import { util } from '@kit.ArkTS';
     
     const DOMAIN = 0x0000;
     class TransferInfo {
       public type: string = ''
       public info: notificationExtensionSubscription.NotificationInfo | undefined
       public cancelHashCodes: Array<string> | undefined
     }
     // Spp means Serial Port Profile
     class SppClientManager {
       private clientNumber: number = -1;
       private peerDevice: string = '';
     
       constructor(peerDevice: string) {
         this.peerDevice = peerDevice
       }
     
       public isConnect(): boolean {
         return this.clientNumber !== -1;
       }
     
       public async startConnect(): Promise<boolean> {
         let option: socket.SppOptions = {
           uuid: '00009999-0000-1000-8000-00805F9B34FB',
           secure: false,
           type: socket.SppType.SPP_RFCOMM
         };
         socket.sppConnect(this.peerDevice, option, (err: BusinessError, num: number) => {
           if (err) {
             hilog.error(DOMAIN, 'testTag', `cpp connect failed, errCode: ${err.code}, errMessage: ${err.message}`);
           } else {
             hilog.info(DOMAIN, 'testTag', `spp connect success clientNumber: ${num}`);
             this.clientNumber = num;
           }
         });
         return true
       }
     
       private sendData(jsonStr: string) {
         if (!this.isConnect()) {
           hilog.error(DOMAIN, 'testTag', `server is not connected`);
           return;
         }
         if (!jsonStr) {
           hilog.error(DOMAIN, 'testTag', 'json is empty');
           return;
         }
         hilog.info(DOMAIN, 'testTag', `prepare sending data to client ${this.clientNumber}`);
         const textEncoder:util.TextEncoder = new util.TextEncoder();
         const uint8Array: Uint8Array = textEncoder.encodeInto(jsonStr);
         const arrayBuffer = uint8Array.buffer;
     
         socket.sppWrite(this.clientNumber, arrayBuffer);
         hilog.info(DOMAIN, 'testTag', `sending success size: ${arrayBuffer.byteLength} bytes, data: ${jsonStr}`);
       }
     
       public sendNotificationData(notificationInfo: notificationExtensionSubscription.NotificationInfo) {
         let info: TransferInfo = {
           type: 'publish',
           info: notificationInfo,
           cancelHashCodes: undefined
         };
     
         let jsonStr = JSON.stringify(info);
         this.sendData(jsonStr);
       }
     
       public sendCancelNotificationData(cancelHashCodes: Array<string>) {
         let info: TransferInfo = {
           type: 'cancel',
           cancelHashCodes: cancelHashCodes,
           info: undefined
         };
     
         let jsonStr = JSON.stringify(info);
         this.sendData(jsonStr);
       }
     
       public read = (dataBuffer: ArrayBuffer) => {
         let data = new Uint8Array(dataBuffer);
         hilog.info(DOMAIN, 'testTag', `client data: ${JSON.stringify(data)}`);
       };
     
       public stopConnect() {
         hilog.info(DOMAIN, 'testTag', `closeSppClient ${this.clientNumber}`);
         try {
           socket.off('sppRead', this.clientNumber, this.read);
         } catch (err) {
           hilog.error(DOMAIN, 'testTag', `off sppRead errCode: ${err.code}, errMessage: ${err.message}`);
         }
         try {
           socket.sppCloseClientSocket(this.clientNumber);
           this.clientNumber = -1;
         } catch (err) {
           hilog.error(DOMAIN, 'testTag', `stopConnect errCode: ${err.code}, errMessage: ${err.message}`);
         }
       }
     }
     
     // export SppClientManager;
     export class NotificationSubscriberExtAbility extends NotificationSubscriberExtensionAbility {
       private sppClientManager: SppClientManager | undefined;
       onDestroy(): void {
         hilog.info(DOMAIN, 'testTag', 'onDestroy');
         this.sppClientManager!.stopConnect();
       }
       // Called back when a notification is published.
       onReceiveMessage(notificationInfo: notificationExtensionSubscription.NotificationInfo): void {
         hilog.info(DOMAIN, 'testTag', `on receive message ${JSON.stringify(notificationInfo)}`)
         notificationExtensionSubscription.getSubscribeInfo()
           .then(async (info) => {
             if (this.sppClientManager == undefined) {
               this.sppClientManager = new SppClientManager(info[0].addr);
             }
             if (this.sppClientManager.isConnect()) {
               this.sendPublishWithRetry(notificationInfo);
             } else {
               try {
                 await this.sppClientManager.startConnect().then(() => {
                   hilog.info(DOMAIN, 'testTag', `startConnect success`);
                 });
               } catch (err) {
                 hilog.error(DOMAIN, 'testTag', `Failed to start connect: ${JSON.stringify(err)}`);
               }
               setTimeout(() => {
                 this.sendPublishWithRetry(notificationInfo);
               }, 3000)
             }
           }).catch((err: BusinessError) => {
           hilog.error(DOMAIN, 'testTag',
             `notificationExtensionSubscription failed, errCode ${err.code}, errorMessage ${err.message}`);
         });
       }
       // Sends a publish notification and retries once upon failure.
       private async sendPublishWithRetry(notificationInfo: notificationExtensionSubscription.NotificationInfo) {
         try {
           this.sppClientManager!.sendNotificationData(notificationInfo);
         } catch (err) {
           hilog.error(DOMAIN, 'testTag', `send failed, errCode ${err.code}, errorMessage ${err.message}, and retry one times`);
           try {
             await this.sppClientManager!.startConnect().then(() => {
               hilog.info(DOMAIN, 'testTag', `startConnect success`);
             });
           } catch (err) {
             hilog.error(DOMAIN, 'testTag', `Failed to start connect: ${JSON.stringify(err)}`);
           }
           setTimeout(() => {
             this.sppClientManager!.sendNotificationData(notificationInfo);
           }, 3000);
         }
       }
     
       // Called back when notifications are cancelled.
       onCancelMessages(hashCodes: Array<string>): void {
         hilog.info(DOMAIN, 'testTag', `on cancel message ${JSON.stringify(hashCodes)}`)
         notificationExtensionSubscription.getSubscribeInfo()
           .then(async (info) => {
             if (this.sppClientManager == undefined) {
               this.sppClientManager = new SppClientManager(info[0].addr);
             }
             if (this.sppClientManager.isConnect()) {
               this.sendCancelWithRetry(hashCodes);
             } else {
               try {
                 await this.sppClientManager.startConnect().then(() => {
                   hilog.info(DOMAIN, 'testTag', `startConnect success`);
                 });
               } catch (err) {
                 hilog.error(DOMAIN, 'testTag', `Failed to start connect: ${JSON.stringify(err)}`);
               }
               setTimeout(() => {
                 this.sendCancelWithRetry(hashCodes);
               }, 3000)
             }
           }).catch((err: BusinessError) => {
           hilog.error(DOMAIN, 'testTag', `notificationExtensionSubscription failed, errCode ${err.code}, errorMessage ${err.message}`);
         });
       }
       // Retries a cancel operation if it fails.
       private async sendCancelWithRetry(hashCodes: string[]) {
         try {
           this.sppClientManager!.sendCancelNotificationData(hashCodes);
         } catch (err) {
           hilog.error(DOMAIN, 'testTag', `send failed, errCode ${err.code}, errorMessage ${err.message}, and retry one times`);
           try {
             await this.sppClientManager!.startConnect().then(() => {
               hilog.info(DOMAIN, 'testTag', `startConnect success`);
             });
           } catch (err) {
             hilog.error(DOMAIN, 'testTag', `Failed to start connect: ${JSON.stringify(err)}`);
           }
           setTimeout(() => {
             this.sppClientManager!.sendCancelNotificationData(hashCodes);
           }, 3000);
         }
       }
     }
     ```
     

注意：请勿频繁建立连接，可能会影响功能。