Android 13中SAF获取目录URI后无法访问文件的解决方案
解决Android 13下SAF目录URI的文件访问问题
首先明确:你拿到的content://com.android.externalstorage.documents/tree/primary%3ADocuments%2FSMART%2FFall_data这个URI格式是完全正确的,它是SAF提供的目录树URI,但这种URI不能用传统的File类或者直接查询目录本身来处理,这就是你两种方式报错的原因。
错误原因分析
- 方式一错误:SAF的content URI不是文件系统路径,不能直接转成
File对象,Android 13的存储权限模型更严格,直接操作File会完全失效。 - 方式二错误:直接用目录的tree URI调用
contentResolver.query()是不支持的,因为这个URI指向的是目录树节点,而非可查询的文档集合,需要构建专门的子文档查询URI。
正确实现步骤
1. 持久化目录权限(可选但推荐)
选完目录后,先申请持久化权限,避免APP重启后丢失访问权限:
if (data != null) { Uri directoryUri = data.getData(); // 持久化权限 getContentResolver().takePersistableUriPermission( directoryUri, Intent.FLAG_GRANT_READ_URI_PERMISSION ); accessFilesInDirectory(directoryUri); }
2. 正确查询目录下的JSON文件
使用DocumentsContract.buildChildDocumentsUriUsingTree构建查询子文件的URI,然后通过ContentResolver查询:
private void accessFilesInDirectory(Uri directoryUri) { ContentResolver contentResolver = getContentResolver(); // 构建查询子文档的URI Uri childrenUri = DocumentsContract.buildChildDocumentsUriUsingTree( directoryUri, DocumentsContract.getTreeDocumentId(directoryUri) ); // 查询JSON文件(MIME类型为application/json) String[] projection = { DocumentsContract.Document.COLUMN_DOCUMENT_ID, DocumentsContract.Document.COLUMN_DISPLAY_NAME, DocumentsContract.Document.COLUMN_MIME_TYPE }; String selection = DocumentsContract.Document.COLUMN_MIME_TYPE + " = ?"; String[] selectionArgs = {"application/json"}; Cursor cursor = null; try { cursor = contentResolver.query(childrenUri, projection, selection, selectionArgs, null); if (cursor != null && cursor.moveToFirst()) { do { String docId = cursor.getString(cursor.getColumnIndexOrThrow(DocumentsContract.Document.COLUMN_DOCUMENT_ID)); String fileName = cursor.getString(cursor.getColumnIndexOrThrow(DocumentsContract.Document.COLUMN_DISPLAY_NAME)); Log.i("LOAD FILES:", "file name: " + fileName); // 构建单个文件的URI Uri fileUri = DocumentsContract.buildDocumentUriUsingTree(directoryUri, docId); // 读取文件内容 readJsonFile(contentResolver, fileUri); } while (cursor.moveToNext()); } } catch (Exception e) { e.printStackTrace(); } finally { if (cursor != null) { cursor.close(); } } }
3. 读取JSON文件内容
通过ContentResolver.openInputStream获取文件输入流,不要用FileInputStream:
private void readJsonFile(ContentResolver resolver, Uri fileUri) { BufferedReader reader = null; try { InputStream inputStream = resolver.openInputStream(fileUri); if (inputStream == null) return; reader = new BufferedReader(new InputStreamReader(inputStream)); StringBuilder content = new StringBuilder(); String line; while ((line = reader.readLine()) != null) { content.append(line); } // 这里处理JSON内容,比如转成对象 Log.i("JSON CONTENT:", content.toString()); } catch (IOException e) { e.printStackTrace(); } finally { if (reader != null) { try { reader.close(); } catch (IOException e) { e.printStackTrace(); } } } }
额外注意事项
- Android 13及以上,SAF是访问共享存储的推荐方式,不要试图绕过SAF直接访问Documents目录,会被权限限制。
- 如果需要操作子目录,递归查询的逻辑和上面类似,判断文档的MIME类型是否为
DocumentsContract.Document.MIME_TYPE_DIR即可。
内容的提问来源于stack exchange,提问作者walt3rwhite
相关产品推荐
相关产品推荐

