Android中file://与content:// URI混淆及文件上传问题求助
解决Android 7.0+中content:// URI转可上传文件的问题
嘿,隔了几年回Android开发坑,碰到URI权限的变化确实头大!7.0开始的沙箱安全限制,直接把file://的路子堵死了——你拿到的content:// URI是系统给的访问凭证,根本没法直接转成真实的文件路径(系统也不允许)。下面给你两个合规的解决方案,完美适配你的7.0模拟器场景:
方案1:直接读取Content流上传(推荐,最符合Android规范)
不用费劲转file://,直接用ContentResolver从content:// URI获取文件输入流,把流直接传给服务器的上传接口就行。这是官方推荐的做法,不用处理文件复制,也不会踩权限坑。
代码示例
1. onActivityResult中处理返回的URI
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) if (requestCode == REQUEST_SELECT_FILE && resultCode == RESULT_OK) { data?.data?.let { contentUri -> // 直接调用上传方法,传入contentUri uploadFile(contentUri) } } }
2. 读取流并上传的方法
private fun uploadFile(contentUri: Uri) { // 获取ContentResolver val resolver = contentResolver // 打开文件输入流 resolver.openInputStream(contentUri)?.use { inputStream -> // 这里替换成你的服务器上传逻辑 // 比如用OkHttp上传流: val requestBody = RequestBody.create(MediaType.parse("application/octet-stream"), inputStream.readBytes()) val request = Request.Builder() .url("你的服务器上传接口地址") .post(requestBody) .build() OkHttpClient().newCall(request).enqueue(object : Callback { override fun onFailure(call: Call, e: IOException) { // 上传失败处理 Log.e("Upload", "Failed: ${e.message}") } override fun onResponse(call: Call, response: Response) { // 上传成功处理 Log.d("Upload", "Success: ${response.body()?.string()}") } }) } ?: run { // 无法打开流,处理错误 Toast.makeText(this, "无法读取文件", Toast.LENGTH_SHORT).show() } }
方案2:复制文件到应用私有目录再获取file路径(适配必须要本地路径的老接口)
如果你的服务器接口必须要求本地文件路径(比如老代码改不动),那可以把content://对应的文件复制到自己应用的私有目录(比如getFilesDir()或者getCacheDir()),然后用这个私有目录的file://路径去上传。
代码示例
1. 复制文件到私有目录的工具方法
private fun copyContentToPrivateFile(contentUri: Uri): File? { val resolver = contentResolver // 获取文件名(可以从ContentResolver查询) val fileName = getFileNameFromContentUri(contentUri) ?: "temp_file" // 创建应用私有目录的文件 val privateFile = File(filesDir, fileName) try { resolver.openInputStream(contentUri)?.use { inputStream -> FileOutputStream(privateFile).use { outputStream -> // 复制流 val buffer = ByteArray(4096) var bytesRead: Int while (inputStream.read(buffer).also { bytesRead = it } != -1) { outputStream.write(buffer, 0, bytesRead) } } } return privateFile } catch (e: IOException) { e.printStackTrace() return null } } // 从contentUri获取文件名的辅助方法 private fun getFileNameFromContentUri(contentUri: Uri): String? { val cursor = contentResolver.query(contentUri, null, null, null, null) cursor?.use { if (it.moveToFirst()) { val nameIndex = it.getColumnIndex(OpenableColumns.DISPLAY_NAME) return it.getString(nameIndex) } } return null }
2. onActivityResult中调用并上传
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) if (requestCode == REQUEST_SELECT_FILE && resultCode == RESULT_OK) { data?.data?.let { contentUri -> val privateFile = copyContentToPrivateFile(contentUri) privateFile?.let { file -> // 现在可以用file.path获取本地路径,传给上传接口 uploadFileWithPath(file.path) // 上传完成后记得删除临时文件(如果是缓存的话) // file.delete() } ?: run { Toast.makeText(this, "复制文件失败", Toast.LENGTH_SHORT).show() } } } } // 用文件路径上传的方法(适配老接口) private fun uploadFileWithPath(filePath: String) { val file = File(filePath) val requestBody = RequestBody.create(MediaType.parse("application/octet-stream"), file) // 后续上传逻辑和方案1类似,用file构建请求体即可 }
重要提醒
- 绝对不要用反射或者硬编码
/data/data/之类的路径去获取真实文件路径,这些都是hack手段,在Android 10+会直接失效,还可能触发权限异常。 - 如果你用的是Java,把上面的Kotlin代码转成Java语法就行,核心逻辑是完全一致的。
内容的提问来源于stack exchange,提问作者Zaeem Sattar
相关产品推荐
相关产品推荐

