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

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类型的匹配规则:

  1. 从返回的Uri中解析document id,比如示例Uricontent://com.android.providers.media.documents/document/document%3A27958解码后得到id为document:27958,截取冒号后的数字部分得到媒体库记录id 27958
  2. 以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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 05:39:19