Android 分区存储常见问题解答

Android 分区存储常见问题解答

要在 Google Play 上发布,开发者需要将应用的 目标 API 级别 (targetSdkVersion) 更新到 API 级别 30 (Android 11) 或者更高版本。针对新上架的应用,这个政策自 8 月开始生效;现有应用更新新的版本,这个政策的要求将自 11 月开始生效。

API 30 所带来的一个巨大变更是,应用需要使用分区存储 (Scoped Storage)。

变更之大,对于大型应用来说堪称恐怖。更糟糕的是,我们在网上看到的有关如何适配分区存储的建议,有一些建议十分令人迷惑,甚至会误导我们。

为了帮您排忧解难,我们收集了一些有关分区存储的常见问题,同时也为如何适配您的应用提供了一些建议和可能的替代方案。

Q: android:requestLegacyStorage 会被移除吗?

A: 部分移除。

如果您的应用当前已经设置了 android:requestLegacyStorage="true",就应该在 targetSdkVersion 设置为 30 后保持现状。该标记在 Android 11 设备中没有任何效果,但是可以继续让应用在 Android 10 设备上以旧的方式访问存储。

如果您需要针对 Android 10 设备在 AndroidManifest.xml 中设置 android:requestLegacyStorage="true",那在应用的目标版本改为 Android 11 后应当保留此设置。它仍会在 Android 10 设备上生效。

Q: android:preserveLegacyStorage 是如何工作的?

A: 如果您的应用安装在 Android 10 设备上,并设置了 android:requestLegacyStorage="true",那在设备升级至 Android 11 后,此设置会继续保持旧的存储访问方式。

⚠️ 如果应用被卸载,或者是第一次在 Android 11 上安装,那么就无法使用旧的存储访问方式。此标记仅适用于进一步帮助设备从传统存储升级到分区存储。

Q: 如果我的应用没有访问照片、视频或音频文件,是否仍然需要请求 READ_EXTERNAL_STORAGE 权限?

A: 不需要,从 Android 11 开始,仅在访问其他应用所属的媒体文件时才需要请求 READ_EXTERNAL_STORAGE 权限。如果您的应用仅使用自身创建的非媒体文件 (或自身创建的媒体文件),那么就不再需要请求该权限。

如需在 Android 11 后停止请求该权限,仅需修改应用 AndroidManifest.xml 文件中的 <uses-permission> 标签,添加 android:maxSdkVersion="29" 即可:

<uses-permission
   android:name="android.permission.READ_EXTERNAL_STORAGE"
   android:maxSdkVersion="29" />

Q: 我想要访问不属于我应用的照片、视频或一段音频,我必须使用系统文件选择器吗?

A: 不。但如果您想用就可以用,ACTION_OPEN_DOCUMENT 最早可支持至 Android KitKat (API 19),而 ACTION_GET_CONTENT 则支持至 API 1,二者使用的都是系统文件选择器。由于不需要任何权限,这仍然是首选的解决方案。

如果您不想使用系统文件选择器,您仍然可以请求 READ_EXTERNAL_STORAGE 权限,它会使您的应用可以访问所有的照片、视频以及音频文件,同时也包含访问 File API 的权限!

如果您需要使用 File API 访问媒体内容,记得设置 android:requestLegacyStorage="true",否则 File API 在 Android 10 中将无法工作。

Q: 我想保存非媒体文件,但我不想在卸载我的应用时删除它们。我需要使用 SAF 吗?

A: 也许需要。

如果这些文件允许在应用外打开而无需通过您的应用,那么系统文件选择器是较好的选择。您可以使用 ACTION_CREATE_DOCUMENT 创建文件。当然也可以使用 ACTION_OPEN_DOCUMENT 来打开一个现有文件。

如果应用曾经创建了一个目录用于存储所有这些文件,那最好的选择就是使用系统文件选择器和 ACTION_OPEN_DOCUMENT_TREE,以便用户可以选择要使用的特定文件夹。

如果这些文件只对您的应用有意义,可以考虑在应用 AndroidManifest.xml 文件的 <application> 标签中设置 android:hasFragileUserData="true"。这将使用户可以保留这些数据,即使在卸载应用时亦是如此。

