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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.28 09:25:49