欢迎光临
专注android技术,聚焦行业精粹,我们一直在努力

Android 文件存储系统适配

1、Android 文件存储系统发展历史

  • 从1.0起, 访问外部存储就需要申请权限WRITE_EXTERNAL_STORAGE
  • 从4.1开始如果想读外部存储也要再额外申请权限READ_EXTERNAL_STORAGE
  • 在4.4之前,android仅仅有一个设备称为‘external storage’从4.4起,可能会有多个外部存储设备,而且不同的设备会有不同的访问控制。其中一个外部存储是’primary’ external storage, 而其他的一个或者多个称为’secondary’ external storage;  4.4版本里面对于’primary external storage‘写入不需要申请权限;对于’scondary external storage‘不能再更改,除了app-specific folder(即:Android/data/your.package.name/)
  • Android在4.4上剥夺了第三方应用程序对SD Card的写权限,使得一大批的应用程序不能够再使用外置的SD Card,此举招来了骂声一片,很快就有人给Android提了一个Bug,要求重新允许第三方应用获得SD Card的写权限,并且最终有1682个人关注了这一Bug,迫于压力,Google终于在5.0上Android又打开了一扇通往SD Card的大门:
  • 4.4 版本上Google给出的解决方案就是通过刚刚在4.4 (Kitkat)中引入的Storage Access Framework(SAF)来让第三方应用向用户申请外置SD Card的读写权限,这样一来既保持了对第三方应用访问SD Card的限制,又赋予了用户更大的自主选择权
  • 5.0 基于SAF上还增加了选择目录并将其读写权限赋予应用的新功能
  • 6.0 访问SD卡需要动态申请权限
  • 7.0 不允许在App间使用file://的方式传递File,需要使用FileProvider
  • 8.0 在安装 APK 文件时新增 未知来源安装权限,即 android.permission.REQUEST_INSTALL_PACKAGES

2、FileProvider介绍

从 Android N(7.0) 开始,将严格执行 StrictMode 模式,也就是说,将对安全做更严格的校验。而从 Android N 开始,将不允许在 App 间,使用 file:// 的方式,传递一个 File ,否者会抛出 FileUriExposedException 的错误,会直接引发 Crash。
FileProvider 是 Android support v4 包下,提供的一个 ContentProvider 的子类,用于向其他 App 分享文件,并且是在 v4 包下,所以只要引入了 v4 包,就可以做到全版本兼容。

2.1、不引入support v4 使用FileProvider

FileProvider继承了ContentProvider,它关联的类非常少,我们可以不引入support v4而直接导入它关联的类即可。

1、FileProvider的路径可以在Android SDK路径下面的extras\android\m2repository\com\android\support 找到support-core-utils, 如下图所示:

2、解压support-core-utils-xxx-sources.jar 在android\support\v4\content里面可以找到FileProvider

3、FileProvider关联ContextCompat可以也一同拷贝出来。

这样单独剥离出FileProvider只关联到两部分:FileProviderContextCompat;其中ContextCompat会关联到一些不同版本的ContextCompat,例如ContextCompatKitKat,也一同拷贝出来即可。

 

2.2、Authorities

和ContentProvider一样,authorities是系统范围内用来唯一区分一个Provider实例的标识。所有的provider都会在系统中注册,如果有第二个具有相同authorities的provider去注册会注册失败。

2.3、授予临时的读写权限

在配置 provider 标签的时候,有一个属性 android:grantUriPermissions="true" ,它表示允许它授予 Uri 临时的权限。

当我们生成出一个 content:// 的 Uri 对象之后,其实也无法对其直接使用,还需要对这个 Uri 接收的 App 赋予对应的权限才可以。

而这个授权的动作,提供了两种方式来授权:

1、使用 Context.grantUriPermission() 为其他 App 授予 Uri 对象的访问权限。

grantUriPermission() 方法包含三个参数,这三个参数都非常的好理解。

  • toPackage :表示授予权限的 App 的包名。
  • uri:授予权限的 content:// 的 Uri。
  • modeFlags:前面提到的读写权限。