上图为拥有 "脆弱用户数据" 应用的卸载对话框。对话框中包含了一个复选框,用于指示系统是否应该保留应用数据。

△ 上图为拥有 "脆弱用户数据" 应用的卸载对话框。对话框中包含了一个复选框,用于指示系统是否应该保留应用数据。

设置了该标记后,存储文件的最佳位置将取决于其内容。包含敏感或私人信息的文件应当存储在 Context#getFilesDir() 所返回的目录中;而不敏感的数据则应存储于 Context#getExternalFilesDir() 所返回的目录中。

Q: 我可以将非媒体文件放置于其他文件夹中 (例如 Downloads 文件夹),而无需任何权限。这是一个 Bug 吗?

A: 不是。应用可能会向这类集合提供文件,而且最好的方式是对非媒体文件同时使用 Downloads 和 Documents 集合。不过请记得,默认情况下只有创建该文件的应用才可以访问它们。其他应用需要通过系统文件选择器获得访问权限或者拥有对外部存储的广泛访问权限 (即: MANAGE_EXTERNAL_STORAGE 权限) 才行。

⚠️ 对 MANAGE_EXTERNAL_STORAGE 权限的访问受到 Play 政策 监管。

Q: 如果我需要保存一个文档,是否需要使用 SAF?

A: 不用。应用可以向 Documents 与 Downloads 集合提供非媒体文件,而无需任何特殊权限。只要没被卸载,那么向这些集合提供文档的应用拥有这些文档的完全访问权限。

💡 如果您的应用为了上面提到的方式保存文档而请求 READ_EXTERNAL_STORAGE 权限的话,在 Android 11 及更高版本中将不必再请求该权限。您可以参考下面的示例修改对该权限的请求 (设定 maxSdkVersion 为 API 版本 29):

<uses-permission
   android:name="android.permission.READ_EXTERNAL_STORAGE"
   android:maxSdkVersion="29" />

如要访问其他应用添加的文档,或者在您的应用卸载重装后访问其卸载前添加的文档,就需要通过 ACTION_OPEN_DOCUMENT Intent 来使用系统文件选择器。

Q: 我想要与其他应用共享文件,是否需要使用 SAF?

A: 不需要。如下是一些与其他应用共享文件的方式:

  • 直接分享: 使用 Intent.ACTION_SEND 可以让您的用户通过各种格式与设备上的其他应用共享数据。如果您使用这种方式,使用 AndroidX 的 FileProvider 来将 file:// Uri 自动转换为 content:// Uri 可能会对您有所帮助。
  • 创建您自己的 DocumentProvider: 这可以让您的应用继续处理应用的私有目录 ( Context#getFilesDirs() 或 Context#getExternalFilesDirs()) 中内容的同时,仍可以向使用系统文件选择器的其他应用提供访问权限。(请注意,可以在卸载应用后继续保存这些文件——参阅上文中的 android:hasFragileUserData="true" 设置来了解其使用方式。)

最后的思考

Scoped Storage 是一项旨在改善用户隐私保护的重大变更。不过仍然有很多方法可以处理不依赖使用存储访问框架 (Storage Access Framework) 的内容。

如果要存储的数据仅适用于您的应用,那么我们强烈建议使用 应用特定目录

如果数据是媒体文件,例如照片、视频或者音频,那么可以 使用 MediaStore。注意,从 Android 10 开始,提供内容 不再需要请求权限

也别忘了可以通过 ACTION_SEND 来与 其他应用共享数据 (或允许它们 与您的应用共享数据)!

版权声明

禁止一切形式的转载-禁止商用-禁止衍生 申请授权

脉脉不得语
脉脉不得语
Zhengzhou Website
Android Developer | https://androiddevtools.cn and https://androidweekly.io WebMaster | GDG Zhengzhou Funder & Ex Organizer | http://Toast.show(∞) Podcast Host

你已经成功订阅到 Android 开发技术周报
太棒了!接下来,完成检验以获得全部访问权限 Android 开发技术周报
欢迎回来!你已经成功登录了。
Unable to sign you in. Please try again.
成功!您的帐户已完全激活,您现在可以访问所有内容。
Error! Stripe checkout failed.
Success! Your billing info is updated.
Error! Billing info update failed.