Android 11系统通知渠道自定义外部存储提示音无法播放如何解决
问题根因
Android 11(API 30)开始强制开启分区存储特性,应用私有外部数据目录(即你用到的/storage/emulated/0/Android/data/com.appName.app/files/路径)仅允许应用自身访问,系统进程(包括负责播放通知提示音的NotificationManager服务)无权限读取该路径下的文件。你直接将私有目录下的本地文件路径传给通知渠道作为提示音地址,系统播放时无法访问到音频文件,就会出现通知音失效的问题。
Android 11以下版本因为分区存储未强制生效,系统进程可读取应用私有外部目录文件,所以该方案此前可正常运行。
修复方案
你需要将自定义提示音文件导出到系统进程有权限访问的公共区域,再用对应的Content URI配置通知渠道即可,完整步骤如下:
- 第一步:提前清理旧渠道
Android 8.0及以上版本通知渠道创建后,其提示音、震动等配置就无法再修改,所以需要先删除旧的同ID渠道,后续新配置才会生效:
// 创建渠道前先删除旧渠道,只在Android O及以上执行 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { NotificationManager manager = reactContext.getSystemService(NotificationManager.class); manager.deleteNotificationChannel(channelID); }
- 第二步:将私有目录下的音频文件插入系统媒体库,获取公共访问URI
将你存放在私有目录的提示音文件拷贝到系统公共通知音目录,并写入MediaStore获取Content URI,该URI系统进程可正常访问:
// 音频插入MediaStore的示例代码 private Uri getNotificationSoundUri(File audioFile) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { ContentValues values = new ContentValues(); values.put(MediaStore.Audio.Media.DISPLAY_NAME, audioFile.getName()); values.put(MediaStore.Audio.Media.MIME_TYPE, "audio/wav"); values.put(MediaStore.Audio.Media.RELATIVE_PATH, Environment.DIRECTORY_NOTIFICATIONS); values.put(MediaStore.Audio.Media.IS_NOTIFICATION, true); values.put(MediaStore.Audio.Media.IS_PENDING, 1); ContentResolver resolver = reactContext.getContentResolver(); Uri uri = resolver.insert(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, values); if (uri == null) return null; try (OutputStream outputStream = resolver.openOutputStream(uri); FileInputStream inputStream = new FileInputStream(audioFile)) { byte[] buffer = new byte[1024]; int length; while ((length = inputStream.read(buffer)) > 0) { outputStream.write(buffer, 0, length); } values.clear(); values.put(MediaStore.Audio.Media.IS_PENDING, 0); resolver.update(uri, values, null, null); return uri; } catch (IOException e) { resolver.delete(uri, null, null); e.printStackTrace(); } } else { // 低于Android 10版本兼容逻辑 ContentValues values = new ContentValues(); values.put(MediaStore.Audio.Media.DATA, audioFile.getAbsolutePath()); values.put(MediaStore.Audio.Media.TITLE, audioFile.getName()); values.put(MediaStore.Audio.Media.MIME_TYPE, "audio/wav"); values.put(MediaStore.Audio.Media.IS_NOTIFICATION, true); return reactContext.getContentResolver().insert(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, values); } return null; }
- 第三步:用新的URI设置通知渠道
调用上面的方法拿到合法的Content URI后,替换你原来代码里的soundUri即可:
public void createChannel(String channelID, String channelName, File audioFile) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { // 先删旧渠道 NotificationManager manager = reactContext.getSystemService(NotificationManager.class); manager.deleteNotificationChannel(channelID); Uri soundUri = getNotificationSoundUri(audioFile); if (soundUri == null) { // 拿不到uri的降级逻辑,用系统默认通知音 soundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION); } AudioAttributes audioAttributes = new AudioAttributes.Builder() .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) .setUsage(AudioAttributes.USAGE_NOTIFICATION) .build(); NotificationChannel notificationChannel = new NotificationChannel(channelID, channelName, NotificationManager.IMPORTANCE_HIGH); notificationChannel.setDescription("Text here"); notificationChannel.setSound(soundUri, audioAttributes); notificationChannel.setShowBadge(true); notificationChannel.enableVibration(true); manager.createNotificationChannel(notificationChannel); } }
内容的提问来源于stack exchange,提问作者Kash
相关产品推荐
相关产品推荐

