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

试图打开文件时,应用程序崩溃。 它在Android N下工作,但在Android N上崩溃。 它只是当我试图从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: file:///storage/emulated/0/test.txt exposed beyond app through Intent.getData() 

编辑:

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

如果您的targetSdkVersion为24或更高,我们必须使用FileProvider类来访问特定文件或文件夹,以使其他应用程序可以访问该文件或文件夹。 我们创build自己的类inheritanceFileProvider ,以确保我们的FileProvider不会与在这里描述的在导入依赖项中声明的FileProviders冲突。

用内容replacefile:// uri的步骤:// uri:

  • 添加一个扩展FileProvider的类

     public class GenericFileProvider extends FileProvider { } 
  • 在AndroidManifest.xml标签下添加一个FileProvider标签。 为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}.my.package.name.provider" android:exported="false" android:grantUriPermissions="true"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/provider_paths"/> </provider> </application> </manifest> 
  • 然后在res文件夹下的xml文件夹中创build一个provider_paths.xml文件。 如果文件夹不存在,可能需要创build文件夹。 该文件的内容如下所示。 它描述了我们想要以名称external_files在根文件夹(path=".")共享对外部存储的访问。
 <?xml version="1.0" encoding="utf-8"?> <paths xmlns:android="http://schemas.android.com/apk/res/android"> <external-path name="external_files" path="."/> </paths> 
  • 最后一步是改变下面的代码行

     Uri photoURI = Uri.fromFile(createImageFile()); 

     Uri photoURI = FileProvider.getUriForFile(context, context.getApplicationContext().getPackageName() + ".my.package.name.provider", createImageFile()); 
  • 编辑:如果使用意图使系统打开您的文件,您可能需要添加以下代码行:

     intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); 

希望这将有助于… 🙂

请参考,完整的代码和解决scheme已经在这里解释。

除了使用FileProvider的解决schemeFileProvider ,还有另一种解决方法。 简单的说

 StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder(); StrictMode.setVmPolicy(builder.build()); 

Application.onCreate() 。 这样虚拟机就会忽略文件的URI暴露。

方法

 builder.detectFileUriExposure() 

启用文件暴露检查,如果我们不设置VmPolicy,这也是默认行为。

我遇到了一个问题,如果我使用content:// URI发送content:// ,有些应用程序就不能理解它。 并且不允许降级target SDK版本。 在这种情况下我的解决scheme是有用的

如果您的targetSdkVersion为24或更高, 则无法在Android 7.0及更高版本设备上的Intents使用file: Uri值 。

您的select是:

  1. 把你的targetSdkVersion降到23或更低

  2. 将内容放在内部存储上,然后使用FileProvider将其select性地提供给其他应用程序

例如:

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

(从这个示例项目 )

如果您的应用程序的目标是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(); } } 

方法StrictMode.disableDeathOnFileUriExposure隐藏并logging为:

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

问题是,我的应用程序不是跛脚,而是不想通过使用内容:/意图是许多应用程序不知道在那里跛子。 例如,使用content:// scheme打开mp3文件比在file:// scheme打开相同的应用程序要less得多。 我不想通过限制我的应用程序的function来支付Google的devise错误。

谷歌希望开发人员使用内容scheme,但系统没有为此做好准备,多年来,应用程序使用的文件不是“内容”,文件可以编辑和保存回来,而内容scheme服务的文件不能他们?)。

首先,您需要在您的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> 

现在在xml资源文件夹中创build一个文件(如果使用android studio,您可以在突出显示file_paths后点击Alt + Enter并selectcreate a xml resource option)

接下来在file_paths文件中input

 <?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> 

这个例子是用于外部path,你可以在这里引用更多的选项。 这将允许您共享该文件夹及其子文件夹中的文件。

现在剩下的就是创造意图如下:

  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(); } 

编辑 :我在file_paths中添加了SD卡的根文件夹。 我已经testing了这个代码,它确实工作。

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

创build一个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> 

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> 

更换

 Uri uri = Uri.fromFile(fileImagePath); 

 Uri uri = FileProvider.getUriForFile(MainActivity.this, BuildConfig.APPLICATION_ID + ".provider",fileImagePath); 

你很好走。 希望能帮助到你。

@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) 

这帮助我解决外部存储文件的崩溃,希望这会帮助一些人有我的相同的问题:)

使用fileProvider是要走的路。 但是你可以使用这个简单的解决方法:

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

更换:

 startActivity(intent); 

通过

 startActivity(Intent.createChooser(intent, "Your title")); 

我用上面给出的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);