如何通过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
相关产品推荐
相关产品推荐