这种情况下,授权的有效期限,从授权一刻开始,截止于设备重启或者手动调用 Context.revokeUriPermission() 方法,才会收回对此 Uri 的授权。

 

2、配合 Intent.addFlags() 授权。

既然这是一个 Intent 的 Flag,Intent 也提供了另外一种比较方便的授权方式,那就是使用 Intent.setFlags() 或者 Intent.addFlag 的方式。

这种形式的授权,权限截止于该 App 所处的堆栈被销毁。也就是说,一旦授权,直到该 App 被完全退出,这段时间内,该 App 享有对此 Uri 指向的文件的对应权限,我们无法再主动收回此权限了。

3、一种权限异常场景

分析:遇到这种错误一般是分享给目标app的uri没有权限读取,但是我们已经给该uri用上面提到的2种方式赋予了权限,为啥还是没有权限读取呢?

原因:原因是赋予权限的App没有sd卡读写权限,所以它也就无法赋予权限给该Uri了。

解决方法:分享uri和赋予权限给uri前需要动态检查当前app是否有sd卡的权限。关于动态权限如何获取,参考这篇文章:android 6.0运行时权限的完整接入流程

2.4、FileProvider完整接入步骤

  1. 引入FileProvider(引入support-v4,或者单独拷贝出FileProvider类)
  2. manifest配置FileProvider
  3. 定义provider的文件路径映射资源
  4. 获取分享的文件uri,授予临时访问权限

2.4.1、引入FileProvider

2.1节已经介绍了不引入support-v4包使用FileProvider的方式

2.4.2、Manifest里面配置FileProvider

注意exported需要设置为false,grantUriPermissions需要设置为true

2.4.3、配置文件路径映射资源

文件路径映射的一个主要目的是对外隐藏文件的绝对路径,用一种别名的方式访问复杂的文件路径。

不同的存储路径可以通过不同的tag标识.

  • <files-path> 内部存储 internal storage 的子目录.
  • <external-path> 外部存储external storage 的子目录.
  • <cache-path> 缓存子目录.
更多存储路径标签参考这里:https://developer.android.com/reference/android/support/v4/content/FileProvider#SpecifyFiles
例如:我配置外部存储的子目录为我的下载目录:

2.4.4、获取文件Uri,并授予临时权限

使用FileProvider.getUriForFile方法获得指定path的uri,该方法有3个参数:

authority 代表你指定接收的FileProvider;

file 为一个File对象,是一个需要转换成content uri的本地文件。

举一个例子:把本地下载的apk发送给系统安装

注意点:

1、我们在Manifest里面配置FileProvider时,设置的authorities通常会是applicationId,因为applicationId在系统内是唯一的,和authorities对系统内唯一的要求是一致的。而我们在生成uri的时候没有直接获取applicationId的方法,所以我们通常用context.getPackageName方法代替,它们的获取结果是相同的。

2、我们生成uri后需要赋予该uri临时读取权限,不然对方app无法访问该uri。具体方式是通过setFlags或addFlags方法设置Intent.FLAG_GRANT_READ_URI_PERMISSION

3、7.0之前的版本调用系统安装,需要传schema为file的uri;7.0及7.0以上的版本需要用shema为content的uri

 

3、一个完整的接入案例

考虑到6.0到8.0版本系统对存储和安装的一些优化,我们想要实现一个从下载到安装的逻辑并非那么顺利,包含以下几个注意点:

  • 下载时需要动态申请外部存储的SD卡写权限
  • 下载完成后发送本地uri给系统安装时需要配置FileProvider
  • 发送Uri之前需要检查是否已经申请sd卡的权限,这样才能授予uri临时访问权限。不然系统安装器会提示“There was a problem parsing the package”
  • 调用系统安装时需要动态申请Manifest.permission.REQUEST_INSTALL_PACKAGES权限

可以看到我们在这个过程中需要频繁申请权限,我们可以把权限申请简单封装一个方法,可以让使用方操作更简单:

赞(0) 打赏
未经允许不得转载:花花鞋 » Android 文件存储系统适配
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

国内精品Android技术社区

联系我们

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