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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.27 17:17:50