原文链接:FileProvider 墙内链接:FileProvider
FileProvider 是 ContentProvider 的一个特殊的子类,它有利于安全地分享应用相关的文件,通过对一个文件创建content:// Uri
而不是file:/// Uri
。
content URI允许你使用临时访问权限授权读写访问。当你创建一个包含content URI的Intent时,为了发送content URI 到一个客户端应用程序,你可以调用Intent.setFlags()
添加权限。这些权限是提供给客户端应用程序,只要接收Activity的栈处于活动状态。对于一个和Service通讯的Intent,只要Service在运行,权限也是可用的。
比较而言,为了控制访问file:/// Uri
,你不得不修改底层文件的文件系统权限。你提供的权限可应用于任何应用,并且会保持同样的效果直到你修改它们。这个访问级别是从根本上不安全的。通过content URI提高访问文件的安全级别让FileProvider成为Android安全基础的关键部分。
FileProvider主要包括下面几点内容:
1.定义一个FileProvider
2.指定可用文件
3.为文件创建Content URI
4.对URI授权临时权限
5.和其它应用共享Content URI
定义一个FileProvider
由于FileProvider的默认功能包括文件的content URI的生成,你并不需要在代码中定义一个子类。相反,你可以在你的应用中包含一个FileProvider通过在XML文件中指定它。对于指定FileProvider,添加一个<provider>
元素在你应用的清单文件中。设置android:name
属性为android.support.v4.content.FileProvider
。根据你控制的域名设置android:authorities
属性为一个URI authority。例如,你控制的域名为mydomain.com你应该设置authority为 com.mydomain.fileprovider。设置android:exported
属性为false;FileProvider不需要公开。设置android:grantUriPermissions
属性为true,为了允许你进行临时访问文件的授权。例如:
1 | <manifest> |
如果你想重写FileProvider的方法的任何的默认行为,继承FileProvider类并在<provider>
元素的android:name
属性使用全类名。
指定可用文件
一个FileProvider只能生成一个content URI 对于你事先指定目录下的文件。对于指定一个目录,使用<paths>
元素的子元素,在XML中指定它的存储区域和路径。例如,下面的paths元素告诉FileProvider你打算请求你的私有文件区域的 images/
子目录的content URIs。
1 | <paths xmlns:android="http://schemas.android.com/apk/res/android"> |
<paths>
元素必须包含一个或多个下面的子元素:
1 | <files-path name="name" path="path" /> |
表示你应用内部存储区域的文件的子目录。这个子目录和Context.getFilesDir()的返回值一样。1
<external-path name="name" path="path" />
表示你应用内部存储区域的根目录。Context.getExternalFilesDir()返回这个根目录下的文件的子目录。
1 | <cache-path name="name" path="path" /> |
表示你应用内部存储区域的缓存子目录。这个子目录的根目录和getCacheDir()的返回值一样。
这些子元素都使用相同的属性:
name=”name”
URI路径段。为了安全考虑,这个值会隐藏你分享的子目录的名称。子目录的名称包含在path属性。
path=”path”
你分享的子目录。name属性是一个URI路径段,path值是实际子目录名。请注意,该值是指一个子目录,而不是单独的文件。你不能通过文件名分享单个文件,也不能使用通配符指定文件的子集。
你必须给<paths>
指定一个子元素,包含你想添加content URIs文件的每个目录。例如,这个XML指定了两个目录:1
2
3
4<paths xmlns:android="http://schemas.android.com/apk/res/android">
<files-path name="my_images" path="images/"/>
<files-path name="my_docs" path="docs/"/>
</paths>
在你的工程中添加一个XML文件添加<paths>
及其子元素,例如,你可以添加一个名为res/xml/file_paths.xml
的文件。把这个文件关联到FileProvider,声明FileProvider,添加<meta-data>
元素作为<provider>
元素的子元素。设置<meta-data>
元素的"android:name"
属性为android.support.FILE_PROVIDER_PATHS。设置元素的"android:resource"
属性为@xml/file_paths(注意不要添加.xml扩展名)。例如:
1 | <provider |
为文件创建Content URI
为了使用content URI和其它应用共享文件,你的应用得生成一个content URI。为了生成content URI,对文件创建File对象,然后把File对象作为getUriForFile()方法的参数。你可以使用Intent发送getUriForFile()返回的content URI到其它应用程序。接收content URI 的客户端应用可以打开该文件并且可以通过调用ContentResolver.openFileDescriptor得到ParcelFileDescriptor访问它的内容。
例如,假设你的应用使用FileProvider给其它应用提供文件authority是com.mydomain.fileprovider。为了得到你内部存储的images/子目录下的default_image.jpg文件的content URI,添加如下代码:1
2
3File imagePath = new File(Context.getFilesDir(), "images");
File newFile = new File(imagePath, "default_image.jpg");
Uri contentUri = getUriForFile(getContext(), "com.mydomain.fileprovider", newFile);
上面片段的结果,getUriForFile()返回的content URI为content://com.mydomain.fileprovider/my_images/default_image.jpg
。
对URI授权临时权限
为了对getUriForFile()返回的content URI授权访问权限,执行下面的操作:
- 对
content:// Uri
调用Context.grantUriPermission(package, Uri, mode_flags)方法,使用想要的mode flags。这个对content URI授权临时权限是针对指定包名的,根据mode_flags参数的值,你可以设置为FLAG_GRANT_READ_URI_PERMISSION、FLAG_GRANT_WRITE_URI_PERMISSION或者两者,直到你调用revokeUriPermission()撤销权限或设备重启,权限才会失效。- 通过调用setData()把content URI 放到Intent中。
- 然后调用Intent.setFlags(),设置为FLAG_GRANT_READ_URI_PERMISSION、FLAG_GRANT_WRITE_URI_PERMISSION或两者。
- 最后发送Intent到其它应用。常用的是通过调用setResult()来实现。
当接收Activity的栈处于活动状态,被授权权限的Intent会一直有效。当栈结束后,权限会被自动移除。授权权限到客户端应用的一个Activity会自动继承应用的其它组件。
和其它应用共享Content URI
这有多种方式共享一个文件的content URI到客户端应用程序。一个常见的方式是客户端应用通过调用startActivityResult()开启你的应用,发送Intent到你的应用开启一个Activity。作为响应,你的应用可以马上返回一个content URI到客户端应用或者显示一个允许用户选择文件的用户界面。后一种情况,你的应用可以返回用户选择的文件的 content URI。在这两种情况下,你的应用通过setResult()在Intent中返回content URI。
你也可以把content URI放到ClipData对象中,然后添加对象到Intent中发送到客户端应用。如果用这个方法,调用Intent.setClipData()。当你用这个方法时,可以添加多个ClipData对象到Intent中,每个都自己的content URI。当你在Intent 上调用Intent.setFlags()设置临时访问权限时,同样的权限会应用到所有的 content URIs。
注意:Intent.setClipData()只支持平台版本16(Android 4.1)及以上。如果你想兼容以前的版本,你应该在Intent中发送一个content URI。设置content URI 动作并通过调用setData()存入URI。
更多信息
如果想了解更多关于FileProvider,请看Android培训课程——用URI安全共享文件。