Android 12下如何获取pdf/doc等文档文件的Content URI
问题根因
依赖解析ContentUri获取本地真实文件路径的方案本身不具备跨版本兼容性:Android 10上线分区存储机制后,应用默认无权直接访问文件提供者暴露的资源对应的真实磁盘路径,即便拿到路径也会触发权限拒绝异常。
你现有工具类仅匹配了image、video、audio三类媒体的Uri规则,未覆盖com.android.providers.media.documents下的普通文档节点,因此选择doc、pdf、excel、txt等文件时会返回null路径。
推荐实现方案(全Android版本兼容,无需申请存储权限)
不要做任何Uri转真实路径的操作,直接通过ContentResolver获取文件输入流完成上传,该方案适配所有系统版本、所有文件类型、所有文件管理器返回的Uri,没有适配遗漏问题。
- 第一步:拉起文件选择器时明确指定支持的MIME类型,减少无效选择
// 示例代码使用Kotlin,Java逻辑完全一致 val pickIntent = Intent(Intent.ACTION_GET_CONTENT).apply { addCategory(Intent.CATEGORY_OPENABLE) // 先设置通配MIME,再通过EXTRA_MIME_TYPES限定具体支持的格式 type = "*/*" putExtra(Intent.EXTRA_MIME_TYPES, arrayOf( "application/msword", // .doc "application/vnd.openxmlformats-officedocument.wordprocessingml.document", // .docx "application/pdf", // .pdf "application/vnd.ms-excel", // .xls "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", // .xlsx "text/plain" // .txt )) } // 使用Activity Result API替代废弃的startActivityForResult即可 filePickLauncher.launch(pickIntent)
- 第二步:回调中拿到Uri后,直接读取元信息和输入流用于上传,不需要落地为本地临时文件
private val filePickLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> if (result.resultCode != Activity.RESULT_OK) return@registerForActivityResult val uri = result.data?.data ?: return@registerForActivityResult // 读取文件名、文件大小等基础元信息 val (fileName, fileSize) = contentResolver.query(uri, null, null, null, null)?.use { cursor -> cursor.moveToFirst() val nameCol = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME) val sizeCol = cursor.getColumnIndex(OpenableColumns.SIZE) Pair( cursor.getString(nameCol) ?: "unknown_file", cursor.getLong(sizeCol) ) } ?: Pair("unknown_file", 0L) // 直接打开输入流传入上传逻辑 contentResolver.openInputStream(uri)?.use { inputStream -> // 执行上传逻辑,把inputStream作为请求体传入即可 uploadToOwnServer(inputStream, fileName, fileSize) } }
临时适配方案(不推荐,高版本存在兼容/权限问题)
如果暂时无法重构现有上传逻辑,必须拿到文件路径,可以在现有UriUtils中补全Media Document类型的匹配规则:
- 从返回的Uri中解析document id,比如示例Uri
content://com.android.providers.media.documents/document/document%3A27958解码后得到id为document:27958,截取冒号后的数字部分得到媒体库记录id 27958 - 以
MediaStore.Files.getContentUri("external")为查询目标,以_id = ?为查询条件,传入刚才拿到的数字id,查询MediaStore.MediaColumns.DATA字段即可拿到对应路径
注意:该方案存在明确缺陷:Android 10及以上版本无存储权限时,即便查到路径也无法读取文件;Android 13+的细粒度媒体权限不覆盖文档类文件,该方案会直接失效;不同厂商、第三方文件管理器返回的Uri规则不统一,硬编码匹配规则会出现大量机型兼容问题。
避坑提示
- 不要硬编码各类DocumentProvider的authority做匹配,不同ROM、不同文件应用返回的authority没有统一标准
MediaStore.MediaColumns.DATA字段从Android 10开始已被官方标记为废弃,官方文档明确说明不建议应用依赖该字段访问文件- 基于ContentResolver操作输入流的方案不需要申请任何存储权限,兼容Android 4.4到Android 14的所有正式版本,不需要针对不同文件类型做单独适配
内容的提问来源于stack exchange,提问作者Chirag Savsani
相关产品推荐
相关产品推荐

