Android 11+ 存储权限适配指南

当我尝试实现一个非常简单的需求时—— 下载图片并将其保存到本地存储 — 起初一切看起来都很正常。.

  • 荣耀(Android 10)——运行正常
  • Redmi(Android 11)——运行正常
  • 小米(Android 13)——运行正常
  • 三星(Android 13) 完全失败:存储权限对话框始终不会显示。

同样的代码,同样的功能,但一台运行 Android 13 的设备就是不显示权限提示。就这样,一个原本简单的“下载图片”任务,演变成了一场深入的调查。 作用域存储管理外部存储.

这篇文章总结了我如何调整存储权限。 Android 11 及以上版本, 以及我如何处理不同版本之间的不同行为。.


1. 漏洞:存储权限对话框始终不显示(三星安卓 13)

要求很简单:

下载图片并保存到设备中,使其显示在图库中。.

我的三台测试设备如下:

  • 荣耀 – 安卓 10 → 确定
  • Redmi – Android 11 → 确定
  • 小米 – Android 13 → 确定

但是, 运行 Android 13 的三星设备, 系统 从未显示过存储权限对话框, 无论我如何要求,都无济于事。.

起初我以为这只是另一个 OEM 厂商的怪癖,但在检查了 Android 各个版本之间的存储权限更改后,我意识到我所依赖的行为在 Android 13 上针对 SDK 33 时实际上已被弃用。.


2. 根本原因:WRITE/READ_EXTERNAL_STORAGE 在 Android 13 (SDK 33) 中已弃用

在旧版本的 Android 系统中,我们可以直接在清单文件中声明这两个权限:

  • 读取外部存储
  • 写入外部存储

然后根据需要,在运行时请求它们。.

在 Android 13 (SDK 33) 上 targetSdkVersion = 33, 这种方法开始失效:

  • 写入外部存储已弃用且实际上无用 在最新的 Android 版本上
  • 如果你添加 maxSdkVersion=32 这些权限在 Android 11/12 上仍然有效。
    但他们是 忽略 在 Android 13 上,目标版本为 33
  • 同时,Play 商店要求新应用至少要面向 SDK 33。

因此,对于 Android 11 及更高版本,我们必须进行如下调整:

  • 作用域存储
  • 在某些情况下,还需要特殊许可: 管理外部存储

管理外部存储 赋予应用程序对所有共享存储内容(包括非媒体文件)的广泛访问权限。 不是 允许访问其他应用的私有目录,但 Google Play 仍然认为这是一项高度敏感的权限。.

为了支持不同的安卓版本,我最终将权限处理拆分为:

  • Android 11 之前版本(API < 30) 旧式外部存储权限
  • Android 11 及以上版本 – 范围存储 + 特殊处理 管理外部存储 在严格需要的情况下

3. 逐步适应

3.1 在清单文件中声明 MANAGE_EXTERNAL_STORAGE

AndroidManifest.xml:

<uses-permission
    android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
    tools:ignore="ScopedStorage" />

⚠️ 注意:因为 管理外部存储 这是一个敏感权限,在 Google Play 上受到限制。如果您只是想保存图片,我稍后会介绍其他方法。.


3.2 检查是否已授予权限

我使用 简易权限 简化权限检查。.

private fun checkPer(activity: PreViewActivity): Boolean { return if (Build.VERSION.SDK_INT >= 30) { EasyPermissions.hasPermissions( activity, android.Manifest.permission.MANAGE_EXTERNAL_STORAGE ) } else { EasyPermissions.hasPermissions( activity, android.Manifest.permission.WRITE_EXTERNAL_STORAGE ) } }
  • Android 11+(API >= 30)我检查 管理外部存储
  • Android 10 及以下版本我还在检查 写入外部存储

这种分裂至关重要,因为 写入外部存储 新版本中运行方式已不再相同。.


3.3 当权限缺失时请求权限

如果未获得权限,我会根据系统版本以不同的方式请求权限。.

