深入浅出 Android 存储(二):权限篇 - 掌控存储访问的钥匙

1,439 阅读5分钟

Android 的权限管理经历了重大变革:

  1. 安装时授权(Android 5.0 之前)

    • 应用安装时一次性请求所有权限
    • 用户只能选择"全部接受"或"放弃安装"
    • 导致过度授权和隐私泄露风险
  2. 运行时权限(Runtime Permissions,Android 6.0+)

    • 引入危险权限(Dangerous Permissions) 概念
    • 应用在运行时请求关键权限
    • 用户可以按需授予或拒绝单个权限
    • 存储权限属于典型的危险权限
// 运行时权限请求示例
if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) 
    != PackageManager.PERMISSION_GRANTED) {
    
    // 解释为何需要权限(可选)
    if (ActivityCompat.shouldShowRequestPermissionRationale(this, 
            Manifest.permission.READ_EXTERNAL_STORAGE)) {
        // 显示解释UI
    }
    
    // 实际请求权限
    ActivityCompat.requestPermissions(this,
            new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
            REQUEST_CODE_STORAGE);
}

二、存储权限全景图

1. 核心权限详解

权限引入版本作用范围分区存储下的变化使用场景
READ_EXTERNAL_STORAGEAPI 16读取公共存储区域API 29+ 仅能访问媒体文件读取公共图片/视频/音频文件
WRITE_EXTERNAL_STORAGEAPI 4写入公共存储区域API 29+ 仅能修改应用自己创建的文件保存文件到公共目录
READ_MEDIA_IMAGESAPI 33读取图片和照片替代 READ_EXTERNAL_STORAGE 的图片部分访问用户相册
READ_MEDIA_VIDEOAPI 33读取视频文件替代 READ_EXTERNAL_STORAGE 的视频部分访问用户视频
READ_MEDIA_AUDIOAPI 33读取音频文件替代 READ_EXTERNAL_STORAGE 的音频部分访问用户音乐库
MANAGE_EXTERNAL_STORAGEAPI 30访问所有文件(特殊权限)绕过分区存储限制文件管理器、备份应用等

2. 权限与Android版本的兼容矩阵

图表

deepseek_mermaid_20250630_b35809.png

3. MANAGE_EXTERNAL_STORAGE:最后的核选项

特性:

  • 允许应用访问设备上所有文件(包括其他应用的文件)
  • 用户必须在系统设置中手动启用
  • Google Play 有严格的审核政策
  • 仅适用于文件管理器、备份应用等特定类型

声明方式:

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

检查是否已授权:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
    if (!Environment.isExternalStorageManager()) {
        // 引导用户前往设置页面
        Intent intent = new Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION);
        intent.setData(Uri.parse("package:" + getPackageName()));
        startActivity(intent);
    }
}

使用忠告:  除非绝对必要,否则不要使用此权限。Google Play 会严格审查使用此权限的应用,普通应用很可能被拒绝上架。

三、运行时权限最佳实践

1. 完整的权限请求流程

deepseek_mermaid_20250630_551c37.png

2. 权限请求代码模板

// 检查权限状态
when {
    // 情况1:权限已授予
    ContextCompat.checkSelfPermission(
        context,
        permission
    ) == PackageManager.PERMISSION_GRANTED -> {
        performStorageOperation()
    }
    
    // 情况2:需要显示解释
    ActivityCompat.shouldShowRequestPermissionRationale(
        activity,
        permission
    ) -> {
        showRationaleDialog(permission)
    }
    
    // 情况3:直接请求权限
    else -> {
        ActivityCompat.requestPermissions(
            activity,
            arrayOf(permission),
            requestCode
        )
    }
}

// 处理权限结果
override fun onRequestPermissionsResult(
    requestCode: Int,
    permissions: Array<out String>,
    grantResults: IntArray
) {
    when (requestCode) {
        REQUEST_CODE -> {
            if (grantResults.isNotEmpty() && 
                grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                // 权限已授予
            } else {
                // 处理拒绝
                if (!shouldShowRequestPermissionRationale(permission)) {
                    // 用户勾选"不再询问"
                    showSettingsDialog()
                } else {
                    // 普通拒绝
                    showDenialMessage()
                }
            }
        }
    }
}

3. 处理用户拒绝的优雅方案

  1. 首次拒绝:轻量提示,解释权限必要性
  2. 再次拒绝:提供更详细的解释和引导
  3. 勾选"不再询问" :引导用户前往应用设置手动开启权限
