Kotlin替换存储选择器后,目录文件检测失败问题求助
Android目录选择后无法检测文件/目录存在性问题解决
要实现的功能
- 让用户选择文件夹
- 检查该文件夹里有没有
Morrowind.ini文件和Data Files目录 - 符合条件就把路径赋值给
game_files变量
当前写的代码
触发文件夹选择的代码
findPreference("game_files").setOnPreferenceClickListener { // 用ACTION_OPEN_DOCUMENT_TREE让用户选目录 val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE) startActivityForResult(intent, REQUEST_CODE_OPEN_DOCUMENT_TREE) true } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) if (requestCode == REQUEST_CODE_OPEN_DOCUMENT_TREE && resultCode == Activity.RESULT_OK) { data?.data?.also { uri -> // 保存目录的访问权限 activity.contentResolver.takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION) // 把选中目录的路径传给setupData val selectedDirectory = DocumentFile.fromTreeUri(activity, uri) val path = selectedDirectory?.uri?.path ?: "" setupData(path) } } }
检测文件/目录的setupData方法
private fun setupData(path: String) { val sharedPref = preferenceScreen.sharedPreferences // 先清空旧值,出错时不会保留无效路径 var gameFiles = "" val inst = GameInstaller(path) if (inst.check()) { val iniFile = File(path, "Morrowind.ini") val dataFilesFolder = File(path, "Data Files") if (iniFile.exists() && dataFilesFolder.exists() && dataFilesFolder.isDirectory) { inst.setNomedia() if (!inst.convertIni(sharedPref.getString("pref_encoding", GameInstaller.DEFAULT_CHARSET_PREF)!!)) { showError(R.string.data_error_title, R.string.ini_error_message) } else { gameFiles = path } } else { // 找不到文件或目录时弹错误提示 showError(R.string.data_error_title, R.string.data_error_message) } } }
问题现象
明明选的文件夹里确实有Morrowind.ini和Data Files,但运行时还是弹出文件缺失的错误提示。
问题原因和修复方案
核心问题
用ACTION_OPEN_DOCUMENT_TREE拿到的URI路径是文档提供者的虚拟路径(比如/tree/primary:Games/Morrowind),不是真实的文件系统路径,File类认不出这个路径,自然判断不了文件/目录是否存在。
修复代码
第一步:修改onActivityResult,直接传DocumentFile
findPreference("game_files").setOnPreferenceClickListener { val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE) startActivityForResult(intent, REQUEST_CODE_OPEN_DOCUMENT_TREE) true } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) if (requestCode == REQUEST_CODE_OPEN_DOCUMENT_TREE && resultCode == Activity.RESULT_OK) { data?.data?.also { uri -> activity.contentResolver.takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION) // 直接把DocumentFile对象传给setupData,不要转成字符串路径 val selectedDirectory = DocumentFile.fromTreeUri(activity, uri) selectedDirectory?.let { setupData(it) } } } }
第二步:修改setupData,用DocumentFile检测文件/目录
private fun setupData(selectedDir: DocumentFile) { val sharedPref = preferenceScreen.sharedPreferences var gameFiles = "" // 如果GameInstaller需要字符串路径,先转成真实路径 val path = getRealPathFromUri(selectedDir.uri) ?: "" val inst = GameInstaller(path) if (inst.check()) { // 用DocumentFile的findFile方法找子文件和目录 val iniFile = selectedDir.findFile("Morrowind.ini") val dataFilesFolder = selectedDir.findFile("Data Files") // 检查:ini是文件且存在,Data Files是目录且存在 if (iniFile != null && iniFile.isFile && dataFilesFolder != null && dataFilesFolder.isDirectory) { inst.setNomedia() if (!inst.convertIni(sharedPref.getString("pref_encoding", GameInstaller.DEFAULT_CHARSET_PREF)!!)) { showError(R.string.data_error_title, R.string.ini_error_message) } else { gameFiles = path } } else { showError(R.string.data_error_title, R.string.data_error_message) } } } // 辅助方法:把Document URI转成真实文件路径(适配不同Android版本) private fun getRealPathFromUri(uri: Uri): String? { val context = activity ?: return null // 先尝试用MediaStore查询 val projection = arrayOf(MediaStore.Files.FileColumns.DATA) val cursor = context.contentResolver.query(uri, projection, null, null, null) cursor?.use { if (it.moveToFirst()) { val columnIndex = it.getColumnIndexOrThrow(MediaStore.Files.FileColumns.DATA) return it.getString(columnIndex) } } // 不行就用DocumentsContract解析 return try { val docId = DocumentsContract.getDocumentId(uri) val split = docId.split(":") val type = split[0] if ("primary".equals(type, ignoreCase = true)) { "${Environment.getExternalStorageDirectory()}/${split[1]}" } else { null } } catch (e: Exception) { null } }
额外说明
DocumentFile是Android官方推荐的操作文档提供者文件的方式,能正确识别通过系统选择的目录下的内容- 如果
GameInstaller可以修改,建议直接让它支持DocumentFile或Uri操作,避免路径转换的麻烦 - 不需要额外申请存储权限,
ACTION_OPEN_DOCUMENT_TREE已经通过系统弹窗获得了授权
内容的提问来源于stack exchange,提问作者Jared Davenport
相关产品推荐
相关产品推荐

