0

我在我的应用程序中播放闹钟声音。在大多数设备上,这个工作完美无瑕。有些用户描述说没有声音在播放。这似乎主要影响三星和华为设备。这是那里的东西不能小例子:READ_EXTERNAL_STORAGE权限需要在MediaPlayer中打开content:// media/external/[...]?

alarmUri = RingtoneManager.getActualDefaultRingtoneUri(this, RingtoneManager.TYPE_ALARM); 
try { 
      if(!mediaPlayer.isPlaying()) { 
       mediaPlayer.setDataSource(this, alarmUri); 
       AudioManager audioManager = (AudioManager) this.getSystemService(Context.AUDIO_SERVICE); 
       mediaPlayer.setAudioStreamType(audioManager.STREAM_ALARM); 
       mediaPlayer.setLooping(true); 
       mediaPlayer.prepare(); 
       mediaPlayer.start(); 
      } 
     } catch(Exception e) {     
       Log.d(logtag,"Exception: " + e); 
     } 

对于受影响的用户,下面的异常被抛出:

异常java.io.IOException的:失败的setDataSource: 状态= 0x80000000的

这似乎只发生在alarmURI如下所示:content://media/external/audio/media/11,即它驻留在外部存储上。当alarmURI看起来这是content://media/internal/audio/media/40时,我从未见过它发生。

为了播放声音,我需要READ_EXTERNAL_STORAGE权限吗?如果我使用的Uri包含/external/部分?还是我错过了别的?通常,如果权限是问题,我会期望抛出SecurityException。

/电子邮件:我可以重现我的设备上的错误。这里有一个更详细的错误报告:

异常java.lang.SecurityException异常:权限拒绝:从PID //媒体/外部/音频/媒体:阅读 com.android.providers.media.MediaProvider URI 内容= 27158,UID = 10177 需要android.permission.READ_EXTERNAL_STORAGE,或 grantUriPermission()

回答

1

通常情况下,我期望一个SecurityException

您不会得到SecurityException,因为您的代码在遇到Linux文件系统(OS级别)权限时失败,而不是Android框架权限。关于您从API接收的RuntimeException没有严格的保证(如SequrityException),这就是为什么他们被称为“未经检查的例外”的原因。

为了播放声音,我需要READ_EXTERNAL_STORAGE权限吗?如果我使用的Uri包含/external/部分?

切勿使用外部提供的Uri的内容在您的代码中作出任何决定。只要把它们当作不透明的字符串。

只有当alarmURI看起来像这样...,即它驻留在外部存储上时,才会出现这种情况。我从来没有看到它发生alarmURI看起来这样...

你是正确的发布Uris的故障排除,但它也有帮助的,知道,哪里的Uri来自。 Android的现代版本使用dynamic Uri permission model。即:您对Uri的访问级别可能会有所不同,具体取决于Uri的来源以及主叫方是否已将FLAG_GRANT_READ_URI_PERMISSION添加到Intent(或者针对相同结果调用Context#grantUriPermission),以及您是否在接收Uri的Intent时调用了Context#takePersistableUriPermission

是的,您可能需要READ_EXTERNAL_STORAGE权限。 Android使用FUSE或类似厂商特定的API根据调用应用程序的权限调整外部存储的可见操作系统级权限:当您的应用程序具有权限时,外部文件看起来可读(文件#isReadable返回true),当它不,外部文件看起来不可访问(File#isReadable返回false)。当然,这只适用于文件,但如果调用者给你一个file:// Uri呢? Uri背后的ContentProvider可能会在给你文件描述符之前检查你是否拥有READ_EXTERNAL_STORAGE权限(这很愚蠢,但肯定可行)。

此外,最好自己打开Uri并将流/文件描述符提供给玩家,而不是Uri。大多数Linux安全检查*发生在打开文件描述符时。为了实现可预测的行为,您应该在自己的代码中打开Uri,并尽可能多地执行错误处理,包括捕获远程ContentProvider中的RuntimeException。

为什么会出现这个问题?可能有多种原因,但它可能是其中之一:

  1. 的MediaStorage的ContentProvider上犯的设备仅仅是越野车(厂家可以并且经常更换定制的越野车版本系统ContentProviders)
  2. 的MediaPlayer的执行情况有罪的设备是越野车
  3. 上犯设备SELinux的权限是有缺陷的(限制使用权限的文件太多)
  4. 你的代码接收尤里斯,将它们存储的地方,但不调用takePersistableUriPermission,让您的访问权限的应用程序重新启动后失效。

没有异常堆栈跟踪,很难诊断确切的问题。无论哪种方式,如果上述建议不起作用,除了使用不同的API播放声音文件和/或强制用户在不使用Android MediaProvider的情况下选择闹钟声音,您几乎无法做到这一点。


*除了一些SELinux的检查,但你不能真正做到那些

+1

谢谢您的回答什么。这真的很有见地。我已经开始阅读你提到的所有主题。此外,我成功地重现了错误,并且还能够解决它。请求READ_EXTERNAL_STORAGE权限解决它。但是,我想尽可能少地使用权限。因此,我试图查找如何实现动态的uri权限,但我无法弄清楚。有两种方法可以接收URI: – mc51

+0

1. RingtoneManager.getActualDefaultRingtoneUri()和2. Intent intent = new Intent(RingtoneManager.ACTION_RINGTONE_PICKER); RingtoneManager的文档没有提及动态权限。因此,我现在真的不会如何开始...... – mc51

+0

@ mc51没有文档明确提到动态权限(除了相关方法本身的文档),因为在创建ContentProvider系统时不存在动态权限。它们在Android的更高版本中引入。当你使用选择器Intent时,在收到Uri后调用'takePersistableUriPermission'。不知道,如何处理'getActualDefaultRingtoneUri',你可能想看看,各种开源应用程序如何使用它。 – user1643723