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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.04 21:35:28