Kotlin中File(path).walkTopDown()无法识别文件问题排查
无法通过递归读取识别外部存储中的文件,仅能识别目录
我尝试递归读取指定目录,期望找出所有文件夹与文件,但目前代码仅能识别目录及子目录,找不到文件。
虚拟设备存储结构

遍历目录的代码
import android.util.Log import java.io.File // path = "/storage/1B10-1D17/ReadTest/" class FileHelper { fun walkTest(path: String){ File(path).walkTopDown().forEach { // println(it) Log.e("walkTest extension", it.extension) if(it.isDirectory){ Log.e("walkTest", "Directory: ${it.name}") }else{ Log.e("walkTest", "File: ${it.name}") } } } }
输出结果
E/walkTest extension: E/walkTest: Directory: ReadTest E/walkTest extension: E/walkTest: Directory: SubFolder
AndroidManifest权限配置
已添加以下权限且已授权:
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
更新信息
已确认媒体访问权限已授予(截图:
),执行adb命令adb shell ls /storage/1B10-1D17/ReadTest/可看到目录下存在File_1.pdf和SubFolder,但代码就是识别不到文件,请问代码问题出在哪?怎么找到这些目录里的三个PDF文件?
问题原因与解决方案
问题根源
从Android 10(API 29)开始,系统启用了分区存储机制:默认情况下READ_EXTERNAL_STORAGE权限只能让File API访问媒体文件(图、音、视频),像PDF这类非媒体文件,即使授权也无法通过File.walkTopDown()直接遍历到。这就是你能看到目录但看不到PDF文件的核心原因。
可行解决方法
方法一:用存储访问框架(SAF)让用户选择目录(推荐合规方案)
这是Android官方推荐的方式,适用于所有Android版本,能访问任意目录下的文件:
// 定义请求码 private const val REQUEST_CODE_OPEN_DIR = 1001 // 启动SAF选择目录 fun openDirectoryPicker() { val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE) startActivityForResult(intent, REQUEST_CODE_OPEN_DIR) } // 处理选择结果 override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) if (requestCode == REQUEST_CODE_OPEN_DIR && resultCode == Activity.RESULT_OK) { data?.data?.let { treeUri -> // 持久化访问权限,避免重启后失效 contentResolver.takePersistableUriPermission( treeUri, Intent.FLAG_GRANT_READ_URI_PERMISSION ) // 开始遍历目录 traverseDirectory(treeUri) } } } // 递归遍历SAF返回的目录 private fun traverseDirectory(treeUri: Uri) { val childDocumentsUri = DocumentsContract.buildChildDocumentsUriUsingTree( treeUri, DocumentsContract.getTreeDocumentId(treeUri) ) contentResolver.query(childDocumentsUri, null, null, null, null)?.use { cursor -> val nameIndex = cursor.getColumnIndex(DocumentsContract.Document.COLUMN_DISPLAY_NAME) val mimeTypeIndex = cursor.getColumnIndex(DocumentsContract.Document.COLUMN_MIME_TYPE) while (cursor.moveToNext()) { val name = cursor.getString(nameIndex) val mimeType = cursor.getString(mimeTypeIndex) if (DocumentsContract.Document.MIME_TYPE_DIR == mimeType) { Log.e("walkTest", "Directory: $name") // 递归遍历子目录 val childDirId = cursor.getString(cursor.getColumnIndex(DocumentsContract.Document.COLUMN_DOCUMENT_ID)) val childDirUri = DocumentsContract.buildChildDocumentsUriUsingTree(treeUri, childDirId) traverseDirectory(childDirUri) } else { Log.e("walkTest", "File: $name") } } } }
方法二:关闭分区存储(仅适配Android 10-11临时方案)
如果你的应用无需适配Android 12+,可以在AndroidManifest.xml的<application>标签中添加属性,回到旧存储模式:
<application ... android:requestLegacyExternalStorage="true"> ... </application>
添加后File API就能正常遍历所有文件,但Android 12及以上版本会忽略这个属性,无法生效。
方法三:通过MediaStore查询PDF文件
如果你的PDF已经被系统媒体库索引,可以通过媒体库查询获取:
fun queryAllPdfFiles() { val projection = arrayOf( MediaStore.Files.FileColumns.DISPLAY_NAME, MediaStore.Files.FileColumns.DATA ) // 筛选PDF类型文件 val selection = "${MediaStore.Files.FileColumns.MIME_TYPE} = ?" val selectionArgs = arrayOf("application/pdf") val sortOrder = "${MediaStore.Files.FileColumns.DATE_ADDED} DESC" contentResolver.query( MediaStore.Files.getContentUri("external"), projection, selection, selectionArgs, sortOrder )?.use { cursor -> val nameIndex = cursor.getColumnIndex(MediaStore.Files.FileColumns.DISPLAY_NAME) while (cursor.moveToNext()) { val fileName = cursor.getString(nameIndex) Log.e("walkTest", "PDF File: $fileName") } } }
注意:手动放入的PDF可能需要等待系统索引完成才能被查询到。
内容的提问来源于stack exchange,提问作者Mathlight
相关产品推荐
相关产品推荐

