HarmonyOS 6实战Notification Kit 实现自定义消息通知一、推送服务设计原则二、Notification Kit实战—— 实现通知角标2.1 请求通知权限2.2 角标管理最关键的部分2.3 通知发布2.4 通知内容类型2.5 通知点击跳转2.6 UI组件发送通知三、云侧推送Push Kit3.1 获取Push Token3.2 服务端推送消息3.3 通知角标云侧3.4 点击消息跳转配置总结之前我们已经做了一个AI旅行助手用户可以在上面分享自己的行程卡片我之前设想的是消息通知应该成为连接用户社交互动与行程提示的关键入口。比如“用户XXX查看了您的分享”这类提醒一直还没来得及做。最近正好有点时间我们把这个功能完善一下。这个功能本身并不复杂但涉及通知权限、角标管理、消息类型定义、云端推送等一系列环节。接下来我将完整记录整个开发过程。先说说最终效果。用户收到新消息时系统会推送一条通知同时桌面图标右上角显示一个数字角标。角标数字等于未读消息的数量。用户在通知栏点击通知可以跳转到对应的页面。角标数字也会同步减少。整个功能分为三块通知发布发送各种类型的通知角标管理统计未读数量并更新桌面角标通知列表展示所有收到的通知一、推送服务设计原则Notification Kit能做什么鸿蒙的Notification Kit提供了完整的本地通知能力主要包括能力说明发布通知支持文本、进度条等多种类型角标管理携带或更新应用通知数字角标通知取消取消某条或全部通知通知查询查询已发布的通知列表权限管理请求用户授权发布通知一些约束限制单个应用在通知中心最多留存24条通知通知长度不能超过200KB发布频次单应用每秒不超过10条所有应用合计每秒不超过15条在设计通知时有几个官方推荐的原则值得注意内容要有价值通知应该提供有用的信息而不是骚扰用户。比如“你的游记收到一条新评论”比“快来查看最新内容”更有价值。不要重复发送同样的内容不要反复推送用户会烦。采用规范布局不允许自定义复杂布局保证通知体验的一致性。鸿蒙支持的通知样式包括普通文本、多行文本、图片预览、通讯对话等。不要当广告板用通知不是小工具或广告板不要出于商业目的强制改变属性。通知会在不同场景以不同形式提示用户场景说明通知中心所有通知的浏览界面按类别和时间排序锁屏通知仅显示本次锁屏期间接收的通知可配置是否隐藏内容横幅通知界面顶部显示5秒后消失非沉浸态显示3行沉浸态显示1行桌面图标角标数字角标最多显示“99”不完全对应通知数量状态栏图标以图标形式显示在状态栏取应用图标角标的特殊说明桌面图标角标是应用新消息的提示但不完全对应通知中心的通知数量。角标由服务方自己定义打开应用或清理通知并不会自动清理角标需要通过setBadgeNumber(0)手动清理。二、Notification Kit实战—— 实现通知角标2.1 请求通知权限应用需要先请求通知授权用户同意后才能发送通知。// entry/src/main/ets/entryability/EntryAbility.etsimport{notificationManager}fromkit.NotificationKitexportdefaultclassEntryAbilityextendsUIAbility{onWindowStageCreate(windowStage:window.WindowStage){// 请求通知权限notificationManager.requestEnableNotification(this.context).then((){console.info(requestEnableNotification success)}).catch((err:BusinessError){console.error(requestEnableNotification failed, code is${err.code})})}}2.2 角标管理最关键的部分角标管理的核心是记录当前有多少条未读通知然后调用系统API更新桌面角标。// notification/src/main/ets/notification/NotificationManagementUtil.etsimport{notificationManager}fromkit.NotificationKitclassNotificationManagementUtil{// 按类型存储通知typeNotifications:ArrayArraynotificationManager.NotificationRequest[]// 按类型统计数量countsByType:Arraynumber[]// 当前角标总数badgeNum:number0constructor(){// 初始化时获取所有活动通知恢复角标状态notificationManager.getActiveNotifications().then((notifications){for(leti0;inotifications.length;i){lettypeIdnotifications[i].content.notificationContentTypethis.countsByType[typeIdasnumber]1this.typeNotifications[typeIdasnumber].push(notifications[i])}this.countsByType.forEach((num:number){this.badgeNumnum})})}// 【核心方法】设置角标数量asyncsetBadgeNumber(num:number){notificationManager.setBadgeNumber(num).then((){this.badgeNumnum}).catch((error:BusinessError){console.error(setBadgeNumber failed: code is${error.code})})}// 添加新通知并更新角标asyncaddNotification(notification:notificationManager.NotificationRequest){consttypeIdnotification.content.notificationContentTypethis.typeNotifications[typeId].push(notification)this.countsByType[typeId]1this.badgeNum1awaitthis.setBadgeNumber(this.badgeNum)}}exportletnotificationManagementnewNotificationManagementUtil()关键点setBadgeNumber是系统API直接设置桌面角标数字应用启动时调用getActiveNotifications恢复之前的角标状态每次添加通知角标1取消通知角标-12.3 通知发布发布通知分两步构建通知内容 → 调用系统API发布。// notification/src/main/ets/notification/NotificationUtil.etsimport{notificationManager}fromkit.NotificationKitimport{notificationManagement}from./NotificationManagementUtilclassNotificationUtil{privateid:number0// 发布通知asyncpublishNotification(notificationRequest:notificationManager.NotificationRequest){notificationRequest.idthis.idthis.idtry{// 1. 发布到系统awaitnotificationManager.publish(notificationRequest)// 2. 更新角标awaitnotificationManagement.addNotification(notificationRequest)}catch(err){console.error(publishNotification err${JSON.stringify(err)})}}}exportletnotificationUtilnewNotificationUtil()2.4 通知内容类型鸿蒙支持多种通知样式我们逐一实现基础文本通知// notification/src/main/ets/notification/NotificationContentUtil.etsclassNotificationContentUtil{// 基础文本通知initBasicNotificationContent(basicContent){return{notificationContentType:notificationManager.ContentType.NOTIFICATION_CONTENT_BASIC_TEXT,normal:basicContent}}// 长文本通知可展开initNotificationLongTextContent(basicContent,longText,briefText,expandedTitle){return{notificationContentType:notificationManager.ContentType.NOTIFICATION_CONTENT_LONG_TEXT,longText:{title:basicContent.title,text:basicContent.text,longText:longText,briefText:briefText,expandedTitle:expandedTitle}}}// 多行文本通知initNotificationMultiLineContent(basicContent,briefText,longTitle,lines){return{notificationContentType:notificationManager.ContentType.NOTIFICATION_CONTENT_MULTILINE,multiLine:{title:basicContent.title,text:basicContent.text,briefText:briefText,longTitle:longTitle,lines:lines}}}// 图片通知initNotificationPictureContent(basicContent,briefText,expandedTitle,picture){return{notificationContentType:notificationManager.ContentType.NOTIFICATION_CONTENT_PICTURE,picture:{title:basicContent.title,text:basicContent.text,briefText:briefText,expandedTitle:expandedTitle,picture:picture}}}}2.5 通知点击跳转用户点击通知后跳转到指定页面需要用到WantAgent。// notification/src/main/ets/notification/WantAgentUtil.etsimport{wantAgent}fromkit.AbilityKitclassWantAgentUtil{asynccreateWantAgentForStartAbility(bundleName:string,abilityName:string){letwantAgentInfo{wants:[{bundleName:bundleName,abilityName:abilityName}],actionType:wantAgent.OperationType.START_ABILITY,requestCode:0}returnawaitwantAgent.getWantAgent(wantAgentInfo)}}exportletwantAgentUtilnewWantAgentUtil()2.6 UI组件发送通知// entry/src/main/ets/components/NotificationPublish.etsimport{notificationUtil,notificationContentUtil,notificationRequestUtil}fromnotificationimport{wantAgentUtil}fromnotificationComponentexportstruct NoticePublish{privatecontextthis.getUIContext().getHostContext()!aboutToAppear(){// 请求通知权限notificationManager.requestEnableNotification(this.context)}build(){Column({space:16}){Button(基础通知).onClick(()this.publishBasicNotification())Button(长文本通知).onClick(()this.publishLongTextNotification())Button(图片通知).onClick(()this.publishPictureNotification())Button(带跳转的通知).onClick(()this.publishWantAgentNotification())}.padding(16).width(100%)}asyncpublishBasicNotification(){letbasicContent{title:基础通知,text:这是一条基础通知,additionalText:附加信息}letcontentnotificationContentUtil.initBasicNotificationContent(basicContent)letrequestnotificationRequestUtil.initBasicNotificationRequest(content)notificationUtil.publishNotification(request)}asyncpublishWantAgentNotification(){letbasicContent{title:可跳转的通知,text:点击跳转到应用首页,additionalText:}letwantAgentawaitwantAgentUtil.createWantAgentForStartAbility(this.context.applicationInfo.name,EntryAbility)letcontentnotificationContentUtil.initBasicNotificationContent(basicContent)letrequestnotificationRequestUtil.initWantAgentNotificationRequest(content,wantAgent)notificationUtil.publishNotification(request)}}三、云侧推送Push Kit本地通知只能在应用运行时发送。如果应用被杀死用户就收不到消息了。这就需要接入Push Kit做云侧推送。3.1 获取Push Tokenimport{pushService}fromkit.PushKit// 获取Push TokenpushService.getToken().then((token){console.info(Get push token success: token)// 上报token到自己的服务端}).catch((err:BusinessError){console.error(Get push token failed, code is${err.code})})3.2 服务端推送消息应用服务端调用Push Kit的REST API推送通知消息POSThttps://push-api.cloud.huawei.com/v3/[projectId]/messages:sendContent-Type:application/jsonAuthorization:Bearer eyJr*****...{payload:{notification:{category:MARKETING,title:普通通知标题,body:普通通知内容,clickAction:{actionType:0}}},target:{token:[MAMzLg**********lPW]},pushOptions:{testMessage:true}}3.3 通知角标云侧Push Kit也支持角标设置通过badge字段{payload:{notification:{title:通知标题,body:通知内容,badge:{addNum:1,// 角标累加数字setNum:99// 角标实际显示数字优先级高于addNum}}}}注意打开应用或点击通知并不会自动清理角标需要调用setBadgeNumber(0)手动清理。3.4 点击消息跳转配置点击消息进入应用内页需要在module.json5中配置skills标签{ abilities: [{ name: TestAbility, skills: [ // 首页skill不要配置uris { entities: [entity.system.home], actions: [ohos.want.action.home] }, // 新增skill用于消息跳转 { actions: [com.test.action] } ] }] }发送消息时设置actionType为1{clickAction:{actionType:1,action:com.test.action}}总结通知角标的核心实现要点要点实现方式权限请求notificationManager.requestEnableNotification()角标设置notificationManager.setBadgeNumber(num)通知发布notificationManager.publish(request)角标同步发布时1取消时-1状态恢复启动时调用getActiveNotifications点击跳转WantAgent封装跳转意图云侧推送Push Kit REST API这里补充一下如果在测试场景下用户看完通知后角标数字没减少。原因大概率是只取消了系统通知没更新本地统计的角标数量。解决方案是在取消通知的回调里同步调用setBadgeNumber。除此之外需要注意应用杀死后重新打开之前设置的角标数字没了。因为角标状态没有持久化。解决方案是在初始化时调用getActiveNotifications恢复角标。单应用每秒不能超过10条调试时注意控制频率。测试消息可以设置testMessage: true绕过频控。