Android 13无法下载图片到SD卡:EPERM权限问题求助
Android 13 文件写入权限问题解决
问题原因
Android 13(API 33)对存储权限做了重大变更:
WRITE_EXTERNAL_STORAGE权限被废弃,仅对 API 32 及以下版本有效- 公共媒体目录(如 Movies、Pictures)的自定义子目录写入受到严格限制,直接通过
File类写入会触发EPERM权限错误 - 针对媒体文件新增了细分权限:
READ_MEDIA_IMAGES、READ_MEDIA_VIDEO、READ_MEDIA_AUDIO,但这些权限仅用于读取,无法直接写入自定义公共子目录
解决方法
方案一:使用应用专属外部存储目录(推荐)
应用专属外部存储目录(/Android/data/[包名]/files/)无需申请存储权限,文件会随应用卸载自动删除,适合存储应用自身产生的文件。
1. 修改权限声明(AndroidManifest.xml)
移除不必要的权限,保留兼容旧版本的配置:
<!-- 兼容Android 10-12 --> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="32" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" />
2. 修改目录创建代码
替换 createFolderForApp 方法:
private void createFolderForApp(String FolderName) { // 应用专属图片存储目录 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { directory = new File(getExternalFilesDir(Environment.DIRECTORY_PICTURES), FolderName); } else { directory = new File(Environment.getExternalStorageDirectory() + "/Android/data/" + getPackageName() + "/files/Pictures/" + FolderName); } Log.i("TAG", "createFolderForApp: " + directory.getPath()); if (!directory.exists()) { boolean success = directory.mkdirs(); if (!success) { directory = null; runOnUiThread(() -> Toast.makeText(this, "目录创建失败", Toast.LENGTH_SHORT).show()); } else { new FileDownloader().execute(downloadLink); } } else { new FileDownloader().execute(downloadLink); } }
方案二:写入公共自定义目录(需特殊权限)
如果必须写入公共目录的自定义子目录(如 Movies/RoveDashCam/Photos),需要申请 MANAGE_EXTERNAL_STORAGE 权限,该权限需要向Google Play说明使用必要性,否则可能被拒审。
1. 添加权限声明(AndroidManifest.xml)
<!-- 兼容旧版本 --> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="32" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" /> <!-- Android 10+ 全文件访问权限 --> <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" android:minSdkVersion="30" />
2. 添加权限检查逻辑
修改 onCreate 方法,先检查权限再创建目录:
@Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_new_upload); folderName = "RoveDashCam/Photos"; checkStoragePermission(); } private void checkStoragePermission() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { // 检查是否拥有全文件访问权限 if (!Environment.isExternalStorageManager()) { Intent intent = new Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION); Uri uri = Uri.fromParts("package", getPackageName(), null); intent.setData(uri); startActivityForResult(intent, 100); } else { createFolderForApp(folderName); } } else { // 旧版本检查WRITE权限 if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 101); } else { createFolderForApp(folderName); } } } // 处理权限申请结果 @Override protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == 100) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { if (Environment.isExternalStorageManager()) { createFolderForApp(folderName); } else { Toast.makeText(this, "需要授予全文件访问权限才能下载", Toast.LENGTH_SHORT).show(); } } } } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); if (requestCode == 101) { if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { createFolderForApp(folderName); } else { Toast.makeText(this, "需要存储权限才能下载", Toast.LENGTH_SHORT).show(); } } }
3. 保留原目录创建逻辑
原 createFolderForApp 方法可以继续使用,但需要确保权限已授予。
额外优化建议
AsyncTask已被官方废弃,建议改用 Coroutines 或 ExecutorService 实现后台下载逻辑- 下载进度更新应使用
AsyncTask的onProgressUpdate方法,避免在后台线程直接调用Handler.post操作UI
内容的提问来源于stack exchange,提问作者MARSH
相关产品推荐
相关产品推荐

