Android 13 外部存储写入权限解决方案
TIP
关键变更提醒:自 Android 11(API 30)起,WRITE_EXTERNAL_STORAGE
权限在共享存储空间中不再提供任何额外访问权限。此变更在 Android 13(API 33)中进一步强化。
问题描述
当应用在 Android 13 及以上设备中请求 WRITE_EXTERNAL_STORAGE
权限时:
- 运行时权限弹窗不再显示
- 权限授予机制完全失效
- 仅影响 Android 13+ 设备(Android 12 及以下正常)
根本原因在于 Android 存储权限模型的重大变更:
- 精细媒体权限:Android 13 引入
READ_MEDIA_IMAGES
、READ_MEDIA_VIDEO
、READ_MEDIA_AUDIO
替代原媒体访问权限 - 权限废止:从 Android 11 开始,
WRITE_EXTERNAL_STORAGE
对于共享存储空间访问已无实际效果 - 存储分区限制:应用无法自由访问外部存储中其他应用的文件目录
推荐解决方案
方案1: 使用应用专属存储 (推荐)
适用于无需共享的应用私有文件(如密钥库):
java
File appDir = getExternalFilesDir("KeystoreDir");
if (!appDir.exists()) {
if (!appDir.mkdirs()) {
// 目录创建失败处理
Toast.makeText(this, "目录创建失败", Toast.LENGTH_SHORT).show();
return;
}
}
File keyFile = new File(appDir, "keystore.bks");
try (FileOutputStream fos = new FileOutputStream(keyFile)) {
// 写入文件内容
} catch (IOException e) {
e.printStackTrace();
}
优势:
- ✅ 无需任何权限(包括在 AndroidManifest.xml 中声明)
- ✅ 文件随应用卸载自动清除
- ✅ 支持所有 Android 版本
注意
存储在 getExternalFilesDir()
的文件:
- 只能被本应用访问
- 不会出现在相册/文件管理器等公共视图中
- 用户可通过"设置 > 应用 > 存储"清除
方案2: 存储访问框架 (SAF)
当需要用户指定存储位置时(如下载目录):
java
// 启动目录选择器
private ActivityResultLauncher<Intent> dirLauncher = registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(),
result -> {
if (result.getResultCode() == RESULT_OK) {
Uri treeUri = result.getData().getData();
persistUriPermission(treeUri); // 持久化权限
saveFileToUri(treeUri, "file.txt", "内容");
}
}
);
void pickDirectory() {
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
dirLauncher.launch(intent);
}
void persistUriPermission(Uri uri) {
getContentResolver().takePersistableUriPermission(uri,
Intent.FLAG_GRANT_READ_URI_PERMISSION |
Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
}
void saveFileToUri(Uri baseUri, String name, String content) {
DocumentFile dir = DocumentFile.fromTreeUri(this, baseUri);
DocumentFile file = dir.createFile("text/plain", name);
try (OutputStream os = getContentResolver().openOutputStream(file.getUri())) {
os.write(content.getBytes());
} catch (IOException e) {
e.printStackTrace();
}
}
方案3: 管理所有文件权限 (谨慎使用)
仅适用于文件管理器类应用(需 Play 商店特殊批准):
xml
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
java
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
if (!Environment.isExternalStorageManager()) {
// 跳转系统设置页
Intent intent = new Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION);
startActivity(intent);
}
// 已有权限则可自由写入
} else {
// Android 10 及以下保持原有逻辑
}
版本兼容处理
实现向下兼容的优雅方案:
java
public void saveFileWithFallback() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
// Android 13+ 使用应用专属存储
saveToAppSpecificDir();
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
// Android 11-12 检查旧权限
if (Environment.isExternalStorageManager()) {
saveLegacyWay();
} else {
requestLegacyPermission();
}
} else {
// Android 10 及以下请求运行时权限
if (checkSelfPermission(WRITE_EXTERNAL_STORAGE) == PERMISSION_GRANTED) {
saveLegacyWay();
} else {
requestPermissions(new String[]{WRITE_EXTERNAL_STORAGE}, 101);
}
}
}
最佳实践总结
方法 | 使用场景 | 权限需求 | 用户可见性 |
---|---|---|---|
应用专属存储 | 私有配置文件、缓存数据 | 无需 | 不可见 |
SAF (存储访问框架) | 导出用户可见文件 | 需用户主动选择 | 可见 |
媒体存储 API | 图片、音视频文件 | READ_MEDIA_* | 可见 |
所有文件访问 | 文件管理器类应用 | MANAGE_EXTERNAL_STORAGE | 全部可见 |
升级建议
- 优先使用应用专属目录存储私有文件(最简方案)
- 使用
MediaStore
API 处理媒体文件 - 通过
Intent.ACTION_CREATE_DOCUMENT
创建用户可见文档 - 仅当绝对必要时才考虑管理所有文件权限 ::>
更新您的应用以遵循这些模式,将确保在 Android 13 及以上设备实现无缝存储访问,同时通过 Play 商店的合规审核。