Skip to content

Android 13 存储权限处理指南

问题概述

Android 13(API 级别 33)引入了重大的存储权限变更。根据官方行为变更说明,如果你的应用以 Android 13 为目标平台,必须使用新的细粒度媒体权限,而不是传统的 READ_EXTERNAL_STORAGEWRITE_EXTERNAL_STORAGE 权限。

新增的三个媒体权限包括:

  • READ_MEDIA_IMAGES - 访问图片和照片
  • READ_MEDIA_VIDEO - 访问视频文件
  • READ_MEDIA_AUDIO - 访问音频文件

但关键问题是:如何处理非媒体文件(如 PDF、Excel、文档等)的访问?官方文档主要关注媒体文件,对其他文件类型的具体处理方式缺乏明确说明。

解决方案

1. 针对不同 Android 版本的处理策略

xml
<!-- 针对 Android 13+ 的媒体权限 -->
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />

<!-- 针对 Android 10 及以下版本的权限 -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

<!-- 针对 Android 11+ 的全文件访问权限 -->
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
kotlin
// Kotlin 示例
fun requestStoragePermissions(activity: Activity, requestCode: Int) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
        // Android 11+ 使用 MANAGE_EXTERNAL_STORAGE
        if (!Environment.isExternalStorageManager()) {
            val uri = Uri.parse("package:${activity.packageName}")
            val intent = Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION, uri)
            activity.startActivityForResult(intent, requestCode)
        }
    } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
        // Android 10 通常不需要存储权限(特殊情况除外)
        // 访问应用自有文件不需要权限
    } else {
        // Android 9 及以下版本
        ActivityCompat.requestPermissions(
            activity,
            arrayOf(
                Manifest.permission.READ_EXTERNAL_STORAGE,
                Manifest.permission.WRITE_EXTERNAL_STORAGE
            ),
            requestCode
        )
    }
}
java
// Java 示例
private void requestStoragePermissions(Activity activity, int requestCode) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
        if (!Environment.isExternalStorageManager()) {
            Uri uri = Uri.parse("package:" + activity.getPackageName());
            Intent intent = new Intent(
                Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION, 
                uri
            );
            activity.startActivityForResult(intent, requestCode);
        }
    } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
        // Android 10: 通常不需要权限
    } else {
        ActivityCompat.requestPermissions(
            activity,
            new String[]{
                Manifest.permission.READ_EXTERNAL_STORAGE,
                Manifest.permission.WRITE_EXTERNAL_STORAGE
            },
            requestCode
        );
    }
}

2. 使用系统选择器访问文件

对于非媒体文件的访问,推荐使用系统提供的文件选择器,这不需要请求存储权限:

kotlin
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
    addCategory(Intent.CATEGORY_OPENABLE)
    type = "*/*"  // 所有文件类型
    // 或指定特定类型:type = "application/pdf"
}
startActivityForResult(intent, REQUEST_CODE_OPEN_DOCUMENT)
kotlin
// 请求访问整个目录的权限
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
startActivityForResult(intent, REQUEST_CODE_OPEN_DOCUMENT_TREE)

注意事项

  • ACTION_OPEN_DOCUMENT_TREE 无法访问某些系统文件夹(如下载文件夹)
  • 用户通过选择器授予的访问权限是持久的,即使应用重启后依然有效

3. 针对不同文件类型的处理建议

文件类型Android 10+ 推荐方案注意事项
图片/视频/音频READ_MEDIA_IMAGES 等细粒度权限必须使用新权限
PDF/文档系统文件选择器 (ACTION_OPEN_DOCUMENT)无需存储权限
应用自有文件直接访问,无需权限使用 getExternalFilesDir()
需要广泛文件访问MANAGE_EXTERNAL_STORAGEGoogle Play 审核严格

最佳实践建议

1. 权限声明策略

xml
<!-- AndroidManifest.xml 中的推荐配置 -->
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
    android:maxSdkVersion="29" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
    android:maxSdkVersion="28" />

