如何使用支持FileProvider共享内容到其他应用程序?
我正在寻找一种方法,使用Android支持库的FileProvider与外部应用程序正确共享(而不是打开)内部文件。
以文档为例,
<provider android:name="android.support.v4.content.FileProvider" android:authorities="com.example.android.supportv4.my_files" android:grantUriPermissions="true" android:exported="false"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/my_paths" /> </provider>
并使用ShareCompat将文件共享到其他应用程序,如下所示:
ShareCompat.IntentBuilder.from(activity) .setStream(uri) // uri from FileProvider .setType("text/html") .getIntent() .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
不起作用,因为FLAG_GRANT_READ_URI_PERMISSION只授予在意图data
上指定的Uri的权限,而不是EXTRA_STREAM
额外的值(由setStream
设置)。
我试图通过为提供者设置android:exported
为true
来危害安全性,但FileProvider
内部检查自身是否被导出,如果是,则抛出exception。
从支持库中使用FileProvider
,你必须手动授予和撤销权限(在运行时)为其他应用程序读取特定的Uri。 使用Context.grantUriPermission和Context.revokeUriPermission方法。
例如:
//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);
作为最后的手段,如果您不能提供软件包名称,则可以将权限授予所有可处理特定意图的应用程序:
//grant permisions for all apps that can handle given intent Intent intent = new Intent(); intent.setAction(Intent.ACTION_SEND); ... List<ResolveInfo> 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); }
根据文件的替代方法:
- 通过调用setData()将内容URI放在一个Intent中。
- 接下来,使用FLAG_GRANT_READ_URI_PERMISSION或FLAG_GRANT_WRITE_URI_PERMISSION或两者调用方法Intent.setFlags()。
最后,将意图发送到另一个应用程序。 大多数情况下,你可以通过调用setResult()来做到这一点。
在接收活动的堆栈处于活动状态时,在Intent中授予的权限保持有效。 当堆栈结束时,
权限被自动删除。 权限授予一个
客户端应用程序中的活动会自动扩展到其他
该应用程序的组件。
顺便说一句。 如果需要的话,可以复制FileProvider的源文件并更改attachInfo
方法来防止提供程序检查是否导出。
这个解决scheme适用于我自OS 4.4以来。 为了使它在所有设备上工作,我为老设备添加了解决方法。 这确保始终使用最安全的解决scheme。
的Manifest.xml:
<provider android:name="android.support.v4.content.FileProvider" android:authorities="com.package.name.fileprovider" android:exported="false" android:grantUriPermissions="true"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths" /> </provider>
file_paths.xml:
<paths> <files-path name="app_directory" path="directory/"/> </paths>
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 <= Build.VERSION_CODES.KITKAT) { List<ResolveInfo> 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 <= Build.VERSION_CODES.KITKAT) { 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); context.revokeUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION); } }
该权限在片段或活动的onResume和onDestroy()方法中通过revokeFileReadPermission()被撤销。
完整的工作代码示例如何从内部应用文件夹共享文件。 在Android 7和Android 5上testing
AndroidManifest.xml中
</application> .... <provider android:name="android.support.v4.content.FileProvider" android:authorities="android.getqardio.com.gmslocationtest" android:exported="false" android:grantUriPermissions="true"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/provider_paths"/> </provider> </application>
XML / provider_paths
<?xml version="1.0" encoding="utf-8"?> <paths> <files-path name="share" path="external_files"/> </paths>
代码本身
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);
据我可以告诉这只会在Android的新版本,所以你可能要弄清楚一个不同的方式来做到这一点。 这个解决scheme适用于4.4,但不适用于4.0或2.3.3,所以这将不是一个有用的方式去共享内容的应用程序,可以在任何Android设备上运行。
在manifest.xml中:
<provider android:name="android.support.v4.content.FileProvider" android:authorities="com.mydomain.myapp.SharingActivity" android:exported="false" android:grantUriPermissions="true"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths" /> </provider>
仔细logging你如何指定权限。 您必须指定要从中创buildURI并启动共享意向的活动,在这种情况下,该活动称为SharingActivity。 这个要求从Google的文档中是不明显的!
file_paths.xml:
<?xml version="1.0" encoding="utf-8"?> <paths xmlns:android="http://schemas.android.com/apk/res/android"> <files-path name="just_a_name" path=""/> </paths>
小心如何指定path。 以上默认为您的私人内部存储的根。
在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"));
在这个例子中,我们共享一个JPEG图像。
最后,确保自己已经正确保存了文件,并且可以使用类似如下的方式访问它:
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!"); }
正如菲尔在对原始问题的评论中所说,这是独一无二的,在谷歌上没有其他的信息,我想我也应该分享我的结果:
在我的应用程序中,FileProvider开箱即用共享意图共享文件。 除了设置FileProvider以外,没有必要的特殊configuration或代码。 在我的manifest.xml中,我放置了:
<provider android:name="android.support.v4.content.FileProvider" android:authorities="com.my.apps.package.files" android:exported="false" android:grantUriPermissions="true" > <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/my_paths" /> </provider>
在my_paths.xml中我有:
<paths xmlns:android="http://schemas.android.com/apk/res/android"> <files-path name="files" path="." /> </paths>
在我的代码中,我有:
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)));
而且我可以在应用程序私人存储中与我的应用程序(如Gmail和谷歌驱动器)共享我的文件存储没有任何麻烦。
在我的应用程序FileProvider工作得很好,我可以将文件目录中存储的内部文件附加到Gmail,雅虎等电子邮件客户端。
在我的Android文档中提到的清单中,我放置了:
<provider android:name="android.support.v4.content.FileProvider" android:authorities="com.package.name.fileprovider" android:grantUriPermissions="true" android:exported="false"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/filepaths" /> </provider>
由于我的文件存储在根文件目录中,所以filepaths.xml如下所示:
<paths> <files-path path="." name="name" />
现在在代码中:
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<Uri> uris = new ArrayList<Uri>(); 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(); } }
这对我工作。希望这有助于:)
只是为了改善上面给出的答案:如果你得到NullPointerEx:
你也可以使用没有上下文的getApplicationContext()
List<ResolveInfo> 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); }