原文链接:FileProvider
类概述
FileProvider是 ContentProvider 的一个特殊子类。通过对文件使用content://
Uri 替代file://
Uri,该类保证了应用之间共享文件的安全性。
一个content URI使用临时访问权限允许你授予读写访问。当你在创建一个包含content URI 的 Intent 时,为了发送这个content URI给客户端应用,你需要调用 Intent.setFlags() 来添加权限。对于一个客户端应用来说,只要接收的 Activity 栈堆是存活的,权限就是可用的。对于一个发送到 Service 的 Intent 来说,只要 Service 是在运行的,权限也就是可用的。
相比之下,为了获取file:///
Uri 类型的访问控制,你不得不修改底层文件的文件系统权限。直到你再次修改之前,你提供的这个权限对于任何应用都是可用的,并且一直会产生作用。这种程度的访问基本上是不安全的。
由a content URI提供的文件安全访问机制使FileProvider
成为Android安全基础框架的重要组成部分。
FileProvider
类主要包括以下几个主题
- 定义一个
FileProvider
- 指定可用的文件
- 生成一个文件的Content URI
- 给一个URI授予临时权限
- 将一个URI传输给需要的应用
定义一个文件提供者
FileProvider
的默认功能已经包括了为文件生成content URI,因此你不需要再在代码中定义一个子类来实现此功能。相反,你可以在你的应用中完全使用XML来加入一个 FileProvider
。为了添加FileProvider
,在你的应用的manifest
文件中加入 android:name
属性为 android.support.v4.content.FileProvider
。将android:authorities
属性设置为基于你控制的域名的URI授权:例如,你控制的域名为 mydomain.com
,你应该使用这个授权 com.mydomain.fileprovider
。如果FileProvider
不需要是公开访问,将 android:exported
属性设置为 false
。设置 android:grantUriPermissions 属性为 true
,允许你获得这些文件的临时访问权限。例如:
...
...
...
...
注意: 需要在gradle中加入:
dependencies {
compile 'com.android.support:support-v4:23.3.0'
}
如果你想覆写 FileProvider
方法中的任何默认行为,扩展 FileProvider
类,然后在
的 android:name
属性中使用完全合格的类名。
指定可用的文件
...
这个
元素必须包含一个或者以上的子元素。
files-path
代表你的应用内部存储区域子目录 files/
下的文件。这个子目录与 Context.getFilesDir() 获取的目录路径相同。
代表你应用的外部存储器的根路径文件。这个路径与通过 Context.getExternalFilesDir() 返回的路径相同。
代表了你应用内部存储器区域缓存中的子路径。这个路径与 getCacheDir() 路径相同。
这些子元素使用相同的属性:
name="name"
一个URI路径段。为了保证执行安全,这个值隐藏了你需要分享的子目录名称。这个值的子目录名称包含在path
属性中。path="path"
你正在分享的子目录名称。注意这个值代表子目录,而不仅仅是单独的文件。你不能通过它的文件名来分享一个文件,也不能使用通配符来指定一个文件集。
对于每一个你希望使用content URIs访问的包含文件的目录,你必须指定
对应的子元素。举个例子,下面这个XML元素指定了两个目录:
在你的工程中加入
和它的子元素。举个例子:你能在 res/xml/file_paths.xml
文件中添加它们。为了将这个文件添加到 FileProvider
中,在
的子元素中添加
元素的 android:name
属性为 android.support.FILE_PROVIDER_PATHS
。设置该元素的 android:resource
的属性为 @xml/file_paths
(注意你不需要指定.xml扩展名)。例子如下:
生成文件的Content URI
为了使用content URI方式给其他应用分享文件,你的应用必须生成Content URI。为了生成content URI,问这个文件创建一个新 File ,然后将 File 传送给 [getUriForFile()](http://developer.android.com/reference/android/support/v4/content/FileProvider.html#getUriForFile(android.content.Context, java.lang.String, java.io.File))。你能将 [getUriForFile()](http://developer.android.com/reference/android/support/v4/content/FileProvider.html#getUriForFile(android.content.Context, java.lang.String, java.io.File)) 返回的content URI通过 Intent 发送给其他应用。收到这个content URI的客户端调用[ContentResolver.openFileDescriptor](http://developer.android.com/reference/android/content/ContentResolver.html#openFileDescriptor(android.net.Uri, java.lang.String)) 得到一个 ParcelFileDescriptor 对象,使用这个对象该应用能够打开这个文件并访问它的内容。
例如,假设你的应用使用拥有 com.mydomain.fileprovider
授权的FileProvider向其他应用提供文件。获取在你的内部存储images/
子目录下的default_image.jpg
图片代码如下:
File imagePath = new File(Context.getFilesDir(), "images");
File newFile = new File(imagePath, "default_image.jpg");
Uri contentUri = getUriForFile(getContext(), "com.mydomain.fileprovider", newFile);
[getUriForFile()](http://developer.android.com/reference/android/support/v4/content/FileProvider.html#getUriForFile(android.content.Context, java.lang.String, java.io.File)) 返回示例Uri如下:
content://com.mydomain.fileprovider/my_images/default_image.jpg
给一个URI授予临时权限
- 对
content://
Uri 调用 [Context.grantUriPermission(package, Uri, mode_flags)](http://developer.android.com/reference/android/content/Context.html#grantUriPermission(java.lang.String, android.net.Uri, int)),使用希望的模式标志。这种方式根据mode_flags
授予制定的程序临时访问的权限。mode_flags
可以设定为 FLAG_GRANT_READ_URI_PERMISSION 或者 FLAG_GRANT_WRITE_URI_PERMISSION,或者两者都有。这种授权一直会存在,直到你调用 [revokeUriPermission()](http://developer.android.com/reference/android/content/Context.html#revokeUriPermission(android.net.Uri, int)) 或者手机重启。 - 调用 [setData]((http://developer.android.com/reference/android/content/Intent.html#setData(android.net.Uri))将content URI放入Intent 当中。然后调用 Intent.setFlags() 方法设置 FLAG_GRANT_READ_URI_PERMISSION 或者 FLAG_GRANT_WRITE_URI_PERMISSION 权限,或者两者都设置。最后,给其他应用发送这个 Intent 。大多数情况下,你使用[setResult()](http://developer.android.com/reference/android/app/Activity.html#setResult(int, android.content.Intent)) 完成整个调用。
通过 Intent 方式授权在 Activity 堆栈存在的时候会一直有用。当堆栈结束的时候,这个权限会自动移除。给一个应用中一个 Activity 授予的权限会自动扩展到这个应用的其他组件。
将一个URI传输给需要的应用
存在很多种不同的方式将一个文件的content URI传输给客户端应用。对于一个客户端应用来说,启动你的应用的一种常见方式是调用 [startActivityResult()](http://developer.android.com/reference/android/app/Activity.html#startActivityForResult(android.content.Intent, int, android.os.Bundle)),这将会发送一个 Intent 到你的应用开启一个 Activity。相应的,你的应用能够立即返回一个content URI到客户端应用或者展示一个允许用户选择文件的用户界面。在后一种情况下,一旦用户选择了某个文件,你的应用汇返回它与之对应的content URI。在这两种情况下,你的应用会通过 Intent 的 [setResult()](http://developer.android.com/reference/android/app/Activity.html#setResult(int, android.content.Intent)) 方法返回这个content URI。
你也能将content URI放在 ClipData 对象中,然后将这个对象加入你要发送给客户端应用的 Intent 中。为了完成这些,调用 Intent.setClipData()方法。当你使用这个方法时,你能加入多个 ClipData 对象到 Intent 中,并且每个 Intent 都可以有自己的content URI。当你调用 Intent 的 Intent.setFlags() 方法设置临时访问权限时,相同的权限会应用到所有的content URIs上。
注意: Intent.setClipData() 方法只在版本16 (Android 4.1) 以上的版本支持。如果你想兼容前面的版本,你应该每次使用 Intent 发送一个content URI。设置Intent 动作为 ACTION_SEND,然后调用 setData() 放入content URI中。
更多信息
关于FileProvider的更多信息,参考Android最佳实践 通过URIs安全共享文件.