Android 13 存储权限处理指南
问题概述
Android 13(API 级别 33)引入了重大的存储权限变更。根据官方行为变更说明,如果你的应用以 Android 13 为目标平台,必须使用新的细粒度媒体权限,而不是传统的 READ_EXTERNAL_STORAGE
和 WRITE_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_STORAGE | Google 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 的存储权限变更确实带来了开发上的挑战,但也提供了更精细的隐私保护。对于非媒体文件的处理:
- 优先使用系统文件选择器(
ACTION_OPEN_DOCUMENT
),无需请求权限 - 针对不同 Android 版本实现差异化处理
- 避免不必要的
MANAGE_EXTERNAL_STORAGE
使用 - 合理配置 AndroidManifest.xml 中的权限声明
通过遵循这些最佳实践,你可以在满足 Android 13 新要求的同时,为用户提供良好的文件访问体验。