当我试图打开一个文件时,应用程序崩溃了。它可以在Android Nougat下运行,但在Android Nougat上它会崩溃。只有当我试图从SD卡,而不是从系统分区打开文件时,它才会崩溃。权限问题?

示例代码:

File file = new File("/storage/emulated/0/test.txt");
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(Uri.fromFile(file), "text/*");
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent); // Crashes on this line

日志:

android.os.FileUriExposedException: ///storage/emulated/0/test.txt Intent.getData ()

编辑:

当针对Android Nougat时,file:// uri不再被允许。我们应该使用content:// uri。但是,我的应用程序需要打开根目录下的文件。什么好主意吗?


当前回答

@palash k答案是正确的,适用于内部存储文件,但在我的情况下,我想从外部存储打开文件,我的应用程序崩溃时从外部存储如sdcard和usb打开文件,但我设法通过修改provider_paths.xml从接受的答案解决了这个问题

像下面那样修改provider_paths.xml

<?xml version="1.0" encoding="utf-8"?>
 <paths xmlns:android="http://schemas.android.com/apk/res/android">

<external-path path="Android/data/${applicationId}/" name="files_root" />

<root-path
    name="root"
    path="/" />

</paths>

和在java类(没有改变作为接受的答案,只是一个小编辑)

Uri uri=FileProvider.getUriForFile(getActivity(), BuildConfig.APPLICATION_ID+".provider", File)

这帮助我修复了外部存储文件的崩溃,希望这将帮助一些人有同样的问题,我的 :)

其他回答

我使用了Palash上面给出的答案,但它有点不完整,我必须提供这样的许可

Intent intent = new Intent(Intent.ACTION_VIEW);
    Uri uri;
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
        uri = FileProvider.getUriForFile(this, getPackageName() + ".provider", new File(path));

        List<ResolveInfo> resInfoList = getPackageManager().queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
        for (ResolveInfo resolveInfo : resInfoList) {
            String packageName = resolveInfo.activityInfo.packageName;
            grantUriPermission(packageName, uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
        }
    }else {
        uri = Uri.fromFile(new File(path));
    }

    intent.setDataAndType(uri, "application/vnd.android.package-archive");

    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

    startActivity(intent);

如果你的targetSdkVersion是24或更高,你不能在Android 7.0+设备的intent中使用file: Uri值。

你的选择是:

将targetSdkVersion降至23或更低,或者 把你的内容放在内部存储,然后使用FileProvider使它对其他应用程序有选择性地可用

例如:

Intent i=new Intent(Intent.ACTION_VIEW, FileProvider.getUriForFile(this, AUTHORITY, f));

i.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivity(i);

(来自这个示例项目)

我刚刚做了以下如果android版本> 24

File fl = new File(url);
    Uri uri = Uri.fromFile(fl);
    Intent intent = new Intent(Intent.ACTION_VIEW);
    if (android.os.Build.VERSION.SDK_INT>=24)
    {
        Context context = getApplicationContext();
        uri = FileProvider.getUriForFile(
                context,
                context.getApplicationContext()
                        .getPackageName() + ".provider", fl);
        intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
    }
    intent.setDataAndType(uri, mimetype);
    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    startActivity(intent);

点击这个链接,然后https://medium.com/@ali.muzaffar/what-is-android-os- fileuriexposedexceptionand what-you-can-do-about-it 70b9eb17c6d0#.54odzsnk4

我知道这是一个相当老的问题,但这个答案是给未来的观众的。所以我遇到过类似的问题,经过研究,我找到了这种方法的替代方案。

你的意图 从Kotlin中的路径查看您的图像

 val intent = Intent()
 intent.setAction(Intent.ACTION_VIEW)
 val file = File(currentUri)
 intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
 val contentURI = getContentUri(context!!, file.absolutePath)
 intent.setDataAndType(contentURI,"image/*")
 startActivity(intent)

主要功能如下

private fun getContentUri(context:Context, absPath:String):Uri? {
        val cursor = context.getContentResolver().query(
            MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
            arrayOf<String>(MediaStore.Images.Media._ID),
            MediaStore.Images.Media.DATA + "=? ",
            arrayOf<String>(absPath), null)
        if (cursor != null && cursor.moveToFirst())
        {
            val id = cursor.getInt(cursor.getColumnIndex(MediaStore.MediaColumns._ID))
            return Uri.withAppendedPath(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, Integer.toString(id))
        }
        else if (!absPath.isEmpty())
        {
            val values = ContentValues()
            values.put(MediaStore.Images.Media.DATA, absPath)
            return context.getContentResolver().insert(
                MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values)
        }
        else
        {
            return null
        }
    }

同样地,除了图像,你可以使用任何其他文件格式,比如pdf,在我的例子中,它工作得很好

如果你的应用程序目标API 24+,你仍然想要/需要使用file:// intents,你可以使用hack方法禁用运行时检查:

if(Build.VERSION.SDK_INT>=24){
   try{
      Method m = StrictMode.class.getMethod("disableDeathOnFileUriExposure");
      m.invoke(null);
   }catch(Exception e){
      e.printStackTrace();
   }
}

StrictMode方法。disableDeathOnFileUriExposure被隐藏并记录为:

/**
* Used by lame internal apps that haven't done the hard work to get
* themselves off file:// Uris yet.
*/

问题是我的应用程序不是蹩脚的,而是不想被使用内容://意图所削弱,这是许多应用程序无法理解的。例如,以content:// scheme打开mp3文件,提供的应用程序比打开相同的over file:// scheme时少得多。我不想为谷歌的设计缺陷买单,限制我的应用程序的功能。

谷歌希望开发者使用内容方案,但系统并没有为此做好准备,多年来应用程序都是使用文件而不是“内容”,文件可以编辑和保存,而通过内容方案提供的文件则不能(可以吗?)