fun showSettingsDialog() {
    AlertDialog.Builder(this)
        .setTitle("需要存储权限")
        .setMessage("您已永久拒绝存储权限,请在设置中手动启用")
        .setPositiveButton("去设置") { _, _ ->
            val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {
                data = Uri.fromParts("package", packageName, null)
            }
            startActivity(intent)
        }
        .setNegativeButton("取消", null)
        .show()
}

四、权限与分区存储的协同规则

1. 权限作用域的变化

Android 10(API 29)之前:

  • READ_EXTERNAL_STORAGE:可访问所有公共文件
  • WRITE_EXTERNAL_STORAGE:可写入所有公共区域

Android 10+(分区存储):

  • 即使拥有权限,也无法通过文件路径直接访问公共文件
  • 访问媒体文件必须通过MediaStore API
  • 写入位置受限:只能写入特定媒体集合

2. 各区域访问权限总结

存储区域访问方式所需权限备注
内部存储直接访问无需权限应用私有
应用专属外部存储直接访问无需权限 (API≥19)卸载时自动清理
公共媒体文件MediaStore APIAPI 33+: READ_MEDIA_* API 29-32: READ_EXTERNAL_STORAGE分区存储强制要求
公共非媒体文件SAF需用户主动选择
所有文件直接访问MANAGE_EXTERNAL_STORAGE需用户手动开启

3. 真实场景权限需求

  1. 保存图片到相册

    // 无需 WRITE_EXTERNAL_STORAGE!
    val values = ContentValues().apply {
        put(MediaStore.Images.Media.DISPLAY_NAME, "my_image.jpg")
        put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg")
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            put(MediaStore.Images.Media.RELATIVE_PATH, Environment.DIRECTORY_PICTURES)
        }
    }
    
    val resolver = context.contentResolver
    val uri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values)
    
  2. 读取用户选择的文件

    // 使用SAF不需要声明任何权限
    Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
    intent.addCategory(Intent.CATEGORY_OPENABLE);
    intent.setType("image/*");
    startActivityForResult(intent, REQUEST_CODE_OPEN);
    

五、常见陷阱与解决方案

1. 权限请求的时机错误

错误做法:  应用启动时立即请求所有权限

正确方案:  在用户执行需要权限的操作时才请求(按需请求)

2. 忽略"不再询问"状态

解决方案:

if (!shouldShowRequestPermissionRationale(permission)) {
    // 引导用户前往设置
    showSettingsDialog();
}

3. 适配不同Android版本时权限处理混乱

健壮方案:

fun requestNeededPermissions() {
    val permissionsToRequest = mutableListOf<String>()
    
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
        // Android 13+ 使用细化权限
        if (!hasPermission(Manifest.permission.READ_MEDIA_IMAGES)) {
            permissionsToRequest.add(Manifest.permission.READ_MEDIA_IMAGES)
        }
    } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
        // Android 10-12 使用READ_EXTERNAL_STORAGE
        if (!hasPermission(Manifest.permission.READ_EXTERNAL_STORAGE)) {
            permissionsToRequest.add(Manifest.permission.READ_EXTERNAL_STORAGE)
        }
    }
    // Android 9及以下使用老模式
    
    if (permissionsToRequest.isNotEmpty()) {
        requestPermissions(permissionsToRequest.toTypedArray(), REQUEST_CODE)
    }
}

4. 过度依赖 MANAGE_EXTERNAL_STORAGE

替代方案:

  • 使用应用专属存储存放私有文件
  • 使用MediaStore API访问媒体文件
  • 使用SAF让用户选择文件

六、权限测试清单

  1. 基础流程测试

    • 首次请求权限 → 允许 → 功能正常
    • 首次请求权限 → 拒绝 → 优雅降级
    • 二次请求权限 → 允许 → 功能正常
    • 二次请求权限 → 拒绝 → 不再提示
  2. 特殊场景测试

    • 勾选"不再询问"后的处理
    • 权限在设置中手动启用/禁用
    • 低版本Android(<6.0)的权限处理
    • Android 10-12 与 Android 13+ 的权限差异
  3. 边界情况测试

    • 权限允许后撤销
    • 同时请求多个权限
    • 权限请求过程中配置变更(旋转屏幕)

总结

理解并正确使用Android存储权限,是你构建健壮、合规应用的关键一步。记住这些核心原则:

  1. 最小权限原则:只请求真正需要的权限
  2. 按需请求:在用户需要时请求权限
  3. 优雅降级:妥善处理权限拒绝情况
  4. 尊重分区存储:优先使用MediaStore和SAF
  5. 远离MANAGE_EXTERNAL_STORAGE:除非绝对必要