可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
The app is crashing when I'm trying to open a file. It works below Android Nougat, but on Android Nougat it crashes. It only crashes when I try to open a file from the SD card, not from the system partition. Some permission problem?
Sample code:
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
Log:
android.os.FileUriExposedException: file:///storage/emulated/0/test.txt exposed beyond app through Intent.getData()
Edit:
When targeting Android Nougat, file://
URIs are not allowed anymore. We should use content://
URIs instead. However, my app needs to open files in root directories. Any ideas?
回答1:
If your targetSdkVersion >= 24
, then we have to use FileProvider
class to give access to the particular file or folder to make them accessible for other apps. We create our own class inheriting FileProvider
in order to make sure our FileProvider doesn't conflict with FileProviders declared in imported dependencies as described here.
Steps to replace file://
URI with content://
URI:
Add a class extending FileProvider
public class GenericFileProvider extends FileProvider {}
Add a FileProvider tag in AndroidManifest.xml under tag. Specify a unique authority for the android:authorities
attribute to avoid conflicts, imported dependencies might specify ${applicationId}.provider
and other commonly used authorities.
- Then create a
provider_paths.xml
file in res/xml
folder. Folder may be needed to created if it doesn't exist. The content of the file is shown below. It describes that we would like to share access to the External Storage at root folder (path=".")
with the name external_files.
The final step is to change the line of code below in
Uri photoURI = Uri.fromFile(createImageFile());
to
Uri photoURI = FileProvider.getUriForFile(context, context.getApplicationContext().getPackageName() + ".my.package.name.provider", createImageFile());
Edit: If you're using an intent to make the system open your file, you may need to add the following line of code:
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
Please refer, full code and solution has been explained here.
回答2:
Besides the solution using the FileProvider
, there is another way to work around this. Simply put
StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder(); StrictMode.setVmPolicy(builder.build());
in Application.onCreate()
. In this way the VM ignores the file URI
exposure.
Method
builder.detectFileUriExposure()
enables the file exposure check, which is also the default behavior if we don't setup a VmPolicy.
I encountered a problem that if I use a content://
URI
to send something, some apps just can't understand it. And downgrading the target SDK
version is not allowed. In this case my solution is useful.
Update:
As mentioned in the comment, StrictMode is diagnostic tool, and is not supposed to be used for this problem. When I posted this answer a year ago, many apps can only receive File uris. They just crash when I tried to send a FileProvider uri to them. This is fixed in most apps now, so we should go with the FileProvider solution.
回答3:
If your app targets API 24+, and you still want/need to use file:// intents, you can use hacky way to disable the runtime check:
if(Build.VERSION.SDK_INT>=24){ try{ Method m = StrictMode.class.getMethod("disableDeathOnFileUriExposure"); m.invoke(null); }catch(Exception e){ e.printStackTrace(); } }
Method StrictMode.disableDeathOnFileUriExposure
is hidden and documented as:
/** * Used by lame internal apps that haven't done the hard work to get * themselves off file:// Uris yet. */
Problem is that my app is not lame, but rather doesn't want to be crippled by using content:// intents which are not understood by many apps out there. For example, opening mp3 file with content:// scheme offers much fewer apps than when opening same over file:// scheme. I don't want to pay for Google's design faults by limiting my app's functionality.
Google wants developers to use content scheme, but the system is not prepared for this, for years apps were made to use Files not "content", files can be edited and saved back, while files served over content scheme can't be (can they?).
回答4:
If your targetSdkVersion
is 24 or higher, you can not use file:
Uri
values in Intents
on Android 7.0+ devices.
Your choices are:
Drop your targetSdkVersion
to 23 or lower, or
Put your content on internal storage, then use FileProvider
to make it available selectively to other apps
For example:
Intent i=new Intent(Intent.ACTION_VIEW, FileProvider.getUriForFile(this, AUTHORITY, f)); i.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); startActivity(i);
(from this sample project)
回答5:
If targetSdkVersion
is higher than 24, then FileProvider is used to grant access.
Create an xml file(Path: res\xml) provider_paths.xml
Add a Provider in AndroidManifest.xml
and replace
Uri uri = Uri.fromFile(fileImagePath);
to
Uri uri = FileProvider.getUriForFile(MainActivity.this, BuildConfig.APPLICATION_ID + ".provider",fileImagePath);
and you are good to go. Hope it helps.
回答6:
First you need to add a provider to your AndroidManifest
....
now create a file in xml resource folder (if using android studio you can hit Alt + Enter after highlighting file_paths and select create a xml resource option)
Next in the file_paths file enter
This example is for external-path you can refere here for more options. This will allow you to share files which are in that folder and its sub-folder.
Now all that's left is to create the intent as follows:
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(); }
EDIT: I added the root folder of the sd card in the file_paths. I have tested this code and it does work.
回答7:
@palash k answer is correct and worked for internal storage files, but in my case I want to open files from external storage also, my app crashed when open file from external storage like sdcard and usb, but I manage to solve the issue by modifying provider_paths.xml from the accepted answer
change the provider_paths.xml like below
and in java class(No change as the accepted answer just a small edit)
Uri uri=FileProvider.getUriForFile(getActivity(), BuildConfig.APPLICATION_ID+".provider", File)
This help me to fix the crash for files from external storages, Hope this will help some one having same issue as mine :)
回答8:
Using the fileProvider is the way to go. But you can use this simple workaround:
WARNING: It will be fixed in next Android release - https://issuetracker.google.com/issues/37122890#comment4
replace:
startActivity(intent);
by
startActivity(Intent.createChooser(intent, "Your title"));
回答9:
I used Palash's answer given above but it was somewhat incomplete, I had to provide permission like this
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 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);
回答10:
Just paste the below code in activity onCreate()
StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder(); StrictMode.setVmPolicy(builder.build());
It will ignore URI exposure