Android文件选择器返回的URI文件名错误,该如何解决?
解决Android文件选择器返回Uri无真实文件名的问题
这不是你的代码错误,而是Android的设计逻辑导致的:ACTION_OPEN_DOCUMENT和ACTION_GET_CONTENT返回的是内容URI,指向的是ContentProvider提供的抽象数据,而非直接的文件路径,所以Uri中的image%3A25这类标识只是ContentProvider的内部索引,不是真实文件名。
要获取真实文件名和扩展名,你需要通过ContentResolver查询该Uri对应的媒体元数据,具体方案如下:
1. 编写工具函数获取真实文件名
实现一个工具方法,通过ContentResolver查询Uri对应的文件名,同时处理无扩展名的情况:
import android.content.Context import android.database.Cursor import android.net.Uri import android.provider.DocumentsContract import android.provider.MediaStore import android.webkit.MimeTypeMap fun getRealFileName(context: Context, uri: Uri): String? { var fileName: String? = null // 优先从MediaStore查询标准媒体文件信息 val projection = arrayOf( MediaStore.MediaColumns.DISPLAY_NAME, MediaStore.MediaColumns.MIME_TYPE ) context.contentResolver.query(uri, projection, null, null, null)?.use { cursor -> if (cursor.moveToFirst()) { fileName = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DISPLAY_NAME)) // 若文件名无扩展名,通过MIME类型补充 val mimeType = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.MIME_TYPE)) if (fileName?.contains('.') != true && mimeType != null) { MimeTypeMap.getSingleton().getExtensionFromMimeType(mimeType)?.let { ext -> fileName = "$fileName.$ext" } } } } // 处理DocumentsProvider返回的Uri(比如第三方文件管理器) if (fileName == null && DocumentsContract.isDocumentUri(context, uri)) { val docId = DocumentsContract.getDocumentId(uri) // 处理图片类型文档 if (docId.startsWith("image:")) { val id = docId.split(":")[1] val imageProjection = arrayOf(MediaStore.Images.Media.DISPLAY_NAME) val selection = "${MediaStore.Images.Media._ID} = ?" val selectionArgs = arrayOf(id) context.contentResolver.query( MediaStore.Images.Media.EXTERNAL_CONTENT_URI, imageProjection, selection, selectionArgs, null )?.use { cursor -> if (cursor.moveToFirst()) { fileName = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DISPLAY_NAME)) } } } // 可扩展处理video、audio、普通文件等其他类型 } // 兜底返回默认文件名 return fileName ?: "unknown_file_${System.currentTimeMillis()}" }
2. 修改原有代码传递真实文件名
在处理选择结果的地方,调用工具函数获取文件名,再传入importFile方法(建议修改importFile方法,新增文件名参数):
val selectFilesActivityResult = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> if (result.resultCode == Activity.RESULT_OK) { val data: Intent? = result.data val context = this // 或者使用你的上下文对象 // 多选文件处理 data?.clipData?.let { clipData -> val count = clipData.itemCount for (i in 0 until count) { val uri = clipData.getItemAt(i).uri val fileName = getRealFileName(context, uri) fileUtils.importFile(uri, viewModel.getInternalPath(), fileName) } } // 单选文件处理 data?.data?.let { uri -> val fileName = getRealFileName(context, uri) fileUtils.importFile(uri, viewModel.getInternalPath(), fileName) } } }
注意事项
- 权限问题:Android 10及以下需要申请
READ_EXTERNAL_STORAGE权限;Android 11+通过ACTION_OPEN_DOCUMENT选择的文件,无需传统存储权限,系统会自动授予临时访问权限。 - 特殊文件处理:部分非媒体文件(如文档)可能无法在MediaStore中查询到信息,此时可以通过
ContentResolver.openInputStream读取文件流,再通过文件头判断类型补充扩展名。 - 避免路径转换:不要尝试将内容Uri转换为真实文件路径,Android 10+的存储沙箱机制限制了直接访问外部存储路径,使用SAF(存储访问框架)提供的Uri进行操作是官方推荐的方式。
内容的提问来源于stack exchange,提问作者AashishKSahu
相关产品推荐
相关产品推荐

