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

Android 10(小米9)下无法在Environment.DIRECTORY_DOCUMENTS创建子目录

问题原因解析

1. Android版本存储规则差异

  • Android 11(API30):谷歌调整了分区存储策略,允许应用直接在Environment.DIRECTORY_DOCUMENTS等系统公共目录下创建自定义子目录,只要持有WRITE_EXTERNAL_STORAGE权限即可,这就是代码在Android11设备上正常工作的原因。
  • Android 10(API29):严格执行分区存储规则(当targetSdk>=29时默认启用),应用无法直接在系统公共目录(如Documents)下创建自定义子目录,必须通过**存储访问框架(SAF)**请求用户授权,或者使用MediaStoreAPI操作文件。

2. 小米设备的定制化限制

部分国内厂商(如小米)的Android10定制系统,对公共Documents目录额外增加了权限校验,即使手动创建了Documents目录,应用也没有足够权限在其下创建子目录,导致mkdirs()始终返回false。

解决方案

方案一:启用遗留存储兼容(过渡方案)

如果暂时不想重构代码,可以在AndroidManifest.xml的<application>标签中添加以下属性,让应用在Android10设备上使用旧的存储模式:

android:requestLegacyExternalStorage="true"

注意:该属性仅在targetSdk<=30时生效,当targetSdk升级到31及以上时会被忽略,属于临时兼容方案。

方案二:适配存储访问框架(SAF)(推荐长期方案)

对于Android10及以上设备,使用SAF让用户手动授权访问Documents目录,然后通过Uri创建子目录:

  1. 发起SAF授权请求:
private static final int REQUEST_CODE_DOCUMENTS_DIR = 1001;

public void requestDocumentsDirAccess() {
    Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
    intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, Uri.parse("content://com.android.externalstorage.documents/tree/primary%3ADocuments"));
    startActivityForResult(intent, REQUEST_CODE_DOCUMENTS_DIR);
}
  1. 在onActivityResult中处理授权结果并创建子目录:
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if (requestCode == REQUEST_CODE_DOCUMENTS_DIR && resultCode == RESULT_OK) {
        Uri treeUri = data.getData();
        // 持久化Uri权限,重启应用后仍可访问
        getContentResolver().takePersistableUriPermission(treeUri, Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
        // 创建子目录MySubfolder
        DocumentsContract.createDocument(getContentResolver(), treeUri, DocumentsContract.Document.MIME_TYPE_DIR, "MySubfolder");
    }
}

方案三:调整目录策略

如果不需要让用户在Documents目录下看到文件,可以改用应用专属的外部存储目录,无需额外权限:

File dir = new File(getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS), "MySubfolder");
dir.mkdirs();

该目录属于应用私有空间,卸载应用时会被删除,但不受分区存储限制,也无需申请存储权限。

代码优化建议

针对不同Android版本做分支处理,避免不必要的异常:

public static File getWriteableUserDirPath(Context context) {
    String folderName = "MySubfolder";
    File dir = null;

    // 优先尝试应用专属外部目录(无权限问题)
    dir = new File(context.getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS), folderName);
    if (dir.mkdirs() || dir.exists()) {
        return dir;
    }

    // 针对Android 11及以上,尝试公共Documents目录
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
        try {
            dir = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS), folderName);
            if (dir.mkdirs() || dir.exists()) {
                return dir;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    // 针对Android 10及以下,返回null让上层触发SAF授权
    if (Build.VERSION.SDK_INT == Build.VERSION_CODES.Q) {
        return null;
    }

    // Android 9及以下,尝试旧的根目录方式
    try {
        dir = Environment.getExternalStoragePublicDirectory(folderName);
        if (dir.mkdirs() || dir.exists()) {
            return dir;
        }
    } catch (Exception e) {
        e.printStackTrace();
    }

    return null;
}

内容的提问来源于stack exchange,提问作者JustAMartin

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.13 20:00:47