private fun requestStoragePermission(activity: PreViewActivity, curImg: Int) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { // Android 11+ – 重定向到系统"所有文件访问权限"设置页面 val intent = Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION) intent.data = Uri.parse("package:" + activity.packageName) activity.startActivityForResult(intent, 200) } else { // Android 10 及以下 – 普通运行时权限 val perm = android.Manifest.permission.WRITE_EXTERNAL_STORAGE PaperThreeVariable.isToRequestPer = true EasyPermissions.requestPermissions( PermissionRequest.Builder( activity, 200, perm ).build() ) } }
  • Android 11+你不能直接“弹出”一个普通的运行时对话框 管理外部存储
    您必须引导用户进入系统设置页面,让他们手动授予“所有文件访问权限”。.
  • Android 10 及以下版本经典的运行时权限对话框仍然有效。.

3.4 处理权限回调

EasyPermissions 有助于连接 Activity 的回调函数和我们自己的逻辑:

override fun onRequestPermissionsResult( requestCode: Int, permissions: Array , grantResults: IntArray ) { super.onRequestPermissionsResult(requestCode, permissions, grantResults) EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this) } override fun onPermissionsGranted(requestCode: Int, perms: MutableList ) { AppInitUtils().saveFreshAppImageToGallery(this, curImg) PaperThreeVariable.isToRequestPer = false } override fun onPermissionsDenied(requestCode: Int, perms: MutableList ) { PaperThreeVariable.isToRequestPer = false if (EasyPermissions.somePermissionPermanentlyDenied(this, perms)) { AppSettingsDialog.Builder(this) .setRationale("此函数需要启用存储权限") .setNegativeButton("否") .setPositiveButton("是") .build() .show() } }

我在这里使用 EasyPermissions 的原因:

  • 用户可以 永久否认 权限问题,导致重复请求静默失败
  • EasyPermissions 让以下操作变得更简单:
    • 检测“永久拒绝”状态
    • 显示一个对话框,引导用户进行以下操作: 系统设置 → 应用权限 手动启用存储访问

获得许可后,我调用:

AppInitUtils().saveFreshAppImageToGallery(this, curImg)

实际保存图像并刷新图库。.

经过此次适配,三星 Android 13 设备终于与其他设备表现一致了。.

补充说明:我的小米设备显示为 Android 13,但 Android Studio 的“历史连接设备”却将其识别为 Android 12。这或许可以解释为什么在某些情况下它仍然可以工作——但这正是版本感知权限处理如此重要的原因。.


4. 关于 MANAGE_EXTERNAL_STORAGE 和 Google Play 限制

管理外部存储 功能强大:

它授予对以下内容的读/写权限 所有共享存储 在设备上。.

因此,Google Play 将其视为 高度敏感的许可:

  • 它主要用于 文件管理器/备份/杀毒软件 类型应用
  • 你必须提交使用理由才能使用它。
  • 如果您的应用只是典型的消费者应用(例如,保存图片、简单下载),那么您的请求很可能不会被批准。 拒绝

所以,如果你的唯一要求是:

“将图片保存到图库并使其对用户可见。”

那么你应该 强烈建议避免 管理外部存储 相反:

  • 使用 媒体商店 将图像插入系统媒体库
  • 或者使用无需完全文件访问权限即可保存图像的 API。

有几种模式:

  • 将图像保存到 Pictures/DCIM 目录
  • 通知媒体扫描器或依赖 MediaStore,以便画廊可以读取它。
  • 做所有这些 无需请求 MANAGE_EXTERNAL_STORAGE

对于内部或非 Play 商店分发(例如,企业内部应用商店),从技术上讲,您仍然可以使用 Environment.getExternalStorageDirectory(), 但我并不建议在 2025 年围绕这个主题设计一款新应用。.


5. 版本演进总结(Android 9 → 13)

为了将所有内容集中在一处,这里对外部存储和权限在不同版本中的行为方式进行了概述。.

