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是:
-
把你的
targetSdkVersion
降到23或更低 -
将内容放在内部存储上,然后使用
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);