1 FileProvider 概述
FileProvider 是ContentProvider 的子类。 ContentProvider 用于应用间的数据共享,FileProvider 用于不用应用间的文件共享。
FileProvider 的共享流程
- 与ContentProvider 相似:
当一个 请求文件的 client app 想要向共享了文件的 Server app 发送请求来获取文件,大多数情况,该请求第一个会开启一个分享的文件列表的 Activity(该 Activity隶属于Server app,目的是为了让用户选择先要获取的文件,因为他可能分享了不止一个文件)。
用户选择文件之后,Server app返回文件的 Uri 和权限给 client app。client app根据 Uri 去找文件,有了权限也就能对该文件进行读写操作了。
为什么选择使用 Uri 呢
如果直接将文件传输为 client app:当该文件非常大时,十分消耗资源。
如果将文件的地址传给 client app:这样相当于把自身的app的文件共享地址暴露出来。这是非常危险的,我们无法估计其他app拿到这个地址之后的操作。如果这个地址下也有其他的共享文件,就不能保证他们的安全。因此,app共享文件以一种提供content Uri的形式提供一个对文件的安全操作:android 中的 FileProvider 组件通过 getUriForFile 方法为 File 生成一个 Content Uri。
2 FileProvider 的使用流程
2.1 注册 FileProvider
和 ContentProvider一样,需要在Manifest 文件中等记,在manifest 中指定 authority属性用来生成 content Uri。
额外的,必须指定一个分享 xml 文件的目录,所以分享的 xml 文件都在该目录下。<provider android:name="androidx.core.content.FileProvider" android:authorities="${applicationId}.fileprovider" android:exported="false" android:grantUriPermissions="true"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths" /> </provider>
- name 一般固定为android.support.v4.content.FileProvider。如果开发者继承了FileProvider,则可以写上其绝对路径。
- authorities 字段的值是一个由 build.gradle 文件中的 applicationId 值和自定义的名称 组成的 Uri 字符串(这样写是约定俗成的)。用来表明使用的使用者,在FileProvider的函数getUriForFile需要传入该参数。
- exported 的值为false,表示该FileProvider只能本应用使用,不是public的。
- grantUriPermissions 的值为true,表示允许赋予临时权限。
- <meta-data> resourece 属性指向 res/xml/file_paths 文件,在这个文件中存放的是想要分享的目录。
2.2 添加共享目录
在 res/xml/file_paths.xml 文件,指名目录
<?xml version="1.0" encoding="utf-8"?> <resources> <files-path name="my_images" path="images/"/> </resources>
- 元素必须包含一到多个子元素,这些子元素用于指定共享文件的目录路径:
<file-path>
:内部存储空间应用私有目录下的 files/ 目录,即 Content.getFilesDir() 所获得的的路径
<cache-path>
:内部存储空间应用私有目录下的 cache/ 目录,即 Content.getCacheDir() 所获得的的路径
<external-path>
:外部存储空间根目录,即 Environment.getExternalStorageDirectory() 所获得的的路径
<external-file-path>
:外部存储空间应用私有目录下的 files/ 目录,即 Context.getExternalFilesDir(null) 所获得的的路径
<external-cache-path>
:外部存储空间应用私有目录下的 cache/ 目录,等同于 Context.getExternalCacheDir()- 上面的5种基本涵盖内外存储空间所有目录路径,包含应用私有目录。其中,都会有 name 和path 两个属性
path
:用于指定当前子元素所代表目录下需要共享的子目录名称。注意:path属性值不能直接使用具体的独立文件名,只能是目录名
name
:用于给path 属性所指定的子目录名称取一个别名。后续生成 content:// URI 时,会使用这个别名替代真实目录名。可以提供安全性。
2.3 生成 Content URI
在 Android 7.0 出现之前,通常使用 Uri.fromFile() 方式生成一个 File URI。现在这里,使用 FileProvider 类提供的公有静态方法 getUriForFile 生成 Content URI。
Uri contentUri = FileProvider.getUriForFile(this, BuildConfig.APPLICATION_ID + " myprovider", myFile);
其中,第2个参数是 Manifest 文件中注册 FileProvider 时设置的 authority 属性值;第3个参数是要共享的文件,并且这个文件一定位于第二步在 path 文件中添加的子目录中。
String filePath = Environment.getExternalStorageDirectory() + "/images/"+System.currentTimeMillis()+".jpg"; File outputFile = new File(filePath); if (!outputFile.getParentFile().exists()) { outputFile.getParentFile().mkdir(); } Uri contentUri = FileProvider.getUriForFile(this, BuildConfig.APPLICATION_ID + ".myprovider", outputFile);
生成的 Content URI:
【content://com.yifeng.samples.myprovider/my_images/1493715330339.jpg】
其中,构成URI的【host 】部分为 <provider> 元素的 authorties 属性值(applicationId + customname);【path】片段 my_images 为 res/xml 问文件中指定的子目录别名(真实名为 images)
2.4 授予访问权限
生成 Content URI 对象后,需要对其授权访问权限。授权方式有两种:
- 第一种方式,使用 Context 提供的 grantUriPermission(package, Uri, mode_flags) 方法向其他应用授权访问 URI 对象。三个参数分别表示授权访问 URI 对象的其他应用包名,授权访问的 Uri 对象,和授权类型。其中,授权类型为 Intent 类提供的读写类型常量:
FLAG_GRANT_READ_URI_PERMISSION FLAG_GRANT_WRITE_URI_PERMISSION
或者二者同时授权。这种形式的授权方式,权限有效期截止至发生设备重启或者手动调用 revokeUriPermission() 方法撤销授权时。- 第二种方式,配合 Intent 使用。通过 setData() 方法向 intent 对象添加 Content URI。然后使用 setFlags() 或者 addFlags() 方法设置读写权限,可选常量值同上。这种形式的授权方式,权限有效期截止至其它应用所处的堆栈销毁,并且一旦授权给某一个组件后,该应用的其它组件拥有相同的访问权限。
2.5 提供 Content URI 给其它应用
拥有授予权限的 Content URI 后,便可以通过 startActivity() 或者 setResult() 方法启动其他应用并传递授权过的 Content URI 数据。当然,也有其他方式提供服务。
如果你需要一次性传递多个 URI 对象,可以使用 intent 对象提供的 setClipData() 方法,并且 setFlags() 方法设置的权限适用于所有 Content URIs。