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

Android 11作用域存储API无法正确统计SD卡及私有文件夹文件数量

Android 11下MediaStore查询自建文件数量返回0的问题解决

问题根源

  1. 应用私有目录不被MediaStore索引:/Android/data/com.nj.scopedstorage/files属于应用专属私有目录,MediaStore不会收录该目录下的任何文件,所以用MediaStore查询这个目录本身就不可能得到结果。
  2. File API创建的非媒体文件不会自动被扫描:你用File.createNewFile()创建的TXT属于非媒体文件,MediaStore不会主动扫描这类文件,自然查不到。
  3. 查询条件写法错误:LIKE操作符缺少通配符,而且MediaStore.MediaColumns.DATA在Android 10+已被废弃,用它做路径匹配本身就有问题。

具体解决方案

1. 统计应用私有目录的文件数量

直接用File API遍历即可,完全不需要依赖MediaStore:

private fun countPrivateFolderFiles(context: Context): Int {
    // 获取应用私有files目录的File对象
    val privateFolder = context.filesDir
    // 返回目录内文件数量,空目录或无权限时返回0
    return privateFolder.listFiles()?.size ?: 0
}

2. 统计SD卡NJFiles文件夹的文件数量

有两种可行方式,按需选择:

方式一:直接用File API遍历(已获取全文件权限,最简单高效)

private fun countSdCardNjFiles(sdCardRoot: String?): Int {
    if (sdCardRoot.isNullOrEmpty()) return 0
    val njFolder = File("$sdCardRoot/$sdCardNjFolder")
    return njFolder.listFiles()?.size ?: 0
}

方式二:用MediaStore查询(需先让文件被索引)

如果一定要用MediaStore查询,需要先让系统扫描到你创建的文件,同时修正查询逻辑:

步骤1:创建文件后触发媒体扫描

在fileSdCard.createNewFile()之后添加扫描代码:

// 触发媒体扫描,让MediaStore收录新创建的文件
MediaScannerConnection.scanFile(
    this,
    arrayOf(fileSdCard.absolutePath),
    arrayOf("text/plain") // 指定文件MIME类型
) { _, _ -> 
    // 扫描完成的回调,可做后续操作
}
步骤2:修正MediaStore查询逻辑

改用RELATIVE_PATH替代废弃的DATA字段,同时补充LIKE的通配符:

private fun getFilesFromFolder(
    context: Context,
    fileNames: HashSet<String>,
    privateFolder: Boolean
): HashSet<String>? {
    try {
        if (privateFolder) {
            // 私有目录直接返回空,改用File API统计
            return fileNames
        }
        
        val projection = arrayOf(MediaStore.Files.FileColumns.DISPLAY_NAME)
        // 匹配RELATIVE_PATH以NJFiles文件夹开头的文件
        val selection = "${MediaStore.Files.FileColumns.RELATIVE_PATH} LIKE ?"
        val selectionArgs = arrayOf("${sdCardNjFolder}/%")

        context.contentResolver.query(
            MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL),
            projection,
            selection,
            selectionArgs,
            null
        )?.use { cursor ->
            val nameColumn = cursor.getColumnIndex(MediaStore.Files.FileColumns.DISPLAY_NAME)
            while (cursor.moveToNext()) {
                fileNames.add(cursor.getString(nameColumn))
            }
        }
    } catch (e: Exception) {
        e.printStackTrace()
    }
    return fileNames
}
额外推荐:直接用MediaStore API创建文件(更符合Scoped Storage规范)

如果从创建环节就用MediaStore API,文件会自动被索引,无需额外扫描:

private fun createFileViaMediaStore(context: Context, fileName: String) {
    val contentValues = ContentValues().apply {
        put(MediaStore.Files.FileColumns.DISPLAY_NAME, fileName)
        put(MediaStore.Files.FileColumns.RELATIVE_PATH, sdCardNjFolder)
        put(MediaStore.Files.FileColumns.MIME_TYPE, "text/plain")
        put(MediaStore.Files.FileColumns.IS_PENDING, 1)
    }

    val uri = context.contentResolver.insert(
        MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL),
        contentValues
    )

    uri?.let {
        // 创建空文件直接关闭输出流即可
        context.contentResolver.openOutputStream(it)?.close()
        // 标记文件创建完成
        contentValues.clear()
        contentValues.put(MediaStore.Files.FileColumns.IS_PENDING, 0)
        context.contentResolver.update(it, contentValues, null, null)
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 01:44:57