目录
- 核心概念:HTTP 上传
- 主流上传方案
- 使用
HttpURLConnection(原生,无需库) - 使用 OkHttp (强烈推荐,现代 Android 开发标准)
- 使用 Retrofit (基于 OkHttp,适合 RESTful API)
- 使用
- 关键步骤与代码示例 (以 OkHttp 为例)
- 步骤 1:添加依赖
- 步骤 2:获取文件与创建请求体
- 步骤 3:构建并发起请求
- 步骤 4:处理响应
- 高级功能与最佳实践
- 上传进度监听
- 多线程/分块上传
- 断点续传
- 权限处理
- 错误处理
- 后台执行 (使用 WorkManager)
核心概念:HTTP 上传
在 Web 世界中,文件上传通常通过 HTTP 协议的 POST 请求来完成,最常用的两种格式是:

-
multipart/form-data:- 这是最通用、最标准的文件上传方式。
- 它允许你在一个
POST请求中同时发送文件数据和表单字段(文件名、描述、用户ID等)。 - 请求体被分割成多个部分(part),每个部分之间由一个特定的边界字符串分隔。
- 服务器端(如 PHP, Java Spring, Node.js)都有成熟的库来解析这种格式。
-
application/octet-stream:- 这种方式只发送文件二进制流本身,不包含任何额外的表单数据。
- 通常用于简单的、只上传文件的场景,灵活性较差。
对于绝大多数 Android 应用,你都应该使用 multipart/form-data。
主流上传方案
使用 HttpURLConnection (原生)
这是 Java 标准库自带的类,无需添加任何第三方依赖。

- 优点:无依赖,轻量级。
- 缺点:
- API 较为繁琐,代码量大。
- 处理流、线程、回调等需要手动编写较多代码。
- 不支持现代的 HTTP/2。
- 处理上传进度比较麻烦。
适用场景:对项目依赖有严格限制,或上传逻辑极其简单的场景。不推荐用于新项目。
使用 OkHttp (强烈推荐)
OkHttp 是目前 Android 和 Java 平台上最流行的 HTTP 客户端。
- 优点:
- 性能卓越:支持 HTTP/2、连接池,能有效减少延迟。
- API 简洁:代码可读性高,易于使用。
- 功能强大:内置拦截器、缓存、超时控制等。
- 社区活跃:文档完善,问题容易解决。
- 易于扩展:可以方便地添加拦截器来实现进度监听、日志等功能。
适用场景:几乎所有需要网络请求的 Android 应用,是现代 Android 开发的首选。
使用 Retrofit
Retrofit 是一个类型安全的 HTTP 客户端,它底层就是 OkHttp。

