Kotlin获取设备已有文件列表失败:仅读取到应用创建文件如何解决?
问题分析与解决方案
核心原因
你的问题出在Android 10(API 29)引入的分区存储(Scoped Storage)机制:默认情况下,APP只能访问自身创建的文件、媒体库中的文件,无法通过File API直接读取其他APP或手动导入的文件(比如你通过Android Studio导入的PDF)。同时你的Manifest权限配置存在错误:ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION、ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION是跳转系统设置的Intent Action,不是权限,需要删除。
解决方案
根据你的需求,提供两种可行方案:
方案一:适配分区存储(推荐,符合Google规范)
对于文档类文件(如PDF),使用MediaStore API查询,无需申请全文件访问权限,适合绝大多数APP场景。
步骤1:修正Manifest权限
保留必要权限,适配不同Android版本:
<!-- Android 12及以下需要,Android 13+用细分权限 --> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" /> <!-- Android 13+读取文档需要的权限 --> <uses-permission android:name="android.permission.READ_MEDIA_DOCUMENTS" android:minSdkVersion="33" />
步骤2:动态申请权限+查询文件
import android.Manifest import android.content.Context import android.content.pm.PackageManager import android.os.Build import android.provider.MediaStore import android.widget.Toast import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.contract.ActivityResultContracts import androidx.appcompat.app.AppCompatActivity import androidx.core.content.ContextCompat import java.io.File import android.os.Environment class MainActivity : AppCompatActivity() { // 权限申请Launcher private lateinit var requestReadMediaLauncher: ActivityResultLauncher<String> private lateinit var requestReadStorageLauncher: ActivityResultLauncher<Array<String>> override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) initPermissionLaunchers() checkPermissions() } private fun initPermissionLaunchers() { // Android 13+ 读取文档权限 requestReadMediaLauncher = registerForActivityResult(ActivityResultContracts.RequestPermission()) { granted -> if (granted) proceedWithOperations() else Toast.makeText(this, "需要读取文档权限才能访问PDF文件", Toast.LENGTH_SHORT).show() } // Android 6-12 读取存储权限 requestReadStorageLauncher = registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { permissions -> val isGranted = permissions[Manifest.permission.READ_EXTERNAL_STORAGE] ?: false if (isGranted) proceedWithOperations() else Toast.makeText(this, "需要读取存储权限才能访问文件", Toast.LENGTH_SHORT).show() } } private fun checkPermissions() { when { Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU -> { if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_MEDIA_DOCUMENTS) != PackageManager.PERMISSION_GRANTED) { requestReadMediaLauncher.launch(Manifest.permission.READ_MEDIA_DOCUMENTS) } else proceedWithOperations() } Build.VERSION.SDK_INT >= Build.VERSION_CODES.M -> { if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { requestReadStorageLauncher.launch(arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE)) } else proceedWithOperations() } else -> proceedWithOperations() } } private fun proceedWithOperations() { createFile() queryPdfFiles() // 查询所有PDF getFilesList(documentsPath) // 读取自身创建的文件 } // 查询Documents目录下的所有PDF文件 private fun queryPdfFiles() { val projection = arrayOf( MediaStore.Files.FileColumns.DISPLAY_NAME, MediaStore.Files.FileColumns.RELATIVE_PATH ) val selection = "${MediaStore.Files.FileColumns.MIME_TYPE} = ? AND ${MediaStore.Files.FileColumns.RELATIVE_PATH} LIKE ?" val selectionArgs = arrayOf("application/pdf", "%Documents%") contentResolver.query( MediaStore.Files.getContentUri("external"), projection, selection, selectionArgs, null )?.use { cursor -> while (cursor.moveToNext()) { val fileName = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.DISPLAY_NAME)) println("PDF文件: $fileName") } } } // 原有代码逻辑 private val externalStorage: String = Environment.getExternalStorageDirectory().absolutePath private val documentsDir: String = Environment.DIRECTORY_DOCUMENTS private val documentsPath: String = "$externalStorage/$documentsDir" private fun createFile() { val fileName = "test.txt" val file = File(documentsPath, fileName) file.writeText("testing file...") } private fun getFilesList(path: String) { File(path).walk() .forEach { println("WALK: $it") } } }
方案二:申请全文件访问权限(仅适合文件管理器类APP)
如果你的APP是文件管理器这类需要访问所有文件的应用,可申请MANAGE_EXTERNAL_STORAGE权限,但该权限需要用户手动在系统设置中授权,且Google Play对这类权限的审核严格。
步骤1:修正Manifest权限
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" tools:ignore="ScopedStorage" /> <!-- 忽略Lint警告 -->
步骤2:跳转系统设置授权
import android.content.Intent import android.net.Uri import android.os.Build import android.os.Environment import android.provider.Settings import android.widget.Toast import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.contract.ActivityResultContracts import androidx.appcompat.app.AppCompatActivity import java.io.File import android.os.Environment class MainActivity : AppCompatActivity() { private lateinit var requestManageStorageLauncher: ActivityResultLauncher<Intent> override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) requestManageStorageLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && Environment.isExternalStorageManager()) { proceedWithOperations() } else { Toast.makeText(this, "需要全文件访问权限才能读取所有文件", Toast.LENGTH_SHORT).show() } } checkManageStoragePermission() } private fun checkManageStoragePermission() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { if (!Environment.isExternalStorageManager()) { val intent = Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION).apply { data = Uri.parse("package:$packageName") } requestManageStorageLauncher.launch(intent) } else proceedWithOperations() } else { // Android 9及以下复用方案一的READ_EXTERNAL_STORAGE权限申请逻辑 } } private fun proceedWithOperations() { createFile() getFilesList(documentsPath) } // 原有代码逻辑 private val externalStorage: String = Environment.getExternalStorageDirectory().absolutePath private val documentsDir: String = Environment.DIRECTORY_DOCUMENTS private val documentsPath: String = "$externalStorage/$documentsDir" private fun createFile() { val fileName = "test.txt" val file = File(documentsPath, fileName) file.writeText("testing file...") } private fun getFilesList(path: String) { File(path).walk() .forEach { println("WALK: $it") } } }
关键注意事项
- 分区存储是趋势:Google强烈推荐使用
MediaStore或SAF(存储访问框架)访问文件,MANAGE_EXTERNAL_STORAGE权限仅开放给特定类型的APP。 - 权限适配版本:不同Android版本的权限机制差异较大,需针对性处理。
- 避免滥用权限:不要为了省事就申请全文件访问权限,否则可能无法通过Google Play审核。
内容的提问来源于stack exchange,提问作者Felipe Damaro
相关产品推荐
相关产品推荐