Android 9 及以下版本(API 28 及更早版本)

  • 权限:
    • 读取外部存储
    • 写入外部存储
  • 行为:
    • 应用程序可以自由访问 /sdcard 及其子目录
    • 即使卸载了应用程序,该应用程序创建的文件仍会保留在设备上。
  • 典型方法:
    • 直接在外部存储路径下进行读写操作

Android 10 (API 29) – 引入了作用域存储

  • 权限:
    • 读取外部存储 仍然有效
    • 写入外部存储 它仍然存在,但其有效范围已缩小。
  • 行为:
    • 作用域存储 引入:
      • 应用程序仅限于其自身的应用程序专属目录。
        Android/data/你的软件包名称/
      • 直接访问其他应用程序的文件受到限制
    • 媒体文件(图像、视频、音频)应通过以下方式访问: 媒体商店
    • requestLegacyExternalStorage=true 可能会暂时保留旧的行为
      (但从 Android 11 开始,此标志将被忽略)
  • 推荐方法:
    • 对于图像/视频/音频:使用 媒体商店
    • 对于私人文件:请使用 getExternalFilesDir() 或者 getDataDir()

Android 11 (API 30) – 强制执行作用域存储

  • 权限:
    • 读取外部存储 有效,但仅适用于 MediaStore 管理的媒体。
    • 写入外部存储 有效地 过时的 用于一般外部存储
    • 管理外部存储 为特殊的“所有文件访问”用例而引入
  • 行为:
    • requestLegacyExternalStorage=true 不再有效;作用域存储是 始终开启
    • 访问 /sdcard/ root 权限被阻止
    • 应用程序只能:
      • 访问他们自己的私人目录
      • 通过以下方式访问共享媒体 媒体商店
  • 推荐方法:
    • 对于典型应用程序:
      • 使用 MediaStore 或 新加坡武装部队 (操作_打开文档, 操作_创建文档)针对用户选择的文件
    • 仅考虑 管理外部存储 如果你的应用确实是一款文件管理器、备份工具、安全应用等等。.

Android 13 (API 33) – 媒体权限拆分

  • 权限:
    • 读取媒体图像 – 访问图像
    • 读取媒体视频 – 访问视频
    • 读取媒体音频 – 访问音频
  • 行为:
    • 媒体权限是 细粒:
      • 用户可以仅授予图像访问权限、仅授予视频访问权限等等。.
    • Android 11 中的作用域存储规则仍然有效。
  • 推荐方法:
    • 请申请您需要的具体媒体授权:
      • 例如,如果您只处理图像,则只需请求即可。 读取媒体图像
    • 不是 要求 读取外部存储 在 Android 13 及更高版本中,该功能已被新的媒体权限取代。

快速矩阵(概念图)

  • Android 9 及以下版本
    • 对外部存储的访问范围很广,由 READ/WRITE_EXTERNAL_STORAGE 控制。
  • Android 10
    • 引入了范围存储,但也有退路(请求旧版外部存储)
  • Android 11
    • 强制执行范围存储,移除旧式交换机
    • 管理外部存储 出现但受到严格限制
  • Android 13
    • 媒体访问分为 READ_MEDIA_* 权限
    • 相同的作用域存储规则,但用户控制更加细粒度。

6. 要点总结

  • 不要以为“在一台 Android 13 设备上运行正常”就意味着它在所有设备上都能正常运行;OEM 厂商和系统报告可能存在不一致的情况。.
  • 为了 Android 11+, 可以从以下角度思考:
    • 应用私有目录 + 媒体库 + SAF, 不是“生的”。 /sdcard 使用权”
  • 对待 管理外部存储 作为一个 最后手段 尤其对于某些特定类型的应用,如果您计划在 Google Play 上发布,那就更需要注意了。.
  • 始终进行测试 多款设备和安卓版本, 尤其是在权限和存储方面。.

本文源于我在实际 Android 项目(包括运行 Android 13 的三星设备)中的调试和调整。GPT 仅协助翻译和润色文字;所有技术内容和决策均出自本人。.

关于作者

分享帖子:

用 AI 总结文章

目录

保持联系

更多更新