Android开发:如何从Uri获取任意文件的真实路径(CSV适配问题)
适配任意类型文件(含CSV)的真实路径获取方案
你的原有代码只针对图片文件做了适配,依赖了MediaStore.Images.Media.DATA这个特定于图片媒体的列,所以对于CSV这类非图片文件自然无法正常获取路径。下面是一套通用的解决方案,能处理包括CSV在内的绝大多数文件类型的Uri:
通用实现代码
private fun getRealPathFromURI(contentURI: Uri, context: Context): String? { // 直接处理file://协议的本地文件Uri if (contentURI.scheme == "file") { return contentURI.path } var realPath: String? = null // 处理Android 4.4及以上的文档类型Uri(通过DocumentsProvider提供) if (DocumentsContract.isDocumentUri(context, contentURI)) { when { // 外部存储目录的文件 isExternalStorageDocument(contentURI) -> { val docId = DocumentsContract.getDocumentId(contentURI) val split = docId.split(":") if ("primary".equals(split[0], ignoreCase = true)) { realPath = "${Environment.getExternalStorageDirectory()}/${split[1]}" } } // 下载目录的文件(CSV大多存这里) isDownloadsDocument(contentURI) -> { val id = DocumentsContract.getDocumentId(contentURI) val contentUri = ContentUris.withAppendedId( Uri.parse("content://downloads/public_downloads"), id.toLong() ) realPath = getDataColumn(context, contentUri, null, null) } // 媒体库中的文件(包括图片、音频、视频,以及被索引的CSV等) isMediaDocument(contentURI) -> { val docId = DocumentsContract.getDocumentId(contentURI) val split = docId.split(":") val type = split[0] val contentUri = when (type) { "image" -> MediaStore.Images.Media.EXTERNAL_CONTENT_URI "video" -> MediaStore.Video.Media.EXTERNAL_CONTENT_URI "audio" -> MediaStore.Audio.Media.EXTERNAL_CONTENT_URI // 非媒体类型用Files表查询 else -> MediaStore.Files.getContentUri("external") } val selection = "_id=?" val selectionArgs = arrayOf(split[1]) realPath = getDataColumn(context, contentUri, selection, selectionArgs) } } } else if ("content".equals(contentURI.scheme, ignoreCase = true)) { // 普通ContentProvider的Uri,直接查询数据列 realPath = getDataColumn(context, contentURI, null, null) } return realPath } // 辅助函数:判断是否为外部存储文档Uri private fun isExternalStorageDocument(uri: Uri): Boolean { return "com.android.externalstorage.documents" == uri.authority } // 辅助函数:判断是否为下载目录文档Uri private fun isDownloadsDocument(uri: Uri): Boolean { return "com.android.providers.downloads.documents" == uri.authority } // 辅助函数:判断是否为媒体库文档Uri private fun isMediaDocument(uri: Uri): Boolean { return "com.android.providers.media.documents" == uri.authority } // 通用查询ContentProvider中_data列的函数(存储真实文件路径) private fun getDataColumn(context: Context, uri: Uri?, selection: String?, selectionArgs: Array<String>?): String? { var cursor: Cursor? = null val column = "_data" val projection = arrayOf(column) try { uri ?: return null cursor = context.contentResolver.query(uri, projection, selection, selectionArgs, null) if (cursor != null && cursor.moveToFirst()) { val index = cursor.getColumnIndexOrThrow(column) return cursor.getString(index) } } catch (e: Exception) { e.printStackTrace() } finally { cursor?.close() } return null }
关键说明
- 多Uri类型适配:覆盖了
file://本地文件和content://内容提供者两种核心Uri类型,针对不同来源的ContentProvider(外部存储、下载、媒体库)做了专属处理,尤其是下载目录——这是CSV文件最常存放的位置。 - 通用列查询:使用
_data列来获取真实路径,这个列是绝大多数ContentProvider都会提供的,不会像你原来的代码那样依赖图片专属列。 - Android版本兼容:通过
DocumentsContract处理了Android 4.4及以上的文档选取场景,这是现在文件选择的主流方式。
注意事项(针对Android 10+)
从Android 10(API 29)开始,系统引入了分区存储机制,_data列可能会返回null,或者即使拿到路径你也没有权限直接访问文件。这种情况下,更推荐的做法是直接通过ContentResolver.openInputStream(contentURI)获取文件输入流来读取内容,而不是执着于获取真实路径——这不仅更符合系统权限规则,也能避免很多适配问题。
如果确实需要获取真实路径,你需要申请MANAGE_EXTERNAL_STORAGE特殊权限,但这个权限需要用户手动在系统设置中开启,只建议在特殊场景下使用。
内容的提问来源于stack exchange,提问作者Mickael Belhassen
相关产品推荐
相关产品推荐