2. 运行时权限检查

kotlin
fun checkAndRequestStoragePermissions(activity: Activity, requestCode: Int) {
    when {
        Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU -> {
            // Android 13+:检查媒体权限
            val permissionsToRequest = mutableListOf<String>()
            if (ContextCompat.checkSelfPermission(activity, 
                Manifest.permission.READ_MEDIA_IMAGES) != PackageManager.PERMISSION_GRANTED) {
                permissionsToRequest.add(Manifest.permission.READ_MEDIA_IMAGES)
            }
            // 添加其他需要的媒体权限...
            
            if (permissionsToRequest.isNotEmpty()) {
                ActivityCompat.requestPermissions(activity, 
                    permissionsToRequest.toTypedArray(), requestCode)
            }
        }
        Build.VERSION.SDK_INT >= Build.VERSION_CODES.R -> {
            // Android 11-12:检查全文件访问权限
            if (!Environment.isExternalStorageManager()) {
                // 请求 MANAGE_EXTERNAL_STORAGE
            }
        }
        else -> {
            // Android 10 及以下:检查传统存储权限
            if (ContextCompat.checkSelfPermission(activity,
                Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
                ActivityCompat.requestPermissions(activity,
                    arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE), requestCode)
            }
        }
    }
}
java
// 类似的逻辑在 Java 中的实现
private void checkAndRequestStoragePermissions(Activity activity, int requestCode) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
        List<String> permissionsToRequest = new ArrayList<>();
        if (ContextCompat.checkSelfPermission(activity, 
            Manifest.permission.READ_MEDIA_IMAGES) != PackageManager.PERMISSION_GRANTED) {
            permissionsToRequest.add(Manifest.permission.READ_MEDIA_IMAGES);
        }
        // 添加其他权限检查...
        
        if (!permissionsToRequest.isEmpty()) {
            ActivityCompat.requestPermissions(activity,
                permissionsToRequest.toArray(new String[0]), requestCode);
        }
    } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
        if (!Environment.isExternalStorageManager()) {
            // 请求全文件访问权限
        }
    } else {
        // Android 10 及以下版本的处理
    }
}

3. 处理权限请求结果

kotlin
override fun onRequestPermissionsResult(
    requestCode: Int,
    permissions: Array<out String>,
    grantResults: IntArray
) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults)
    
    if (requestCode == STORAGE_PERMISSION_REQUEST_CODE) {
        val allGranted = grantResults.all { it == PackageManager.PERMISSION_GRANTED }
        if (allGranted) {
            // 权限已授予,执行相关操作
        } else {
            // 处理权限被拒绝的情况
            showPermissionDeniedMessage()
        }
    }
}

重要注意事项

关于 MANAGE_EXTERNAL_STORAGE

使用 MANAGE_EXTERNAL_STORAGE 权限需要特别注意:

  • 在 Google Play 上架时需要填写权限声明表
  • 必须提供充分的理由说明为什么需要全文件访问
  • 可能影响应用在 Google Play 的可见性和审核通过率
  • 仅应在绝对必要时使用此权限

Android 10 的特殊配置

对于以 Android 10 为目标平台的应用,如果还需要使用传统存储权限,需要在 AndroidManifest.xml 中设置:

xml
<application
    android:requestLegacyExternalStorage="true"
    ... >

总结

Android 13 的存储权限变更确实带来了开发上的挑战,但也提供了更精细的隐私保护。对于非媒体文件的处理:

  1. 优先使用系统文件选择器ACTION_OPEN_DOCUMENT),无需请求权限
  2. 针对不同 Android 版本实现差异化处理
  3. 避免不必要的 MANAGE_EXTERNAL_STORAGE 使用
  4. 合理配置 AndroidManifest.xml 中的权限声明

通过遵循这些最佳实践,你可以在满足 Android 13 新要求的同时,为用户提供良好的文件访问体验。