Android 10 Scoped Storage

7,269 阅读7分钟

之前在知乎看到一个问题:为什么安卓系统的文件夹如此凌乱?

相信所有使用 Android 手机的用户都会有这个疑问,打开自带的文件管理器,简直是一脸懵,十万个为什么瞬间涌上心头。

为什么会有这么多文件夹?

这些文件夹到底是干嘛的?

这个 app 我明明已经卸载了,为什么还有残留文件?

这些我看不懂的文件夹,到底能不能删除?

万一删除后我手机变砖了怎么办?

......

在该问题下面,得票最高的回答中的第一句话直中要害:因为开发者不遵守规范

今年发布的 Android 10 引入了全新的 Scoped Storage,恰巧 Android Dev Simmit 上面也提到了相关的内容,我会结合大会中的讲解以及我自己的理解,为大家带来 Scoped Storage 的介绍,以及作为开发者来说,哪些变化是需要我们注意的。没关注的小伙伴记得关注订阅鸭!如果觉得这些文章有点意思,记得分享转发评论点赞鸭!

我们回到最开头的问题:为什么 Android 系统文件夹如此混乱?那是因为一旦 App 拿到了 WRITE_EXTERNAL_STORAGE 这个权限之后,就可以在你的根目录下面肆意妄为的建立无数文件夹,根本原因就是因为开发者不遵守规范。

正确的规范

根据官方文档上面的介绍,我们有四种方式存储数据和文件:内部存储、外部存储、SharedPreferences 和数据库。今天我们所关注的地方是上图中画红框的两个,即内部存储和外部存储。为了防止有些同学不清楚这两个存储的定义和目的,我帮大家简单回顾一下。

内部存储

内部存储目录主要分为 4 部分,如上图所示。但我们常用的是文件目录和缓存目录,在 Kotlin 中可以分别通过 filesDir 和 cacheDir 获得。

内部存储用来存储应用的私有文件,且通常是应用的功能相关的必要文件。

其他应用(和用户)不能访问这些文件(除非拥有 Root 访问权限)。如此一来,内部存储便非常适合保存用户无需直接访问的内部应用数据。在文件系统中,系统会为每个应用提供私有目录,您可以在该目录中整理应用所需的任何文件。当用户卸载您的应用时,保存在内部存储中的文件也将随之移除。

同时内部存储中的缓存目录,帮我们暂时保留而非永久存储某些数据。系统会在内部存储空间不足时,通过删除这些缓存文件以回收空间。

内部存储目录对应的路径为 /data/data/<包名>/files

内部存储缓存目录对应的路径为 /data/data/<包名>/cache

外部存储

存储于外部存储中的文件意味着,这些文件不是应用程序中功能运行时的必要文件,但从技术上讲用户和其他应用程序都可以访问这些文件,但它们无法为应用程序外部的用户提供价值,此目录可视为内部存储的补充方案。

外部存储对应的路径为 /storage/Android/data/<包名>/files

根据上面的定义,无论内部存储或者外部存储都应该只保存与我们自己的 App 有关联的数据。例如使用内部存储保存用户信息、使用外部存储保存只有本 App 才能打开的专门格式的文件。需要注意当用户卸载 App 的时候,内部存储和外部存储都会被自动删除。

针对用户行为产生的文件,例如下载的图片、保存的视频等。Google 要求我们保存在系统公共目录中,这样别的 App 也能访问到这些文件,例如 Pictures、Downloads。这里我们把用户行为产生的文件分为两大类:多媒体文件和其他文件,官方推荐多媒体文件存放在系统中有专门的目录:Music、Movies、Pictures等,其他文件一律保存在下载目录中:Downloads。

所以到这里我想问一下那些 App 的开发者,你们在开发的时候有读过官方文档吗?

前面我也讲到了在 Android 10.0 之前,存储文件需要获取 WRITE_EXTERNAL_STORAGE 权限,得到这个权限之后,App 就可以通过 Environment.getExternalStorageDirectory() 在根目录下面随意创建文件了,但是(划重点!)Android 10.0 之后就不行了,没想到吧?

Android 10 Scoped Storage

在 Android 10 上面,上图中的两个访问根目录的 API 已经被弃用了。

执行上面这段创建文件夹的代码根本不会其任何作用,这样 App 就再也无法肆意创建文件夹了。我们之前说了存储文件需要 WRITE_EXTERNAL_STORAGE 权限,在 Android Q 中我们操作内部和外部存储时,不再需要声明任何权限,可以直接使用。

同时文档中也明确规定了,多媒体文件需要通过 MediaStore API 访问,其他文件通过系统内置文件管理器访问(Storage Access Framework API)。

因为官方意识到,大多数 App 申请了权限之后,仅是为了操作外部存储目录,所以他们不需要这么多的访问空间,同时也正是因为之前的存储权限逻辑,给了其他 App 漏洞去肆意的乱建文件夹,从而引发了用户数据泄漏。所以 Google 取消了外部存储权限限制,同时也增加了公共目录的访问限制。

所以 Scoped Storage 的设计原则就是:更好的管理文件、保护 App 的数据、保护用户的数据。

说了这么多,我们来看一下 Scoped Storage 具体有哪些修改,以及我们需要注意的地方,这里我列出来一些重要的点供大家参考:

当然 Scoped Storage 也不是马上强制执行,Google 给了一定的缓冲期让开发者们有时间充分适配。

  1. targetSDK = 29, 默认开启 Scoped Storage, 但可通过在 manifest 里添加 requestLegacyExternalStorage = true 关闭

  2. targetSDK < 29, 默认不开启 Scoped Storage, 但可通过在 manifest 里添加requestLegacyExternalStorage = false 打开

如果你所做的 App 属于文件管理器或数据备份应用,你需要在 Google Play 提交申请信息,获得白名单权限之后才可以访问除自己应用以外的文件。

这是官方依据 Scoped Storage 所划分的存储区域访问图。目前 Scoped Storage 部分内容还在调整,所以在现在的 Android 10 上面并没有强制执行,这一切调整会在 Android 下一个 Release 上完善并开启。

关于 Scoped Storage 的信息很杂也很碎,但是我们只需要关注两点就好了:

  1. 多媒体文件需要使用 MediaStore API 访问

  2. 其他文件需要使用 Storage Access Framework API 访问

所以我们 App 之后的适配工作也需要密切关注这两个 API 的变化和更新,由于我还没有仔细阅读这两个 API 的文档做适配,所以暂时也就没有多余的适配代码分享给大家,避免错误使用误导大家,如果有哪些同学已经开始了适配工作,欢迎写一篇文章并私信我,和大家一起分享你在适配过程中的心得。

明天的推送中,我会为大家带来关于【Fragment 的现在以及未来】的最近进展,没关注的小伙伴记得关注我以及我的公众号【Android丨Kotlin】鸭!如果觉得这些文章有点意思,记得分享转发评论点赞鸭!!

我是 wanbo 大家加油!