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

Android 11文件选择器上传崩溃修复:文件找不到问题排查

问题原因

Android 11及以上版本启用了分区存储(Scoped Storage),通过ACTION_OPEN_DOCUMENT获取的是内容URI(content://开头),而非直接的文件路径。你之前依赖的getRealPathFromURIWithProvider方法在高版本系统中无法获取到有效的可访问文件路径,导致创建的File对象指向不存在的位置,最终触发FileNotFoundException。

android:requestLegacyExternalStorage="true"仅对旧版的存储访问逻辑生效,不适用于通过SAF(存储访问框架)获取的URI,所以添加该属性无法解决问题。

解决方案

直接通过内容URI获取文件输入流,自定义RequestBody实现上传,无需转换为File对象:

1. 调整变量存储

替换原有的uploadFile变量,改为存储选中的URI和文件名:

private var selectedUri: Uri? = null
private var selectedFileName: String? = null

2. 重构选择文件后的回调逻辑

在ActivityResultLauncher的回调中,不再尝试转换为File,而是保存URI并获取文件名:

resultIntent = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
    if (result.resultCode == Activity.RESULT_OK) {
        val data = result.data
        data?.let {
            val uri = it.data
            uri?.let { itUri ->
                binding.coverImg.load(itUri)
                selectedUri = itUri
                selectedFileName = getFileNameFromUri(this, itUri)
            }
        }
    }
}

3. 添加获取文件名的工具方法

从内容URI中解析出文件显示名称:

private fun getFileNameFromUri(context: Context, uri: Uri): String? {
    var fileName: String? = null
    if (uri.scheme == "content") {
        val cursor = context.contentResolver.query(uri, null, null, null, null)
        cursor?.use {
            if (it.moveToFirst()) {
                val nameColumnIndex = it.getColumnIndex(android.provider.OpenableColumns.DISPLAY_NAME)
                fileName = it.getString(nameColumnIndex)
            }
        }
    }
    // 如果content查询失败,尝试从路径截取
    if (fileName == null) {
        fileName = uri.path?.split("/")?.last()
    }
    return fileName
}

4. 修改上传逻辑

通过输入流创建自定义RequestBody,直接读取内容URI对应的文件数据:

uploadBtn.setOnClickListener {
    selectedUri?.let { uri ->
        selectedFileName?.let { fileName ->
            // 获取文件MIME类型
            val contentType = contentResolver.getType(uri)?.toMediaTypeOrNull() 
                ?: "application/octet-stream".toMediaTypeOrNull()
            
            val reqFile = object : RequestBody() {
                override fun contentType(): MediaType? = contentType

                override fun writeTo(sink: BufferedSink) {
                    contentResolver.openInputStream(uri)?.use { inputStream ->
                        sink.writeAll(Okio.source(inputStream))
                    }
                }
            }

            // 构建Multipart请求
            multiPart.addFormDataPart("file", fileName, reqFile)
            requestBody = multiPart.build()
            
            // 执行后续上传请求逻辑
        }
    }
}

额外注意事项

  • 确保在使用contentResolver.openInputStream(uri)时,应用仍持有该URI的访问权限(通过ACTION_OPEN_DOCUMENT获取的URI默认拥有长期访问权限,无需额外处理)。
  • 如果需要上传大文件,建议在后台协程中执行上传操作,避免阻塞主线程。

内容的提问来源于stack exchange,提问作者Dr.KeyOk

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.10 04:35:28