Android工具介绍之文件提供者

原文链接:FileProvider

类概述

FileProvider是 ContentProvider 的一个特殊子类。通过对文件使用content:// Uri 替代file:// Uri,该类保证了应用之间共享文件的安全性。

一个content URI使用临时访问权限允许你授予读写访问。当你在创建一个包含content URI 的 Intent 时,为了发送这个content URI给客户端应用,你需要调用 Intent.setFlags() 来添加权限。对于一个客户端应用来说,只要接收的 Activity 栈堆是存活的,权限就是可用的。对于一个发送到 ServiceIntent 来说,只要 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文件中加入 <provider> 标签。设置 android:name 属性为 android.support.v4.content.FileProvider 。将android:authorities 属性设置为基于你控制的域名的URI授权:例如,你控制的域名为 mydomain.com,你应该使用这个授权 com.mydomain.fileprovider。如果FileProvider 不需要是公开访问,将 android:exported 属性设置为 false。设置 android:grantUriPermissions 属性为 true,允许你获得这些文件的临时访问权限。例如:

<manifest>
   ...
  <application>
    ...
    <provider
        android:name="android.support.v4.content.FileProvider"
        android:authorities="com.mydomain.fileprovider"
        android:exported="false"
        android:grantUriPermissions="true">
        ...
    </provider>
     ...
  </application>
</manifest>

注意: 需要在gradle中加入:

dependencies {    
   compile 'com.android.support:support-v4:23.3.0'
}

如果你想覆写 FileProvider 方法中的任何默认行为,扩展 FileProvider 类,然后在 <provider>android:name 属性中使用完全合格的类名。

指定可用的文件

<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <files-path name="my_images" path="images/"/>
    ...
</paths>

这个 <paths> 元素必须包含一个或者以上的子元素。

<files-path name="name" path="path" />

files-path 代表你的应用内部存储区域子目录 files/ 下的文件。这个子目录与 Context.getFilesDir() 获取的目录路径相同。

<external-path name="name" path="path" />

代表你应用的外部存储器的根路径文件。这个路径与通过 Context.getExternalFilesDir() 返回的路径相同。

<cache-path name="name" path="path" />

代表了你应用内部存储器区域缓存中的子路径。这个路径与 getCacheDir() 路径相同。

这些子元素使用相同的属性:

  • name="name"
    一个URI路径段。为了保证执行安全,这个值隐藏了你需要分享的子目录名称。这个值的子目录名称包含在 path 属性中。

  • path="path"
    你正在分享的子目录名称。注意这个值代表子目录,而不仅仅是单独的文件。你不能通过它的文件名来分享一个文件,也不能使用通配符来指定一个文件集。

对于每一个你希望使用content URIs访问的包含文件的目录,你必须指定 <paths> 对应的子元素。举个例子,下面这个XML元素指定了两个目录:

 <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>

在你的工程中加入 <paths> 和它的子元素。举个例子:你能在 res/xml/file_paths.xml 文件中添加它们。为了将这个文件添加到 FileProvider 中,在 <provider> 的子元素中添加 <meta-data> 元素。设置 <meta-data> 元素的 android:name 属性为 android.support.FILE_PROVIDER_PATHS 。设置该元素的 android:resource 的属性为 @xml/file_paths (注意你不需要指定.xml扩展名)。例子如下:

<provider
  android:name="android.support.v4.content.FileProvider"
  android:authorities="com.mydomain.fileprovider"
  android:exported="false"
  android:grantUriPermissions="true">
  <meta-data
      android:name="android.support.FILE_PROVIDER_PATHS"
      android:resource="@xml/file_paths" />
</provider>

生成文件的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授予临时权限

通过 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。当你调用 IntentIntent.setFlags() 方法设置临时访问权限时,相同的权限会应用到所有的content URIs上。

注意: Intent.setClipData() 方法只在版本16 (Android 4.1) 以上的版本支持。如果你想兼容前面的版本,你应该每次使用 Intent 发送一个content URI。设置Intent 动作为 ACTION_SEND,然后调用 setData() 放入content URI中。

更多信息

关于FileProvider的更多信息,参考Android最佳实践 通过URIs安全共享文件.

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 173,926评论 25 709
  • ¥开启¥ 【iAPP实现进入界面执行逐一显】 〖2017-08-25 15:22:14〗 《//首先开一个线程,因...
    小菜c阅读 6,657评论 0 17
  • Android N系列适配---FileProvider Android 7.0的适配,主要包含方面: Andro...
    25a58172fbb5阅读 7,161评论 3 32
  • 其实一个人没那么糟。无论是没有恋人,还是没有朋友。尽管有时会孤单,有时会无助,有时痛苦难以倾诉。但是这些都督促你成...
    奉孝大人阅读 213评论 0 0
  • 葵花朝阳金霰空,飞崖坠泉若绸梦。当年顽劣犹记,陌上芳影,野旷红蜻蜓。 三五片晚霞映火,六七棵枣树听风。而今蹉跎难...
    天外月明阅读 177评论 0 0