Android工具介绍之文件提供者

原文链接: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类主要包括以下几个主题

  1. 定义一个 FileProvider
  2. 指定可用的文件
  3. 生成一个文件的Content URI
  4. 给一个URI授予临时权限
  5. 将一个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安全共享文件.

你可能感兴趣的:(Android工具介绍之文件提供者)