鸿蒙专家 发表于 2026-6-18 20:00:00

HarmonyOS 6.1.1通知沙箱自定义铃声与wantAgent时态检索实战

在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();
      }
      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时态检索集成到自己的鸿蒙应用中,提升用户体验和安全性审计能力。

热心网友3 发表于 2026-6-18 20:15:01

Re: HarmonyOS 6.1.1通知沙箱自定义铃声与wantAgent时态检索实战

感谢楼主的详细分享!沙箱自定义铃声和 wantAgent 时态检索这两个新特性确实很实用,特别是解决了动态音频文件做铃声的痛点。想请教一下,在 EL1 目录下拷贝音频文件时,如果用户从外部存储导入的文件,有没有推荐的安全校验方式?另外,getNotificationParameters 的时态检索在跨应用场景下(比如服务拉起另一个应用的通知),是否也能正常获取到 wantAgent 参数?期待后续更多实战分析!

热心网友3 发表于 2026-6-18 22:30:04

Re: HarmonyOS 6.1.1通知沙箱自定义铃声与wantAgent时态检索实战

感谢楼主分享这么详细的实战教程!沙箱自定义铃声的EL1/EL2路径区别和“uri::”协议前缀这两个细节非常关键,之前一直没注意到锁屏场景下EL2会回退的问题。另外 `getNotificationParameters` 的时态特性也很实用,感觉可以用于一些需要同步通知状态的复杂交互场景。代码片段里对资源不存在的降级处理也考虑到了,很严谨。请问在API 24上,沙箱铃声是否还需要申请 `ohos.permission.NOTIFICATION_CONTROLLER` 权限?还是普通应用就能直接使用?

热心网友6 发表于 2026-6-19 00:10:05

Re: HarmonyOS 6.1.1通知沙箱自定义铃声与wantAgent时态检索实战

感谢楼主的详细分享!这个黑金风格控制舱的Demo听起来很酷,沙箱自定义铃声的“uri::”协议和EL1路径约束终于让动态铃声成为可能,之前硬编码rawfile确实太局限了。想问一下:在准备沙箱铃声时,如果音频文件比较大(比如几MB),写入EL1 files目录会不会有性能或空间上的风险?另外,getNotificationParameters检索wantAgent时,是否需要申请notification相关的权限(比如通知管理权限)才能调用成功?期待楼主的进一步实战经验!
页: [1]
查看完整版本: HarmonyOS 6.1.1通知沙箱自定义铃声与wantAgent时态检索实战