开发中,如果我们在使用 file:// URI 时忽视了这两条规定,将导致用户在 7.0 及更高版本系统的设备中使用到相关功能时,出现 FileUriExposedException 异常,导致应用出现崩溃闪退问题。而这两个过程的替代解决方案便是使用
。
- FileProvider
作为四大组件之一的
,一直扮演着应用间共享资源的角色。这里我们要使用到的
- ContentProvider
,就是
- FileProvider
的一个特殊子类,帮助我们将访问受限的 file:// URI 转化为可以授权共享的 content:// URI。
- ContentProvider
第一步,注册一个 FileProvider
作为系统四大组件之一的 ContentProvider,其子类 FileProvider,也同样需要使用 元素在 Manifest 文件中添加注册信息,并按照要求设置相关属性值。
|
|
其中,
属性值是一个由 build.gradle 文件中的 applicationId 值和自定义的名称组成的 Uri 字符串(这样写是约定俗成的)。其他属性值使用如上固定值即可。
- android:authorities
第二步,添加共享目录
在 res/xml 目录下新建一个 xml 文件,用于存放应用需要共享的目录文件。这个 xml 文件的内容类似这样:
|
|
元素必须包含一到多个子元素。这些子元素用于指定共享文件的目录路径,必须是这些元素之一:
:内部存储空间应用私有目录下的 files/ 目录,等同于
- <files-path>
所获取的目录路径;
- Context.getFilesDir()
:内部存储空间应用私有目录下的 cache/ 目录,等同于
- <cache-path>
所获取的目录路径;
- Context.getCacheDir()
:外部存储空间根目录,等同于
- <external-path>
所获取的目录路径;
- Environment.getExternalStorageDirectory()
:外部存储空间应用私有目录下的 files/ 目录,等同于
- <external-files-path>
所获取的目录路径;
- Context.getExternalFilesDir(null)
:外部存储空间应用私有目录下的 cache/ 目录,等同于 Context.getExternalCacheDir();
- <external-cache-path>
可以看出,这五种子元素基本涵盖内外存储空间所有目录路径,包含应用私有目录。同时,每个子元素都拥有 name 和 path 两个属性。
其中,path 属性用于指定当前子元素所代表目录下需要共享的子目录名称。注意:path 属性值不能使用具体的独立文件名,只能是目录名。
而 name 属性用于给 path 属性所指定的子目录名称取一个别名。后续生成 content:// URI 时,会使用这个别名代替真实目录名。这样做的目的,很显然是为了提高安全性。
如果我们需要分享的文件位于同级别目录下不同的子目录中,就需要添加多个子元素逐一指定要分享的文件目录,或者共享他们通用的父目录也行。
添加完共享目录后,再在
元素中使用
- <provider>
元素将 res/xml 中的 path 文件与注册的 FileProvider 链接起来:
- <meta-data>
|
|
第三步,生成 Content URI
在 Android 7.0 出现之前,我们通常使用
方法生成一个 File URI。这里,我们需要使用
- Uri.fromFile()
类提供的公有静态方法
- FileProvider
生成 Content URI。比如:
- getUriForFile
|
|
需要传递三个参数。第二个参数便是 Manifest 文件中注册 FileProvider 时设置的 authorities 属性值,第三个参数为要共享的文件,并且这个文件一定位于第二步我们在 path 文件中添加的子目录里面。
举个例子:
|
|
生成的 Content URI 是这样的:
|
|
其中,构成 URI 的 host 部分为
元素的 authorities 属性值(applicationId + customname),path 片段 my_images 为 res/xml 文件中指定的子目录别名(真实目录名为:images)。
- <provider>
第四步,授予 Content URI 访问权限
生成 Content URI 对象后,需要对其授权访问权限。授权方式有两种:
第一种方式,使用 Context 提供的
方法向其他应用授权访问 URI 对象。三个参数分别表示授权访问 URI 对象的其他应用包名,授权访问的 Uri 对象,和授权类型。其中,授权类型为 Intent 类提供的读写类型常量:
- grantUriPermission(package, Uri, mode_flags)
或者二者同时授权。这种形式的授权方式,权限有效期截止至发生设备重启或者手动调用
方法撤销授权时。
- revokeUriPermission()
第二种方式,配合 Intent 使用。通过
方法向 intent 对象添加 Content URI。然后使用
- setData()
或者
- setFlags()
方法设置读写权限,可选常量值同上。这种形式的授权方式,权限有效期截止至其它应用所处的堆栈销毁,并且一旦授权给某一个组件后,该应用的其它组件拥有相同的访问权限。
- addFlags()
第五步,提供 Content URI 给其它应用
拥有授予权限的 Content URI 后,便可以通过
或者
- startActivity()
方法启动其他应用并传递授权过的 Content URI 数据。当然,也有其他方式提供服务。
- setResult()
如果你需要一次性传递多个 URI 对象,可以使用 intent 对象提供的
方法,并且
- setClipData()
方法设置的权限适用于所有 Content URIs。
- setFlags()
前面介绍的内容都是理论部分,在 开发者官方 FileProvider 部分 都有所介绍。接下来我们看看,实际开发一款应用的过程中,会经常遇见哪些 FileProvider 的使用场景。
自动安装文件
版本更新完成时打开新版本 apk 文件实现自动安装的功能,应该是最常见的使用场景,也是每个应用必备功能之一。常见操作为,通知栏显示下载新版本完毕,用户点击或者监听下载过程自动打开新版本 apk 文件。适配 Android 7.0 版本之前,我们代码可能是这样:
|
|
现在为了适配 7.0 及以上版本的系统,必须使用 Content URI 代替 File URI。
在 res/xml 目录下新建一个 file_provider_paths.xml 文件(文件名自由定义),并添加子目录路径信息:
|
|
然后在 Manifest 文件中注册 FileProvider 对象,并链接上面的 path 路径文件:
|
|
修改 java 代码,根据 File 对象生成 Content URI 对象,并授权访问:
|
|
如此这般,便完成了应用中调用系统功能打开 apk 文件的 7.0 适配工作。
调用系统拍照
调用系统拍照功能时也需要传递一个 Uri 对象,用于保存图片至指定目录,这里也需要适配 7.0 版本。其他步骤不再赘述,核心 java 代码如下(路径不同,注意添加 res/xml 中的 path 文件子目录):
|
|
调用系统裁剪
调用系统裁剪的过程中涉及到两个 Uri 对象:inputUri 和 outputUri,较为复杂一些。通常,调用系统裁剪的来源为调用系统拍照或选择系统相册。前者返回的是一个 File URI 对象,后者返回的是一个 Content URI 对象。作为裁剪源,我们要做的就是对其做进一步处理。但是不能像上面那样使用
方法,这个并不难理解,因为如果是选择系统相册所得的图片,本身也不一定属于我们自己的应用。正确处理方式是这样:
- getUriForFile()
|
|
拿到正确的 Content URI 后,作为 inputUri,传递给 Intent 对象:
|
|
注意:这里的 outputUri 并没有改变,仍然使用的是
方法获取的 File URI 类型!这是很奇怪的一点,但是不得不这么做。事实上,使用这种方式调用系统裁剪功能本身就是有问题的!Crop Intent 在官方文档中本来就无迹可寻,本身就是一种不推荐的用法!取而代之的是,我们可以使用 GitHub 上的一些开源库实现应用内的图片裁剪功能,比如 uCrop、cropper 等。
- Uri.fromFile()
说了这么多,还有一个大家比较关心的问题就是:哪些已经上线的旧版本应用没有做 7.0 适配工作怎么办?关于这个问题,Google 已经提前帮我们想好解决方案啦。
还记得 6.0 运行时权限问题吗?如果你不想处理运行时权限事宜的话,只需要在 build.gradle 文件中将 targetSdkVersion 的值设为 23 以下即可。
同样的,只要 targetSdkVersion 值小于 24,File URI 的使用依旧可以出现在 7.0 及以上版本的设备中。不过需要注意的是,如前面所述,调用系统裁剪功能比较特殊,可能会出现一些问题。
虽然 Google 在每次发布新版 Android 系统时,都提供这种设置 targetSdkVersion 的方式兼容旧版本,但只是一种临时解决方案,并不推荐大家使用这种技巧绕开新版本的适配问题。要知道,新出现的 API 改变一定是在解决过去存在的系统问题,是一种进步的表现。遵循规范,是我们每个开发人员开发时都应铭记于心的格言。
本文由 亦枫 创作并首发于 亦枫的个人博客
写在最后:FOR Freedom 看看外边的世界,以及 IT 这一行,少不了去 Google 查资料,最后,安利一些速器代理。
加速器推荐 | 免费方案 | 付费方案 | 官方网站 |
一枝红杏加速器 | 免费方案暂无,稳定高速 | 输入 8 折优惠码 wh80,年付只需 80 元 / 年 | 官网直达 |
安云加速器 | 最好用的外贸 VPN | 最低¥30 / 月 | 官网直达 |
LoCo 加速器 | 每天免费 2 小时 | 最低¥15 / 月 | 官网直达 |
来源: http://www.bubuko.com/infodetail-2055210.html