在HarmonyOS NEXT 6.1.1(API 24)中,Notification Kit新增了两项实用特性:支持应用将非预置音频文件作为通知铃声(需存放在EL1沙箱files目录),以及开放了getNotificationParameters接口用于逆向检索已发布通知携带的wantAgent参数。本文通过一个黑金风格的控制舱Demo,详细介绍这两项特性的实现原理、核心API使用方式以及常见踩坑点。
### 一、沙箱自定义铃声:安全边界与uri::协议
在API 12时代,自定义铃声只能将音频文件硬编码在resources/rawfile目录下。对于需要根据用户动态选择或下载铃声的场景(如协同办公自定义联系人提示音、音乐APP录制音频设为日程提醒),这种方式不够灵活。6.1.1彻底放通了非预置音频文件作为通知铃声的链路,但为了安全防越权,系统做了严格的物理隔离约束:
- 音频文件必须放在应用沙箱的EL1(设备级加密区)的files目录或其子目录下。EL1目录在设备启动时即解密,即使锁屏状态下系统通知服务也能正常读取。若放在EL2(用户级加密区),锁屏时系统会因无法解密导致播放失败或回退为默认提示音。
- 在发布通知时,NotificationRequest的sound字段必须以“uri::”作为协议前缀,即:let soundFile = 'uri::' + fileUri.getUriFromPath(sandboxFilePath);
### 二、wantAgent时态检索:getNotificationParameters实战
现代鸿蒙应用频繁使用wantAgent承载通知点击后的跳转意图。6.1.1开放了notificationManager.getNotificationParameters(id, label?)方法,允许Stage模型下的主应用获取当前处于Active状态的通知所持有的wantAgent快照信息(如目标bundleName、abilityName、flags等)。
注意事项:
- 该API是时态检索,仅当通知还在通知栏中(锁屏、悬挂或下拉通知中心)时可查询。若用户手动清除或应用主动cancel该通知,调用时会抛出错误码1600007(The notification does not exist)。
- 实际开发中需要搭配BusinessError捕获,并在底层模块未就绪时进行降级处理(如使用仿真数据)。
### 三、核心代码实现
以下为Demo中主页面NotificationKitEnhanceDetail.ets的关键片段,展示了沙箱文件拷贝、铃声URI组装、通知发布以及参数检索的完整流程。
- import { router } from '@kit.ArkUI';
- import { hilog } from '@kit.PerformanceAnalysisKit';
- import { BusinessError } from '@kit.BasicServicesKit';
- import { notificationManager } from '@kit.NotificationKit';
- import { contextConstant, common } from '@kit.AbilityKit';
- import { fileIo as fs, fileUri } from '@kit.CoreFileKit';
- const TAG = 'NotificationKitEnhanceDetail';
- const SOUND_FILE_NAME = 'ringtone_demo.mp3';
- // 准备沙箱铃声:必须放在EL1 files目录下
- private prepareSandboxRingtone() {
- try {
- const context = getContext(this) as common.UIAbilityContext;
- const applicationContext = context.getApplicationContext();
- applicationContext.area = contextConstant.AreaMode.EL1;
- const sandboxDir = applicationContext.filesDir;
- const sandboxFilePath = sandboxDir + '/' + SOUND_FILE_NAME;
-
- // 读取rawfile资源并写入沙箱(若资源不存在则写入模拟MP3头部)
- let fileData: Uint8Array;
- try {
- fileData = context.resourceManager.getRawFileContentSync(SOUND_FILE_NAME);
- } catch (err) {
- fileData = new Uint8Array([0x49, 0x44, 0x33, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]);
- }
- if (!fs.accessSync(sandboxDir)) {
- fs.mkdirSync(sandboxDir, true);
- }
- const file = fs.openSync(sandboxFilePath, fs.OpenMode.CREATE | fs.OpenMode.WRITE_ONLY | fs.OpenMode.TRUNC);
- fs.writeSync(file.fd, fileData.buffer);
- fs.closeSync(file);
-
- // 获取FileUri并组装为uri::协议格式
- let rawUri = fileUri.getUriFromPath(sandboxFilePath);
- this.sandboxFileUriStr = 'uri::' + rawUri;
- } catch (error) {
- const err = error as BusinessError;
- // 错误处理
- }
- }
- // 发布通知:根据selectedRingtoneMode选择不同铃声源
- publishCustomNotification() {
- let soundFile = '';
- if (this.selectedRingtoneMode === 0) {
- soundFile = ''; // 系统默认
- } else if (this.selectedRingtoneMode === 1) {
- soundFile = SOUND_FILE_NAME; // rawfile内置
- } else {
- soundFile = this.sandboxFileUriStr; // API 24沙箱
- }
-
- let notificationRequest: notificationManager.NotificationRequest = {
- id: this.currentNotificationId,
- label: this.customNotificationLabel,
- notificationSlotType: notificationManager.SlotType.SOCIAL_COMMUNICATION,
- content: {
- notificationContentType: notificationManager.ContentType.NOTIFICATION_CONTENT_BASIC_TEXT,
- normal: {
- title: '安全总线指令',
- text: `通知铃声已切换,当前校验模式: ${this.selectedRingtoneMode === 2 ? '沙箱文件' : '常规模式'}`,
- additionalText: 'API 24 通知特性测试'
- }
- },
- sound: soundFile,
- wantAgent: { toString: () => 'wantAgent::AllKitSocialIntentHolder' }
- };
-
- notificationManager.publish(notificationRequest).then(() => {
- // 成功处理
- }).catch((err: BusinessError) => {
- // 失败处理
- });
- }
- // 检索wantAgent参数
- queryWantAgentParameters() {
- setTimeout(() => {
- notificationManager.getNotificationParameters(this.currentNotificationId, this.customNotificationLabel)
- .then((data: notificationManager.NotificationParameters) => {
- // 将data中的wantAgent信息转为JSON显示
- this.queryResultJson = JSON.stringify(data, null, 2);
- })
- .catch((err: BusinessError) => {
- // 若底层模块未就绪,使用仿真数据
- this.queryResultJson = JSON.stringify({
- bundleName: 'com.example.harmonyos.allkit',
- abilityName: 'EntryAbility',
- flags: 32,
- extraInfo: '【离线仿真】wantAgent 意图拦截测试'
- }, null, 2);
- });
- }, 400);
- }
复制代码
### 四、开发避坑指南
1. **EL1目录路径获取**:务必使用applicationContext.filesDir,且先设置area为EL1。如果误用其他目录(如databaseDir),锁屏下播放会失败。
2. **uri::前缀必须严格小写**:写成URI::或uri:都会导致系统无法识别。
3. **getNotificationParameters的时态性**:建议在用户点击查看通知或应用后台轮询时调用,并做好错误捕获(catch 1600007)。
4. **文件资源降级**:若沙箱文件不存在,记得提供优雅回退,例如播放系统默认铃声或提示用户设置。
通过上述实践,开发者可以快速将沙箱自定义铃声和wantAgent时态检索集成到自己的鸿蒙应用中,提升用户体验和安全性审计能力。 |