android.os.FileUriExposedException:file:///storage/emulated/0/test.txt通过Intent.getData()暴露在app之外

Tho*_*Vos 682 android android-file android-7.0-nougat

当我尝试打开文件时,应用程序崩溃了.它可以在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
Run Code Online (Sandbox Code Playgroud)

日志:

android.os.FileUriExposedException:file:///storage/emulated/0/test.txt通过Intent.getData()暴露在app之外

编辑:

在定位Android Nougat时,file://不再允许使用URI.我们应该使用content://URI代替.但是,我的应用程序需要打开根目录中的文件.有任何想法吗?

小智 1203

如果您targetSdkVersion >= 24,那么我们必须使用FileProvider类来授予对特定文件或文件夹的访问权限,以使其可供其他应用程序访问.我们创造我们自己的类继承FileProvider,以确保我们的FileProvider不会描述进口依赖宣布FileProviders冲突这里.

file://content://URI 替换URI的步骤:

  • 添加一个扩展类 FileProvider

    public class GenericFileProvider extends FileProvider {}
    
    Run Code Online (Sandbox Code Playgroud)
  • 添加FileProvider <provider>在标签AndroidManifest.xml下的<application>标签.为android:authorities属性指定唯一权限以避免冲突,导入的依赖项可能指定${applicationId}.provider以及其他常用权限.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    ...
    <application
        ...
        <provider
            android:name=".GenericFileProvider"
            android:authorities="${applicationId}.provider"
            android:exported="false"
            android:grantUriPermissions="true">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/provider_paths"/>
        </provider>
    </application>
</manifest>
Run Code Online (Sandbox Code Playgroud)
  • 然后provider_paths.xmlres/xml文件夹中创建一个文件.如果文件夹不存在,则可能需要创建文件夹.该文件的内容如下所示.它描述了我们希望(path=".")在名称为external_files的根文件夹中共享对外部存储的访问.
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <external-path name="external_files" path="."/>
</paths>
Run Code Online (Sandbox Code Playgroud)

