【问题标题】:READ_EXTERNAL_STORAGE permission needed to open content://media/external/[...] in MediaPlayer?在 MediaPlayer 中打开 content://media/external/[...] 需要 READ_EXTERNAL_STORAGE 权限吗?
【发布时间】:2017-08-10 21:22:45
【问题描述】:

我正在我的应用程序中播放警报声。在大多数设备上,这可以完美运行。一些用户描述没有声音播放。这似乎主要影响三星和华为设备。这是失败的一个最小示例:

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 failed.: 状态=0x80000000

这似乎只有在 alarmURI 看起来像这样时才会发生:content://media/external/audio/media/11,即它驻留在外部存储中。当alarmURI 看到这个content://media/internal/audio/media/40 时,我从未见过它发生过。

如果我使用的 Uri 包含 /external/ 部分,我是否需要 READ_EXTERNAL_STORAGE 权限才能播放声音?还是我错过了其他东西?通常,如果权限是问题,我希望抛出一个 SecurityException 。

/e:我可以在我的设备上重现该错误。这是更详细的错误报告:

异常 java.lang.SecurityException: Permission Denial: 读取 com.android.providers.media.MediaProvider URI content://media/external/audio/media 来自 pid=27158, uid=10177 需要 android.permission.READ_EXTERNAL_STORAGE,或 grantUriPermission()

【问题讨论】:

    标签: android permissions android-mediaplayer android-contentprovider android-external-storage


    【解决方案1】:

    通常情况下,我会期待 SecurityException

    您没有收到 SecurityException,因为您的代码在访问 Linux 文件系统(操作系统级别)权限时失败,而不是 Android 框架权限。对于您从 API 收到的RuntimeExceptions(例如 SecurityException)没有严格的保证,这就是为什么它们首先被称为“未经检查的异常”。

    如果我使用的 Uri 包含 /external/ 部分,我是否需要 READ_EXTERNAL_STORAGE 权限才能播放声音?

    切勿使用外部提供的 Uri 的内容在代码中做出任何决定。只需将它们视为不透明的字符串即可。

    这似乎只有在 alarmURI 看起来像这样时才会发生......,即它驻留在外部存储中。当alarmURI 看起来这个时,我从未见过它发生...

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

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

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

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

    1. 有罪设备上的 MediaStorage ContentProvider 存在漏洞(制造商可以并且经常使用自定义漏洞版本替换系统 ContentProvider)
    2. 有罪设备上的 MediaPlayer 实现存在错误
    3. 有罪设备的 SELinux 权限存在缺陷(过多限制对文件的访问)
    4. 您的代码接收 Uris,将它们存储在某处,但不调用 takePersistableUriPermission,因此您的访问在应用重启后失效。

    如果没有异常堆栈跟踪,就很难诊断出确切的问题。无论哪种方式,如果上述建议没有帮助,除了使用不同的 API 播放声音文件和/或强制用户在不使用 Android MediaProvider 的情况下选择警报声音之外,您几乎无能为力。


    *除了一些 SELinux 检查,但你真的无能为力

    【讨论】:

    • 感谢您的回答。这真的很有见地。我已经开始阅读你提到的所有主题。另外,我成功地重现了错误并且也能够解决它。请求 READ_EXTERNAL_STORAGE 权限可以解决它。但是,我想使用尽可能少的权限。因此,我尝试查找如何实现动态 uri 权限,但我无法弄清楚。有两种方式接收 uri:
    • 1. RingtoneManager.getActualDefaultRingtoneUri() 和 2. Intent intent = new Intent(RingtoneManager.ACTION_RINGTONE_PICKER); RingtoneManager 的文档没有提到动态权限。所以我现在真的不知道如何开始......
    • @mc51 没有文档明确提及动态权限(除了相关方法本身的文档),因为创建 ContentProvider 系统时不存在动态权限。它们是在更高版本的 Android 中引入的。使用picker Intent时,收到Uri后调用takePersistableUriPermission。不知道怎么处理getActualDefaultRingtoneUri,你可能想看看,各种开源应用是如何使用它的。
    • 1/2 @mc51 请注意,对动态权限的实际支持程度取决于特定的 ContentProvider 以及您接收 Uri 的方式。一些系统 API 根本无法很好地处理动态权限,而一些 ContentProviders 根本不会授予您权限。例如,剪贴板中的 Uri 不会神奇地提供给您(因为放置它的进程不知道谁会收到它)。因此,存储在 Android 剪贴板中的 Uris 必须来自 Android O 之前的公共 ContentProviders。有可能,在您的情况下,同样根本没有支持......
    • 2/2 @mc51 ...声明权限是您唯一可用的解决方法。
    猜你喜欢
    • 1970-01-01
    • 2015-12-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-12-05
    • 2011-09-02
    • 1970-01-01
    相关资源
    最近更新 更多