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安全共享文件.

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,593评论 6 513
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,080评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 167,025评论 0 357
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,317评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,329评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,036评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,639评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,557评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,089评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,197评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,330评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 36,006评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,689评论 3 332
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,189评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,313评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,676评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,339评论 2 358

推荐阅读更多精彩内容

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