请参考,完整的代码和解决方案已在这里解释.

  • 我只需要添加intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); (58认同)
  • 只有当你想要覆盖任何默认行为时才应该扩展`FileProvider`,否则使用`android:name ="android.support.v4.content.FileProvider"`.请参阅https://developer.android.com/reference/android/support/v4/content/FileProvider.html (56认同)
  • 它适用于所有Android版本,还是仅适用于API 24? (22认同)
  • @rockhammer我刚用Android 5.0,6.0,7.1和8.1进行了测试,它适用于所有情况.所以`(Build.VERSION.SDK_INT> M)`条件没用. (11认同)
  • 这篇文章帮助了我https://medium.com/@ali.muzaffar/what-is-android-os-fileuriexposedexception-and-what-you-can-do-about-it-70b9eb17c6d0#.54odzsnk4 (7认同)
  • 如链接代码所示,在AndroidManifest.xml中,我使用了以下代码:android:authorities =“ $ {applicationId} .provider”,在解析中使用了:Uri uri = FileProvider.getUriForFile(this,BuildConfig.APPLICATION_ID +“ .provider”,文件);`,这有效!谢谢! (4认同)
  • AndroidX的默认提供程序是`android:name =“ androidx.core.content.FileProvider”` (3认同)
  • `引起:java.lang.SecurityException:权限拒绝:从 ProcessRecord 打开提供程序 android.support.v4.content.FileProvider` (2认同)
  • 这给了我错误“java.lang.SecurityException:权限拒绝:打开提供程序......未导出......” (2认同)

hqz*_*zwb 292

除了使用the的解决方案之外FileProvider,还有另一种方法可以解决这个问题.简单的说

StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder();
StrictMode.setVmPolicy(builder.build());
Run Code Online (Sandbox Code Playgroud)

Application.onCreate().以这种方式,VM忽略文件URI曝光.

方法

builder.detectFileUriExposure()
Run Code Online (Sandbox Code Playgroud)

启用文件曝光检查,如果我们不设置VmPolicy,这也是默认行为.

我遇到了一个问题,如果我使用一个content:// URI发送的东西,一些应用程序只是无法理解它.并且target SDK不允许降级版本.在这种情况下,我的解决方案很有用

更新:

正如评论中所提到的,StrictMode是诊断工具,不应该用于此问题.当我在一年前发布此答案时,许多应用程序只能接收文件uris.当我尝试向他们发送FileProvider uri时,它们就崩溃了.这在大多数应用程序中已得到修复,因此我们应该使用FileProvider解决方案.

  • 但是,这如何解决此问题,StrictMode是一种诊断工具,应在开发人员模式而不是发布模式下启用? (3认同)
  • @ImeneNoomene在你的愤怒中我完全和你在一起.你是对的,这是一个诊断工具,或者至少在很久以前我把它添加到我的项目中.这太令人沮丧了!`StrictMode.enableDefaults();`,我只在我的开发版本上运行,防止这种崩溃发生 - 所以我现在有一个生产应用程序崩溃,但它在开发时不会崩溃.基本上,在这里启用诊断工具隐藏了一个严重的问题.谢谢@hqzxzwb帮助我揭开神秘面纱. (3认同)

Poi*_*ull 153

如果您的应用面向API 24+,并且您仍然需要/需要使用file:// intents,则可以使用hacky方式禁用运行时检查:

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

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

/**
* Used by lame internal apps that haven't done the hard work to get
* themselves off file:// Uris yet.
*/
Run Code Online (Sandbox Code Playgroud)

问题是我的应用程序不是蹩脚的,而是不希望被使用内容瘫痪://那些许多应用程序无法理解的意图.例如,使用content:// scheme打开mp3文件比在file:// scheme上打开相同的应用程序要少得多.我不想通过限制我的应用程序的功能来支付Google的设计错误.

谷歌希望开发人员使用内容方案,但系统并没有为此做好准备,多年来,应用程序使用文件而不是"内容",文件可以编辑和保存回来,而文件通过内容方案提供服务不能(可以他们?).

  • 适用于Android 7.谢谢 (5认同)
  • 适用于Android 8,在华为Nexus 6P上测试过. (4认同)
  • 我确认这是在生产(我有超过500,000个用户),目前版本8.1是最高版本,它适用于它. (4认同)
  • "虽然通过内容方案提供的文件不能(可以吗?)." - 确定,如果您对内容具有写入权限.`ContentResolver`同时具有`openInputStream()`和`openOutputStream()`.一个不太常见的方法就是[自己配置VM规则](https://developer.android.com/reference/android/os/StrictMode.VmPolicy.Builder.html),不要启用`文件``Uri`规则. (3认同)

Pan*_*lan 148

如果targetSdkVersion高于24,则使用FileProvider授予访问权限.

创建一个xml文件(Path:res\xml)provider_paths.xml

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <external-path name="external_files" path="."/>
</paths>
Run Code Online (Sandbox Code Playgroud)


AndroidManifest.xml中添加提供程序

    <provider
        android:name="android.support.v4.content.FileProvider"
        android:authorities="${applicationId}.provider"
        android:exported="false"
        android:grantUriPermissions="true">
        <meta-data
            android:name="android.support.FILE_PROVIDER_PATHS"
            android:resource="@xml/provider_paths"/>
    </provider>
Run Code Online (Sandbox Code Playgroud)

替换

 android:name="androidx.core.content.FileProvider"
Run Code Online (Sandbox Code Playgroud)

Uri uri = Uri.fromFile(fileImagePath);
Run Code Online (Sandbox Code Playgroud)

编辑:当您包含URI时,请Intent确保添加以下行:

Uri uri = FileProvider.getUriForFile(MainActivity.this, BuildConfig.APPLICATION_ID + ".provider",fileImagePath);
Run Code Online (Sandbox Code Playgroud)

你很高兴.希望能帮助到你.

  • @MaksimKniazev您能简要描述一下您的错误吗?这样我可以帮助您。 (2认同)
  • @PankajLilan,我完全按照你说的做了。但是每次我在另一个应用程序中打开我的 pdf 时,它都显示为空白(正确保存)。我需要编辑 xml 吗?我已经添加了 FLAG_GRANT_READ_URI_PERMISSION ; (2认同)
  • 我的错误,我在错误的意图中添加了权限。这是最好和最简单的正确答案。谢谢! (2认同)
  • 它抛出异常java.lang.IllegalArgumentException:无法找到包含/我的文件路径的已配置根目录是/storage/emulated/0/GSMManage_DCIM/Documents/Device7298file_74.pdf.你能帮忙吗? (2认同)
  • 这就是答案!其他人都错了。这是新鲜的并且适用于新代码! (2认同)

Com*_*are 87

如果您的版本targetSdkVersion是24或更高,则无法在Android 7.0及file: UriIntents更高版本的设备上使用.

你的选择是:

  1. 放下你的targetSdkVersion23或更低,或

  2. 将您的内容放在内部存储上,然后使用FileProvider它将其有选择地提供给其他应用程序

例如:

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

i.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivity(i);
Run Code Online (Sandbox Code Playgroud)

(来自这个示例项目)

  • @SuperThomasLab:我不会指望`/ system`中的所有东西都是世界可读的.话虽这么说,我的猜测是你仍会得到这个例外.我怀疑他们只是在检查方案,而不是试图确定文件是否真的是世界可读的.但是,`FileProvider`不会帮助你,因为你不能教它从`/ system`服务.您可以[为我的`StreamProvider`创建一个自定义策略](https://github.com/commonsguy/cwac-provider/blob/master/docs/EXTENDING.markdown#supporting-other-stream-locations),或者滚动你的拥有`ContentProvider`,以解决问题. (2认同)
  • @rommex:我不知道什么是"最重要的".例如,在分屏模式或自由形式多窗口设备(Chromebook,Samsung DeX)上工作的用户将被告知您的应用可能无法使用多窗口.这是否重要取决于你. (2认同)

Kar*_*tel 47

首先,您需要为AndroidManifest添加提供程序

  <application
    ...>
    <activity>
    .... 
    </activity>
    <provider
        android:name="android.support.v4.content.FileProvider"
        android:authorities="com.your.package.fileProvider"
        android:grantUriPermissions="true"
        android:exported="false">
        <meta-data
            android:name="android.support.FILE_PROVIDER_PATHS"
            android:resource="@xml/file_paths" />
    </provider>
  </application>
Run Code Online (Sandbox Code Playgroud)

现在在xml资源文件夹中创建一个文件(如果使用android studio,你可以在突出显示file_paths后选择Alt + Enter并选择创建一个xml资源选项)

接下来在file_paths文件中输入

<?xml version="1.0" encoding="utf-8"?>
<paths>
  <external-path path="Android/data/com.your.package/" name="files_root" />
  <external-path path="." name="external_storage_root" />
</paths>
Run Code Online (Sandbox Code Playgroud)

此示例适用于外部路径,您可以在此处引用更多选项.这将允许您共享该文件夹及其子文件夹中的文件.

现在剩下的就是按如下方式创建意图:

    MimeTypeMap mime = MimeTypeMap.getSingleton();
    String ext = newFile.getName().substring(newFile.getName().lastIndexOf(".") + 1);
    String type = mime.getMimeTypeFromExtension(ext);
    try {
        Intent intent = new Intent();
        intent.setAction(Intent.ACTION_VIEW);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
            Uri contentUri = FileProvider.getUriForFile(getContext(), "com.your.package.fileProvider", newFile);
            intent.setDataAndType(contentUri, type);
        } else {
            intent.setDataAndType(Uri.fromFile(newFile), type);
        }
        startActivityForResult(intent, ACTIVITY_VIEW_ATTACHMENT);
    } catch (ActivityNotFoundException anfe) {
        Toast.makeText(getContext(), "No activity found to open this attachment.", Toast.LENGTH_LONG).show();
    }
Run Code Online (Sandbox Code Playgroud)

编辑:我在file_paths中添加了SD卡的根文件夹.我已经测试了这段代码,但确实有效.

  • 这次真是万分感谢。我还想让您知道有更好的方法来获取文件扩展名。`String extension = android.webkit.MimeTypeMap.getFileExtensionFromUrl(Uri.fromFile(file).toString());` 另外,我建议任何寻找答案的人阅读 [FileProvider](https://developer.android.com/ reference/android/support/v4/content/FileProvider.html) 首先了解您在此处处理 Android N 及更高版本中的文件权限。有内部存储与外部存储以及常规文件路径与缓存路径的选项。 (2认同)
  • 我收到以下异常:`java.lang.IllegalArgumentException:无法找到配置的根...`,唯一有效的是`&lt;files-path path="。" xml 文件上的 name="files_root" /&gt;` 而不是 `&lt;external-path ...`。我的文件保存在内部存储器中。 (2认同)

Ram*_*amz 25

@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>
Run Code Online (Sandbox Code Playgroud)

并在java类中(没有更改,因为接受的答案只是一个小编辑)

Uri uri=FileProvider.getUriForFile(getActivity(), BuildConfig.APPLICATION_ID+".provider", File)
Run Code Online (Sandbox Code Playgroud)

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


小智 22

只需在活动onGreate()中粘贴以下代码即可

StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder(); StrictMode.setVmPolicy(builder.build());
Run Code Online (Sandbox Code Playgroud)

它将忽略URI暴露


Cra*_*J36 22

我的解决方案是将文件路径'Uri.parse'作为字符串,而不是使用Uri.fromFile().

String storage = Environment.getExternalStorageDirectory().toString() + "/test.txt";
File file = new File(storage);
Uri uri;
if (Build.VERSION.SDK_INT < 24) {
    uri = Uri.fromFile(file);
} else {
    uri = Uri.parse(file.getPath()); // My work-around for new SDKs, causes ActivityNotFoundException in API 10.
}
Intent viewFile = new Intent(Intent.ACTION_VIEW);
viewFile.setDataAndType(uri, "text/plain");
startActivity(viewFile);
Run Code Online (Sandbox Code Playgroud)

似乎fromFile()使用了一个文件指针,我认为当内存地址暴露给所有应用程序时,这可能是不安全的.但是文件路径字符串从不会伤害任何人,因此它可以在不抛出FileUriExposedException的情况下工作.

测试API级别9到26!不需要FileProvider,也不需要Android支持库.

  • 这不会发生异常,但也无法将文件发送到相关的应用程序。因此,对我不起作用。 (2认同)
  • 这在 Android 10 及更高版本上会失败,因为您不能假设其他应用程序可以通过文件系统访问外部存储。 (2认同)

Sim*_*mon 18

使用fileProvider是可行的方法.但是你可以使用这个简单的解决方法:

警告:它将在下一个Android版本中修复 - https://issuetracker.google.com/issues/37122890#comment4

更换:

startActivity(intent);
Run Code Online (Sandbox Code Playgroud)

通过

startActivity(Intent.createChooser(intent, "Your title"));
Run Code Online (Sandbox Code Playgroud)

  • 谷歌很快将对选择器进行修补以包含相同的检查.这不是解决方案. (7认同)
  • 这不适用于android 8和upper (2认同)

小智 18

只需在活动中粘贴以下代码即可onCreate().

StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder(); 
StrictMode.setVmPolicy(builder.build());
Run Code Online (Sandbox Code Playgroud)

它将忽略URI暴露.

快乐的编码:-)


Max*_*Max 12

我使用了上面给出的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);
Run Code Online (Sandbox Code Playgroud)


Ani*_*ilS 8

只需将以下代码粘贴到活动onCreate()中

StrictMode.VmPolicy.Builder builder =新的StrictMode.VmPolicy.Builder(); StrictMode.setVmPolicy(builder.build());

它将忽略URI暴露


DEV*_*SHK 5

在onCreate中添加这两行

StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder();
    StrictMode.setVmPolicy(builder.build());
Run Code Online (Sandbox Code Playgroud)

共享方式

File dir = new File(Environment.getExternalStorageDirectory(), "ColorStory");
File imgFile = new File(dir, "0.png");
Intent sendIntent = new Intent(Intent.ACTION_VIEW);
sendIntent.setType("image/*");
sendIntent.setAction(Intent.ACTION_SEND);
sendIntent.putExtra(Intent.EXTRA_STREAM, Uri.parse("file://" + imgFile));
sendIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivity(Intent.createChooser(sendIntent, "Share images..."));
Run Code Online (Sandbox Code Playgroud)


Ale*_*lex 5

这是我的解决方案:

Manifest.xml中

<application
            android:name=".main.MainApp"
            android:allowBackup="true"
            android:icon="@drawable/ic_app"
            android:label="@string/application_name"
            android:logo="@drawable/ic_app_logo"
            android:theme="@style/MainAppBaseTheme">

        <provider
                android:name="androidx.core.content.FileProvider"
                android:authorities="${applicationId}.provider"
                android:exported="false"
                android:grantUriPermissions="true">
            <meta-data
                    android:name="android.support.FILE_PROVIDER_PATHS"
                    android:resource="@xml/provider_paths"/>
        </provider>
Run Code Online (Sandbox Code Playgroud)

res / xml / provider_paths.xml中

   <?xml version="1.0" encoding="utf-8"?>
    <paths xmlns:android="http://schemas.android.com/apk/res/android">
        <external-path name="external_files" path="."/>
    </paths>
Run Code Online (Sandbox Code Playgroud)

在我的片段中,我有下面的代码:

 Uri myPhotoFileUri = FileProvider.getUriForFile(getActivity(), getActivity().getApplicationContext().getPackageName() + ".provider", myPhotoFile);               
    intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
    intent.putExtra(MediaStore.EXTRA_OUTPUT, myPhotoFileUri);
Run Code Online (Sandbox Code Playgroud)

您需要什么?

没有必要创建

public class GenericFileProvider extends FileProvider {}
Run Code Online (Sandbox Code Playgroud)

我在Android 5.0、6.0和Android 9.0上进行了测试,并且测试成功。


归档时间:

查看次数:

377881 次

最近记录:

5 年,11 月 前