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
相关产品推荐
相关产品推荐

