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

Scoped Storage技术问题:Documents目录日志文件重复创建及查询返回空求助

问题排查与修复方案

核心问题分析

1. 文件匹配失败导致重复创建

createNewFileAndWrite方法中设置DISPLAY_NAME时未添加.txt后缀,但查询时却判断文件名包含.txt。MediaStore不会自动为无后缀文件补充后缀,导致查询时无法匹配到已创建的文件,每次都会触发新文件创建,系统为避免重名自动生成(1)、(2)这类后缀的文件。

2. 查询条件不严谨引发Cursor异常

  • 仅匹配路径未过滤文件类型,会把目录下的文件夹、其他类型文件全部查出,增加匹配难度
  • 直接使用cursor!!强制非空,当contentResolver.query返回null时会抛出空指针,导致后续逻辑中断
  • 路径格式不统一:代码中FOLDER以/开头和结尾,拼接后路径带有冗余斜杠,与MediaStore实际存储的RELATIVE_PATH格式不匹配

3. 写入模式错误无法实现追加

使用"rwt"模式会清空原有内容再写入,若要实现追加需改用"wa"模式。


修复后的完整代码

const val FOLDER = "Test/TestDocuments" // 去掉首尾斜杠,统一路径格式
const val FILE_NAME = "Log"

fun Context.createFolder() {
    try {
        val values = ContentValues().apply {
            put(
                MediaStore.MediaColumns.RELATIVE_PATH,
                "${Environment.DIRECTORY_DOCUMENTS}/$FOLDER"
            )
            put(MediaStore.MediaColumns.IS_DIRECTORY, true) // 明确标记为目录
        }
        val uri = contentResolver.insert(
            MediaStore.Files.getContentUri("external"),
            values
        )
        Toast.makeText(this, "目录创建成功: $uri", Toast.LENGTH_SHORT).show()
    } catch (e: Exception) {
        Toast.makeText(this, "目录创建失败: ${e.message}", Toast.LENGTH_SHORT).show()
    }
}

fun Context.findAndAppendToFile() {
    val contentUri = MediaStore.Files.getContentUri("external")
    val targetFileName = "$FILE_NAME${getPreferenceData(LOGGER_UNIQUE_ID)}.txt"
    val targetPath = "${Environment.DIRECTORY_DOCUMENTS}/$FOLDER/"

    // 精确查询:匹配路径+文件名+排除目录
    val selection = """
        ${MediaStore.MediaColumns.RELATIVE_PATH} = ? 
        AND ${MediaStore.MediaColumns.DISPLAY_NAME} = ? 
        AND ${MediaStore.MediaColumns.IS_DIRECTORY} = ?
    """.trimIndent()
    val selectionArgs = arrayOf(targetPath, targetFileName, "0")

    contentResolver.query(contentUri, null, selection, selectionArgs, null)?.use { cursor ->
        var uri: Uri? = null
        if (cursor.moveToFirst()) {
            val id = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.MediaColumns._ID))
            uri = ContentUris.withAppendedId(contentUri, id)
        }

        if (uri == null) {
            Toast.makeText(this, "未找到目标日志文件,创建新文件", Toast.LENGTH_SHORT).show()
            createNewFileAndWrite()
        } else {
            try {
                contentResolver.openOutputStream(uri, "wa")?.use { outputStream ->
                    outputStream.write("追加的日志内容\n".toByteArray())
                }
                Toast.makeText(this, "日志追加成功", Toast.LENGTH_SHORT).show()
            } catch (e: IOException) {
                Toast.makeText(this, "日志写入失败: ${e.message}", Toast.LENGTH_SHORT).show()
            }
        }
    } ?: run {
        Toast.makeText(this, "查询文件失败,尝试创建新文件", Toast.LENGTH_SHORT).show()
        createNewFileAndWrite()
    }
}

fun Context.createNewFileAndWrite() {
    val targetFileName = "$FILE_NAME${getPreferenceData(LOGGER_UNIQUE_ID)}.txt"
    val values = ContentValues().apply {
        put(MediaStore.MediaColumns.DISPLAY_NAME, targetFileName) // 直接带.txt后缀
        put(MediaStore.MediaColumns.MIME_TYPE, "text/plain")
        put(
            MediaStore.MediaColumns.RELATIVE_PATH,
            "${Environment.DIRECTORY_DOCUMENTS}/$FOLDER/"
        )
        put(MediaStore.MediaColumns.IS_DIRECTORY, false) // 明确标记为文件
    }

    contentResolver.insert(MediaStore.Files.getContentUri("external"), values)?.let { uri ->
        try {
            contentResolver.openOutputStream(uri)?.use { outputStream ->
                outputStream.write("初始日志内容\n".toByteArray())
            }
            Toast.makeText(this, "新日志文件创建成功: $uri", Toast.LENGTH_SHORT).show()
        } catch (e: IOException) {
            Toast.makeText(this, "新文件写入失败: ${e.message}", Toast.LENGTH_SHORT).show()
        }
    } ?: run {
        Toast.makeText(this, "新文件创建失败", Toast.LENGTH_SHORT).show()
    }
}

// 示例:getPreferenceData实现(确保返回当日唯一标识)
fun Context.getPreferenceData(key: String): String {
    val prefs = getSharedPreferences("AppPrefs", MODE_PRIVATE)
    val today = SimpleDateFormat("yyyyMMdd", Locale.getDefault()).format(Date())
    prefs.edit().putString(key, today).apply()
    return prefs.getString(key, today) ?: today
}

关键修复点说明

  • 路径格式统一:将FOLDER改为不带首尾斜杠的格式,拼接时用/连接,确保与MediaStore存储的RELATIVE_PATH格式一致
  • 文件名一致性:创建文件时直接指定带.txt的文件名,查询时用完全相同的名称匹配,避免后缀不匹配问题
  • 精确查询条件:添加IS_DIRECTORY = 0过滤文件,同时匹配路径和文件名,减少无效结果;使用getColumnIndexOrThrow避免列名错误
  • 安全资源处理:使用use函数自动关闭Cursor和OutputStream,避免资源泄漏;处理query返回null的异常情况
  • 追加模式设置:使用"wa"模式打开输出流,实现内容追加而非覆盖
  • 目录创建修正:添加IS_DIRECTORY = true明确标记为目录,确保创建的是文件夹而非文件

内容的提问来源于stack exchange,提问作者vishnuc156

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.03 19:25:20