- 优点:
- 接口定义:通过 Java 接口来定义 API,代码非常清晰。
- 自动序列化/反序列化:自动将 JSON/XML 等数据转换为 Java 对象,无需手动解析。
- 与 OkHttp 无缝集成:可以轻松利用 OkHttp 的所有功能(如拦截器)。
- 非常适合 RESTful API:如果你的服务器 API 是 REST 风格的,Retrofit 能让你如虎添翼。
适用场景:当你的应用不仅需要上传文件,还需要与 RESTful API 进行复杂的交互(GET, POST, PUT, DELETE 等)时,Retrofit 是最佳选择。
关键步骤与代码示例 (以 OkHttp 为例)
下面我们以最推荐的 OkHttp 为例,演示如何实现一个完整的文件上传功能。
步骤 1:添加依赖
在项目的 app/build.gradle 文件中添加 OkHttp 依赖:
dependencies {
implementation("com.squareup.okhttp3:okhttp:4.12.0") // 使用最新版本
}
步骤 2:获取文件与创建请求体
假设我们要从设备存储中选择一个图片文件进行上传。
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.MultipartBody
import okhttp3.OkHttpClient
import okhttp3.Request
import java.io.File
// 1. 获取要上传的文件
val file = File("/path/to/your/image.jpg") // 替换为你的实际文件路径
// 2. 检查文件是否存在
if (!file.exists()) {
// 处理文件不存在的情况
return
}
// 3. 创建 MultipartBody.Part
// "file" 是服务器端接收文件时使用的字段名
// "upload.jpg" 是提供给服务器的文件名
val requestBody = MultipartBody.Part.createFormData(
"file",
"upload.jpg",
RequestBody.create("image/jpeg".toMediaType(), file)
)
// (可选) 添加其他表单字段
val description = RequestBody.create("text/plain".toMediaType(), "This is a test image")
解释:
MultipartBody.Part:代表了multipart/form-data中的一个“部分”,专门用于上传文件。RequestBody:代表了请求体的一个部分,对于文件,我们使用RequestBody.create()来创建它,并指定文件的MediaType(image/jpeg),对于普通的文本字段,也使用RequestBody。
步骤 3:构建并发起请求
val client = OkHttpClient()
// 4. 构建请求
val request = Request.Builder()
.url("https://your-api-endpoint.com/upload") // 替换为你的服务器地址
.post(
MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addPart(requestBody) // 添加文件部分
.addPart("description", description) // 添加文本部分
.build()
)
.build()
// 5. 发起请求并处理响应
client.newCall(request).enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
// 请求失败,例如网络错误、超时等
e.printStackTrace()
// 在主线程更新 UI
runOnUiThread {
Toast.makeText(this@MainActivity, "上传失败: ${e.message}", Toast.LENGTH_SHORT).show()
}
}
override fun onResponse(call: Call, response: Response) {
// 服务器成功返回响应
if (response.isSuccessful) {
val responseBody = response.body?.string()
// 在主线程更新 UI
runOnUiThread {
Toast.makeText(this@MainActivity, "上传成功: $responseBody", Toast.LENGTH_LONG).show()
}
} else {
// 服务器返回了错误状态码,如 400, 404, 500 等
runOnUiThread {
Toast.makeText(this@MainActivity, "上传失败: ${response.code}", Toast.LENGTH_SHORT).show()
}
}
}
})
解释:
- 我们使用
MultipartBody.Builder来构建完整的multipart/form-data请求体。 client.newCall(request).enqueue()是异步执行网络请求的标准方式,它会自动在后台线程执行,并通过回调返回结果。- 注意:网络请求不能在主线程(UI线程)中执行,否则会抛出
NetworkOnMainThreadException。enqueue方法已经帮我们处理了线程切换,但在回调中操作 UI 时,需要切回主线程,这里使用了runOnUiThread(在 Activity 中)。
高级功能与最佳实践
上传进度监听
OkHttp 本身不直接提供上传进度回调,但我们可以通过 拦截器 来实现。
class ProgressListener(private val progressListener: (Long, Long) -> Unit) : Callback {
// ... 其他代码保持不变 ...
override fun onResponse(call: Call, response: Response) {
// ... 原有代码 ...
}
// 创建一个包装了 ProgressRequestBody 的 MultipartBody.Part
fun createProgressPart(file: File, fieldName: String, filename: String): MultipartBody.Part {
val progressRequestBody = ProgressRequestBody(file, progressListener)
return MultipartBody.Part.createFormData(fieldName, filename, progressRequestBody)
}
}
// 自定义 RequestBody 来追踪上传进度
class ProgressRequestBody(
private val file: File,
private val progressListener: (Long, Long) -> Unit
) : RequestBody() {
override fun contentType(): MediaType? {
return "application/octet-stream".toMediaType()
}
override fun contentLength(): Long {
return file.length()
}
override fun writeTo(sink: BufferedSink) {
val fileLength = file.length()
val buffer = ByteArray(DEFAULT_BUFFER_SIZE)
var uploaded: Long = 0
val inputStream = FileInputStream(file)
inputStream.use { input ->
var read: Int
while (input.read(buffer).also { read = it } != -1) {
sink.write(buffer, 0, read)
uploaded += read
progressListener(uploaded, fileLength)
}
}
}
}
然后在构建请求时使用 createProgressPart,并传入一个 lambda 表达式来更新 UI 进度条。
多线程/分块上传 & 断点续传
- 分块上传:将大文件分割成多个小块(5MB 一块),然后并发上传,这可以显著提高大文件的上传速度,服务器端需要支持接收并合并这些分块。
- 断点续传:记录已上传的字节数,如果上传中断,下次可以从已上传的位置继续,这需要客户端和服务器端协同工作,通常需要记录上传的会话 ID 和已上传的字节数。
这两个功能都比较复杂,通常用于上传超大文件(如视频),实现它们需要与服务器端 API 紧密配合。
权限处理
如果文件来自设备存储(如相册或下载文件夹),你必须在 AndroidManifest.xml 中声明权限,并在运行时动态请求(针对 Android 6.0+)。
<!-- AndroidManifest.xml --> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <!-- 如果需要写入,例如保存下载的文件 --> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <!-- 对于 Android 11 (API 30) 及以上,推荐使用 MANAGE_EXTERNAL_STORAGE 或更精确的权限 --> <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
在代码中请求权限:
// 在 Activity 或 Fragment 中
if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED) {
// 权限未授予,请求权限
ActivityCompat.requestPermissions(
this,
arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE),
MY_PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE
)
} else {
// 权限已授予,可以执行文件操作
startFileUpload()
}
错误处理
- 网络错误:
IOException,可能是无网络、DNS 解析失败等。 - 服务器错误:检查
response.code(),处理 4xx (客户端错误) 和 5xx (服务器错误)。 - 业务逻辑错误:即使 HTTP 状态码是 200,服务器也可能返回 JSON 格式的错误信息,需要解析后判断。
后台执行 (使用 WorkManager)
如果上传任务需要在用户离开应用或关闭屏幕后继续进行,你应该使用 Android Jetpack 的 WorkManager。
- 优点:
- 可靠:即使应用或系统重启,WorkManager 也能保证任务最终执行。
- 灵活:可以设置约束(如仅在 Wi-Fi 下执行)、延迟、重试策略等。
- 生命周期感知:与应用的生命周期解耦。
你需要将 OkHttp 的上传逻辑封装到一个 Worker 或 CoroutineWorker 中,然后通过 WorkManager 来调度它。
| 方案 | 优点 | 缺点 | 推荐度 |
|---|---|---|---|
| HttpURLConnection | 无依赖 | API 熨琐,功能弱 | ⭐☆☆☆☆ |
| OkHttp | 性能好、API简洁、功能强大、社区活跃 | - | ⭐⭐⭐⭐⭐ (首选) |
| Retrofit | 接口化、类型安全、适合RESTful API | 依赖 OkHttp | ⭐⭐⭐⭐⭐ (复杂API首选) |
对于新的 Android 项目,强烈推荐使用 OkHttp 进行文件上传,如果你的应用有复杂的 API 交互,再结合 Retrofit 使用,理解 multipart/form-data 的原理是成功上传的关键,不要忘记处理权限、进度、错误和后台执行等高级场景。
