Android应用中如何用SAF访问与图片同目录的TXT文件?
Android API30下SAF访问同目录图片同名TXT文件失败的解决方案
问题根源
原代码存在以下关键问题:
- 手动拆分
documentId的方式不通用:不同DocumentsProvider的documentId格式存在差异(比如内部存储、SD卡、第三方云存储的ID结构各不相同),硬编码拆分逻辑会导致父目录ID获取错误 buildDocumentUriUsingTree使用场景错误:该方法要求传入的基础URI是树权限URI,但单个图片的URI通常是文件级别的URI,无法以此构建有效的父目录或子文件URIDocumentFile.fromSingleUri局限性:该方法仅能验证已知存在的文件URI,无法用于发现同目录下未授权的其他文件
解决方案
通过SAF规范的方式获取父目录权限,再查找目标TXT文件,具体步骤如下:
- 通过图片URI获取父目录的合法
DocumentFile实例 - 在父目录中直接查找同名TXT文件
- 通过ContentResolver读取TXT内容并解析标签
修正后的代码
fun getPictures(fileUris: List<Uri>, context: Context): List<Picture> { val picturesList = mutableListOf<Picture>() for (fileUri in fileUris) { val picture = Picture(fileUri, mutableListOf()) val imageNameWithoutExtension = fileUri.lastPathSegment?.substringBeforeLast(".") ?: continue val txtName = "$imageNameWithoutExtension.txt" try { // 获取图片文件的documentId val documentId = DocumentsContract.getDocumentId(fileUri) // 获取父目录的documentId(API 26及以上支持) val parentDocId = DocumentsContract.getParentDocumentId(context.contentResolver, documentId) ?: run { Log.d("MSG", "无法获取图片所在父目录ID") picturesList.add(picture) continue } // 构建父目录的树URI val parentTreeUri = DocumentsContract.buildDocumentUriUsingTree(fileUri, parentDocId) val parentDir = DocumentFile.fromTreeUri(context, parentTreeUri) // 在父目录中查找目标TXT文件 val txtFile = parentDir?.findFile(txtName) if (txtFile != null && txtFile.exists() && isTextMimeType(txtFile.type)) { // 读取并解析标签 picture.tags = readTagsFromTxt(txtFile, context) } else { Log.d("MSG", "未找到对应TXT文件或文件类型不符合要求") } } catch (e: Exception) { Log.e("MSG", "访问TXT文件出错", e) } picturesList.add(picture) } return picturesList } // 读取TXT文件内容并解析标签 private fun readTagsFromTxt(txtFile: DocumentFile, context: Context): MutableList<String> { val tags = mutableListOf<String>() context.contentResolver.openInputStream(txtFile.uri)?.use { inputStream -> val content = inputStream.bufferedReader().readText() tags.addAll(content.split(",").map { it.trim() }.filter { it.isNotEmpty() }) } return tags } // 判断是否为文本类型MIME private fun isTextMimeType(mimeType: String?): Boolean { return mimeType?.startsWith("text/") == true || mimeType == "application/octet-stream" // 兼容部分未正确标记MIME的TXT文件 }
关键说明
- 使用
DocumentsContract.getParentDocumentId获取父目录ID:该方法由系统处理不同存储的ID格式,避免手动拆分的兼容性问题 - 基于父目录树URI创建
DocumentFile:确保拥有该目录的访问权限,可直接查找目录内的文件 - 采用
ContentResolver.openInputStream读取文件:严格遵循SAF的文件访问规则,避免直接操作文件路径的权限问题
内容的提问来源于stack exchange,提问作者K. Do
相关产品推荐
相关产品推荐

