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

如何通过Scoped Directory Access权限系统读取文件?Android 7.0 SD卡访问求助

实现Scoped Directory Access的文件读取(Android 7.0+)

我之前在三星S7(Android 7.0)上做过类似需求,Scoped Directory Access确实是针对可移动存储(比如SD卡)的合规权限方案,下面给你完整的实现步骤和代码示例:

一、核心流程梳理

Scoped Directory Access的本质是通过系统Intent向用户请求指定目录的访问权限,授权后会返回一个可持久使用的Uri,我们通过ContentResolver来操作这个Uri对应的目录下的文件,而不是直接用File类访问路径。

二、完整代码实现

1. 初始化权限请求Intent并触发授权

首先在你的MainActivity中,补充触发权限请求的逻辑(比如绑定到按钮点击):

public class MainActivity extends AppCompatActivity {
    private static final int REQUEST_CODE_SDCARD_ACCESS = 1001;
    private Uri mAuthorizedDirUri; // 保存授权后的目录Uri

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // 假设你有一个按钮触发SD卡权限请求
        Button requestBtn = findViewById(R.id.btn_request_access);
        requestBtn.setOnClickListener(v -> requestScopedDirectoryAccess());
    }

    // 请求SD卡目录的访问权限
    private void requestScopedDirectoryAccess() {
        StorageManager storageManager = (StorageManager) getSystemService(Context.STORAGE_SERVICE);
        // 获取可移动存储卷(SD卡)
        List<StorageVolume> storageVolumes = storageManager.getStorageVolumes();
        StorageVolume sdCardVolume = null;
        for (StorageVolume volume : storageVolumes) {
            // 判断是否是可移动存储且不是内部存储
            if (!volume.isPrimary() && volume.isRemovable()) {
                sdCardVolume = volume;
                break;
            }
        }

        if (sdCardVolume != null) {
            // 创建访问SD卡根目录的Intent(参数传null表示请求根目录,也可以传系统目录如Environment.DIRECTORY_DOCUMENTS)
            Intent intent = sdCardVolume.createAccessIntent(null);
            startActivityForResult(intent, REQUEST_CODE_SDCARD_ACCESS);
        } else {
            Toast.makeText(this, "未检测到SD卡", Toast.LENGTH_SHORT).show();
        }
    }
}

2. 处理授权结果

在onActivityResult中接收用户的授权结果,保存返回的Uri:

@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if (requestCode == REQUEST_CODE_SDCARD_ACCESS) {
        if (resultCode == RESULT_OK && data != null) {
            // 保存授权后的目录Uri,后续可以用这个Uri访问目录下的文件
            mAuthorizedDirUri = data.getData();
            Toast.makeText(this, "SD卡访问权限已获取", Toast.LENGTH_SHORT).show();
            // 这里可以调用文件读取方法
            readFilesFromAuthorizedDir();
        } else {
            Toast.makeText(this, "用户拒绝了SD卡访问权限", Toast.LENGTH_SHORT).show();
        }
    }
}

3. 实现文件读取操作

拿到授权的Uri后,通过ContentResolver来遍历目录或读取文件:

// 读取授权目录下的文件
private void readFilesFromAuthorizedDir() {
    if (mAuthorizedDirUri == null) {
        Toast.makeText(this, "请先获取SD卡访问权限", Toast.LENGTH_SHORT).show();
        return;
    }

    // 遍历目录下的文件
    String[] projection = {
            DocumentsContract.Document.COLUMN_DISPLAY_NAME,
            DocumentsContract.Document.COLUMN_SIZE,
            DocumentsContract.Document.COLUMN_MIME_TYPE
    };

    try (Cursor cursor = getContentResolver().query(
            mAuthorizedDirUri,
            projection,
            null,
            null,
            null)) {

        if (cursor != null) {
            while (cursor.moveToNext()) {
                String fileName = cursor.getString(cursor.getColumnIndex(DocumentsContract.Document.COLUMN_DISPLAY_NAME));
                long fileSize = cursor.getLong(cursor.getColumnIndex(DocumentsContract.Document.COLUMN_SIZE));
                String mimeType = cursor.getString(cursor.getColumnIndex(DocumentsContract.Document.COLUMN_MIME_TYPE));

                // 打印文件信息,你可以根据需求处理文件
                Log.d("SDCardFiles", "文件名:" + fileName + ",大小:" + fileSize + ",类型:" + mimeType);

                // 如果要读取某个具体文件的内容,需要构建该文件的Uri
                Uri fileUri = DocumentsContract.buildDocumentUriUsingTree(
                        mAuthorizedDirUri,
                        cursor.getString(cursor.getColumnIndex(DocumentsContract.Document.COLUMN_DOCUMENT_ID)));
                readFileContent(fileUri);
            }
        }
    } catch (Exception e) {
        e.printStackTrace();
        Toast.makeText(this, "读取文件失败:" + e.getMessage(), Toast.LENGTH_SHORT).show();
    }
}

// 读取单个文件的内容
private void readFileContent(Uri fileUri) {
    try (InputStream inputStream = getContentResolver().openInputStream(fileUri);
         BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) {

        StringBuilder content = new StringBuilder();
        String line;
        while ((line = reader.readLine()) != null) {
            content.append(line).append("\n");
        }
        Log.d("FileContent", "文件内容:\n" + content.toString());
    } catch (IOException e) {
        e.printStackTrace();
    }
}

三、关键注意事项

  • 权限持久化:用户授权后,这个Uri的访问权限会被系统持久保存,除非用户在系统设置中撤销该应用的存储权限,或者卸载应用。
  • 目录选择:如果你不需要访问SD卡根目录,可以将createAccessIntent的参数改为系统定义的目录常量,比如Environment.DIRECTORY_PHOTOS,这样用户会看到更细分的目录授权提示。
  • 适配AndroidX:如果你的项目用了AndroidX,可以改用ActivityResultContracts.OpenDocumentTree来替代startActivityForResult,不过在Android 7.0上两种方式都能正常工作。
  • 三星设备特殊处理:部分三星设备的SD卡可能有额外的存储限制,确保你的应用已经在Manifest中声明了READ_EXTERNAL_STORAGE权限(虽然Scoped Directory Access不需要这个权限,但部分厂商可能需要兼容)。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.26 10:12:22