Android 11作用域存储API无法正确统计SD卡及私有文件夹文件数量
Android 11下MediaStore查询自建文件数量返回0的问题解决
问题根源
- 应用私有目录不被MediaStore索引:
/Android/data/com.nj.scopedstorage/files属于应用专属私有目录,MediaStore不会收录该目录下的任何文件,所以用MediaStore查询这个目录本身就不可能得到结果。 - File API创建的非媒体文件不会自动被扫描:你用
File.createNewFile()创建的TXT属于非媒体文件,MediaStore不会主动扫描这类文件,自然查不到。 - 查询条件写法错误:
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
相关产品推荐
相关产品推荐

