可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I'm looking for a way to correctly share (not OPEN) an internal file with external application using Android Support library's FileProvider.
Following the example on the docs,
and using ShareCompat to share a file to other apps as follows:
ShareCompat.IntentBuilder.from(activity) .setStream(uri) // uri from FileProvider .setType("text/html") .getIntent() .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
does not work, since the FLAG_GRANT_READ_URI_PERMISSION only grants permission for the Uri specified on the data of the intent, not the value of the EXTRA_STREAM extra (as was set by setStream).
I tried to compromise security by setting android:exported to true for the provider, but FileProvider internally checks if itself is exported, when so, it throws an exception.
回答1:
Using FileProvider from support library you have to manually grant and revoke permissions(at runtime) for other apps to read specific Uri. Use Context.grantUriPermission and Context.revokeUriPermission methods.
For example:
//grant permision for app with package "packegeName", eg. before starting other app via intent context.grantUriPermission(packageName, uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION); //revoke permisions context.revokeUriPermission(uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
As a last resort, if you can't provide package name you can grant the permission to all apps that can handle specific intent:
//grant permisions for all apps that can handle given intent Intent intent = new Intent(); intent.setAction(Intent.ACTION_SEND); ... List resInfoList = context.getPackageManager().queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY); for (ResolveInfo resolveInfo : resInfoList) { String packageName = resolveInfo.activityInfo.packageName; context.grantUriPermission(packageName, uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION); }
Alternative method according to the documentation:
- Put the content URI in an Intent by calling setData().
- Next, call the method Intent.setFlags() with either FLAG_GRANT_READ_URI_PERMISSION or FLAG_GRANT_WRITE_URI_PERMISSION or both.
Finally, send the Intent to another app. Most often, you do this by calling setResult().
Permissions granted in an Intent remain in effect while the stack of the receiving Activity is active. When the stack finishes, the
permissions are automatically removed. Permissions granted to one
Activity in a client app are automatically extended to other
components of that app.
Btw. if you need to, you can copy source of FileProvider and change attachInfo method to prevent provider from checking if it is exported.
回答2:
This solution works for me since OS 4.4. To make it work on all devices I added a workaround for older devices. This ensures that always the safest solution is used.
Manifest.xml:
file_paths.xml:
Java:
public static void sendFile(Context context) { Intent intent = new Intent(Intent.ACTION_SEND); intent.setType("text/plain"); String dirpath = context.getFilesDir() + File.separator + "directory"; File file = new File(dirpath + File.separator + "file.txt"); Uri uri = FileProvider.getUriForFile(context, "com.package.name.fileprovider", file); intent.putExtra(Intent.EXTRA_STREAM, uri); intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); // Workaround for Android bug. // grantUriPermission also needed for KITKAT, // see https://code.google.com/p/android/issues/detail?id=76683 if (Build.VERSION.SDK_INT resInfoList = context.getPackageManager().queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY); for (ResolveInfo resolveInfo : resInfoList) { String packageName = resolveInfo.activityInfo.packageName; context.grantUriPermission(packageName, attachmentUri, Intent.FLAG_GRANT_READ_URI_PERMISSION); } } if (intent.resolveActivity(context.getPackageManager()) != null) { context.startActivity(intent); } } public static void revokeFileReadPermission(Context context) { if (Build.VERSION.SDK_INT
The permission is revoked with revokeFileReadPermission() in the onResume and onDestroy() methods of the Fragment or the Activity.
回答3:
Fully working code sample how to share file from inner app folder. Tested on Android 7 and Android 5.
AndroidManifest.xml
....
xml/provider_paths
Code itself
File imagePath = new File(getFilesDir(), "external_files"); imagePath.mkdir(); File imageFile = new File(imagePath.getPath(), "test.jpg"); // Write data in your file Uri uri = FileProvider.getUriForFile(this, getPackageName(), imageFile); Intent intent = ShareCompat.IntentBuilder.from(this) .setStream(uri) // uri from FileProvider .setType("text/html") .getIntent() .setAction(Intent.ACTION_VIEW) //Change if needed .setDataAndType(uri, "image/*") .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); startActivity(intent);
回答4:
As far as I can tell this will only work on newer versions of Android, so you will probably have to figure out a different way to do it. This solution works for me on 4.4, but not on 4.0 or 2.3.3, so this will not be a useful way to go about sharing content for an app that's meant to run on any Android device.
In manifest.xml:
Take careful note of how you specify the authorities. You must specify the activity from which you will create the URI and launch the share intent, in this case the activity is called SharingActivity. This requirement is not obvious from Google's docs!
file_paths.xml:
Be careful how you specify the path. The above defaults to the root of your private internal storage.
In SharingActivity.java:
Uri contentUri = FileProvider.getUriForFile(getActivity(), "com.mydomain.myapp.SharingActivity", myFile); Intent shareIntent = new Intent(); shareIntent.setAction(Intent.ACTION_SEND); shareIntent.setType("image/jpeg"); shareIntent.putExtra(Intent.EXTRA_STREAM, contentUri); shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); startActivity(Intent.createChooser(shareIntent, "Share with"));
In this example we are sharing a JPEG image.
Finally it is probably a good idea to assure yourself that you have saved the file properly and that you can access it with something like this:
File myFile = getActivity().getFileStreamPath("mySavedImage.jpeg"); if(myFile != null){ Log.d(TAG, "File found, file description: "+myFile.toString()); }else{ Log.w(TAG, "File not found!"); }
回答5:
Since as Phil says in his comment on the original question, this is unique and there is no other info on SO on in google, I thought I should also share my results:
In my app FileProvider worked out of the box to share files using the share intent. There was no special configuration or code necessary, beyond that to setup the FileProvider. In my manifest.xml I placed:
In my_paths.xml I have:
In my code I have:
Intent shareIntent = new Intent(); shareIntent.setAction(Intent.ACTION_SEND); shareIntent.setType("application/xml"); Uri uri = FileProvider.getUriForFile(this, "com.my.apps.package.files", fileToShare); shareIntent.putExtra(Intent.EXTRA_STREAM, uri); startActivity(Intent.createChooser(shareIntent, getResources().getText(R.string.share_file)));
And I am able to share my files store in my apps private storage with apps such as Gmail and google drive without any trouble.
回答6:
In my app FileProvider works just fine, and I am able to attach internal files stored in files directory to email clients like Gmail,Yahoo etc.
In my manifest as mentioned in the Android documentation I placed:
And as my files were stored in the root files directory, the filepaths.xml were as follows:
Now in the code:
File file=new File(context.getFilesDir(),"test.txt"); Intent shareIntent = new Intent(android.content.Intent.ACTION_SEND_MULTIPLE); shareIntent.putExtra(android.content.Intent.EXTRA_SUBJECT, "Test"); shareIntent.setType("text/plain"); shareIntent.putExtra(android.content.Intent.EXTRA_EMAIL, new String[] {"email-address you want to send the file to"}); Uri uri = FileProvider.getUriForFile(context,"com.package.name.fileprovider", file); ArrayList uris = new ArrayList(); uris.add(uri); shareIntent .putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris); try { context.startActivity(Intent.createChooser(shareIntent , "Email:").addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); } catch(ActivityNotFoundException e) { Toast.makeText(context, "Sorry No email Application was found", Toast.LENGTH_SHORT).show(); } }
This worked for me.Hope this helps :)
回答7:
just to improve answer given above: if you are getting NullPointerEx:
you can also use getApplicationContext() without context
List resInfoList = getPackageManager().queryIntentActivities(takePictureIntent, PackageManager.MATCH_DEFAULT_ONLY); for (ResolveInfo resolveInfo : resInfoList) { String packageName = resolveInfo.activityInfo.packageName; grantUriPermission(packageName, photoURI, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION); }
回答8:
If you get an image from camera none of these solutions work for Android 4.4. In this case it's better to check versions.
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); if (intent.resolveActivity(getContext().getPackageManager()) != null) { if (Build.VERSION.SDK_INT