安卓7.0无法从文件提供者Uri播放通知声音

6

我正在修改我的应用代码以支持Android 7,但是在我的NotificationCompat.Builder.setSound(Uri)中,如果使用来自FileProvider的Uri,则通知不会播放任何声音,在Android 6中使用Uri.fromFile()可以正常工作。

mp3文件位于:

/Animeflv/cache/.sounds/

这是我的通知代码:

knf.animeflv.RequestBackground

NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(context)
.setSmallIcon(R.drawable.ic_not_r)
.setContentTitle(NotTit)
.setContentText(mess);
...
mBuilder.setVibrate(new long[]{100, 200, 100, 500});
mBuilder.setSound(UtilSound.getSoundUri(not)); //int

这是我的UtilSound.getSoundUri(int)。
public static Uri getSoundUri(int not) {
        switch (not) {
            case 0:
                return RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
            default:
                try {
                    File file=new File(Environment.getExternalStorageDirectory()+"/Animeflv/cache/.sounds",getSoundsFileName(not));
                    if (file.exists()) {
                        file.setReadable(true,false);
                        if (Build.VERSION.SDK_INT>=Build.VERSION_CODES.N){
                            return FileProvider.getUriForFile(context, "knf.animeflv.RequestsBackground",file);
                        }else {
                            return Uri.fromFile(file);
                        }
                    }else {
                        Log.d("Sound Uri","Not found");
                        return getSoundUri(0);
                    }
                }catch (Exception e){
                    e.printStackTrace();
                    return getSoundUri(0);
                }
        }
    }

在 AndroidManifest.xml 文件中:
<provider
    android:name="android.support.v4.content.FileProvider"
    android:authorities="knf.animeflv.RequestsBackground"
    android:exported="false"
    android:grantUriPermissions="true">
        <meta-data
            android:name="android.support.FILE_PROVIDER_PATHS"
            android:resource="@xml/provider_paths"/>
</provider>

provider_paths.xml:

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <external-path name="_.sounds" path="Animeflv/cache/.sounds/"/>
</paths>

http://stackoverflow.com/questions/39285228/how-to-set-audio-as-ringtone-programmatically-above-android-n?noredirect=1#comment66048598_39285228 - CommonsWare
1个回答

20
以下内容来自我刚发布的博客文章,在这里再次复制,因为,嘿,为什么不呢?

您可以通过NotificationCompat.Builder上的setSound()等方法,在通知上放置自定义铃声。这需要一个Uri,但在Android 7.0上会出现问题,正如一些人Stack Overflow上所报告的那样。

如果您正在使用file:Uri值,则在您的targetSdkVersion为24或更高版本时,它们将不再在Android 7.0上工作,因为声音Uri将被检查是否符合file:Uri值禁令

然而,如果您尝试使用来自FileProvidercontent:Uri,则您的声音将无法播放...因为Android没有读取该内容的访问权限。

以下是一些解决此问题的选项。

解决方法一:grantUriPermissions()

您始终可以通过Context上可用的grantUriPermissions()方法向其他应用程序授予内容权限。挑战在于知道要将权限授予给

在Nexus 6P(Android 6.0...仍然...)和Nexus 9(Android 7.0)上有效的是:

grantUriPermission("com.android.systemui", sound,
    Intent.FLAG_GRANT_READ_URI_PERMISSION);

(其中sound是您与setSound()一起使用的Uri)

我不能确定这是否适用于所有设备和所有Android操作系统版本。

断头台:不再使用用户文件

对于setSound()android.resource作为一种方案可以很好地处理Uri值。 而不是允许用户从文件中选择自己的铃声, 您只允许他们从您的应用程序中作为原始资源提供的几个铃声之一进行选择。 如果这代表了应用程序功能的损失,那么您的用户可能会感到失望。

斧头:使用自定义ContentProvider

FileProvider被导出时无法使用-它会在启动时崩溃。 但是,在这种情况下,唯一的content:Uri,没有其他问题,就是提供者是exported并且 没有读取访问权限(或需要某些权限的偶然发生 com.android.systemui或等效物持有)。

Eventually, I'll add options for this to 我的StreamProvider,作为一些“只读”提供程序功能的一部分。但是,您可以自己创建此类提供程序。下面的代码片段阻止与VM行为相关的所有StrictMode检查(即,除了主应用程序线程行为之外的其他内容),包括对file: Uri值的禁令。
StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder().build());

或者,您可以配置自己的VmPolicy,使用任何规则,而不必调用detectFileUriExposure()

这使您可以在任何地方使用file: Uri值。谷歌禁止使用file: Uri有充分的理由,因此试图避免禁令可能会在长期内对您造成不幸的影响。

解决方法一:将targetSdkVersion降低

这也会删除对file: Uri值的禁令,以及targetSdkVersion 24+选择的所有其他行为。值得注意的是,如果用户进入分屏多窗口模式,则会导致应用程序显示“可能无法与分屏一起使用”的Toast

解决方法二:在Android中进行修复

NotificationManager 应该为我们调用 grantUriPermissions(),或者有其他方法可以将 FLAG_GRANT_READ_URI_PERMISSION 与我们用于自定义 Notification 声音的 Uri 相关联。敬请期待更多发展

2
非常感谢您提供详尽的答案。为了验证grantUriPermission解决方案(“手术刀”)的健壮性,我已在不同的物理设备上进行了测试(包括LG、Motorola、HTC、Samsung和Sony),从API 15到API 24。它在所有设备上都能正常工作,因此该解决方案似乎相当可靠(尽管有些hacky)。 - jmart
@CommonsWare,您在哪里调用grantUriPermission?每次构建通知时都需要这样做吗?还是只有在uri选择更改时授予权限就足够了? - Allan W
1
据我所知,对于这种情况,您只需要为每个不同的“Uri”值调用“grantUriPermissions()”。因此,在您的情况下,当选择发生更改时。话虽如此,我怀疑每次调用“grantUriPermissions()”都没有任何害处。 - CommonsWare
1
似乎应该在Android P中修复:https://github.com/aosp-mirror/platform_frameworks_base/commit/2990141035d87028a7d31d8b0c3fbebc25fc2772 - rcell
1
@CommonsWare 这个问题已经在去年九月份得到修复,我来更新一下。 - Shadow
显示剩余10条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接