You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.04 23:00:16