目录
- 核心概念:HTTP 请求
- Android 网络权限
- 上传数据的主要方式
- 上传键值对 (Form Data)
- 上传文件 (Multipart)
- 上传 JSON 数据
- 主流网络库对比与选择
- 原生
HttpURLConnection(不推荐,但作为基础了解) - OkHttp (强烈推荐,现代 Android 开发标准)
- Retrofit (基于 OkHttp,更高级的封装)
- 原生
- 完整代码示例 (使用 OkHttp)
- 示例 1: 上传键值对和文件
- 示例 2: 上传纯 JSON 数据
- 高级主题与最佳实践
- 处理大文件与进度显示
- 在后台执行上传 (使用
WorkManager) - 错误处理与重试机制
- 安全性 (HTTPS)
- 调试网络请求
核心概念:HTTP 请求
数据上传本质上是向服务器发送一个 HTTP 请求,最常见的两种方法是:

POST: 用于提交数据,通常用于创建新资源,上传文件、表单数据等都使用POST。PUT: 用于更新已存在的资源。
我们这里主要关注 POST 请求。
Android 网络权限
在任何网络操作之前,你必须在 AndroidManifest.xml 文件中声明网络权限。
<!-- 允许应用完全访问网络 -->
<uses-permission android:name="android.permission.INTERNET" />
<!-- 如果你的应用运行在 Android 9 (API 28) 或更高版本,默认情况下,应用只能使用 HTTPS。
如果你的服务器还在使用 HTTP,你需要添加这个标签来允许 HTTP。
强烈建议在生产环境中使用 HTTPS。 -->
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
上传数据的主要方式
上传键值对
这是最简单的形式,类似于 HTML 表单,上传用户名和密码。
- Content-Type:
application/x-www-form-urlencoded - 数据格式:
key1=value1&key2=value2
上传文件
当你需要上传图片、视频、文档等文件时,需要使用 multipart/form-data。

- Content-Type:
multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW - 数据格式: 一个包含多个部分(part)的请求体,每个部分可以是文本字段(如用户名)或二进制文件(如图片)。
boundary是一个独特的字符串,用于分隔各个部分。
上传 JSON 数据
当你的后端 API 期望接收一个 JSON 对象时,使用这种方式。
- Content-Type:
application/json - 数据格式:
{ "key1": "value1", "key2": "value2" }
主流网络库对比与选择
| 特性 | HttpURLConnection |
OkHttp | Retrofit |
|---|---|---|---|
| 易用性 | 较低,需要手动处理流、线程等 | 高,提供了简洁的 API | 非常高,用接口和注解定义 API |
| 功能 | 基础功能 | 非常强大:连接池、拦截器、GZIP、WebSocket 等 | 非常强大:基于 OkHttp,增加了类型安全、异步/同步支持、RxJava/Coroutines 集成 |
| 性能 | 一般 | 优秀,自动管理连接池,复用 TCP 连接,性能高 | 优秀,底层是 OkHttp |
| 推荐场景 | 简单 demo 或对依赖有严格限制的项目 | 绝大多数项目,特别是需要处理复杂请求、拦截器或 WebSocket 的项目 | 构建大型、结构化 API 客户端的首选,代码最清晰、最易维护 |
- 新手入门或简单任务:直接使用 OkHttp。
- 专业开发、构建大型 App:使用 Retrofit,它会让你的网络层代码无比优雅和健壮。
完整代码示例 (使用 OkHttp)
OkHttp 是 Google 推荐的网络库,下面我们通过两个例子来展示如何使用它。
示例 1: 上传键值对和文件 (Multipart)
假设我们要上传一个用户名、一张头像图片。
添加 OkHttp 依赖
在 app/build.gradle 文件的 dependencies 代码块中添加:
dependencies {
implementation("com.squareup.okhttp3:okhttp:4.12.0") // 请使用最新版本
}
准备上传的文件
// 从设备相册或相机获取图片的 URI val imageUri: Uri = ... // 将 URI 转换为 File 对象 val file = File(imageUri.path)
编写上传逻辑
import okhttp3.*
import java.io.File
import java.io.IOException
class UploadService {
private val client = OkHttpClient()
// 定义一个回调接口,用于通知上传结果
interface UploadCallback {
fun onSuccess(response: String)
fun onFailure(e: IOException)
}
fun uploadFileWithParams(username: String, imageFile: File, callback: UploadCallback) {
// 1. 创建 MultipartBody.Builder
val requestBody = MultipartBody.Builder()
.setType(MultipartBody.FORM) // 设置表单类型
.addFormDataPart("username", username) // 添加普通文本字段
.addFormDataPart(
"avatar", // 服务器期望的文件字段名
imageFile.name, // 文件名
RequestBody.create("image/jpeg".toMediaTypeOrNull(), imageFile) // 添加文件
)
.build()
// 2. 创建 Request
val request = Request.Builder()
.url("https://your-api-endpoint.com/upload") // 替换成你的服务器地址
.post(requestBody)
.build()
// 3. 异步执行请求
client.newCall(request).enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
// 请求失败,在主线程中回调
callback.onFailure(e)
}
override fun onResponse(call: Call, response: Response) {
// 请求成功
response.use { // 使用 use 确保响应体被关闭
if (response.isSuccessful) {
val responseBody = response.body?.string()
// 在主线程中回调
callback.onSuccess(responseBody ?: "Success with no body")
} else {
// 服务器返回了错误码 (如 404, 500)
val errorBody = response.body?.string()
callback.onFailure(IOException("Server returned ${response.code}: $errorBody"))
}
}
}
})
}
}
在 Activity 或 ViewModel 中调用
// 在你的 Activity 或其他地方
val uploadService = UploadService()
val username = "JohnDoe"
val imageFile = File(...) // 获取到的文件
uploadService.uploadFileWithParams(username, imageFile, object : UploadService.UploadCallback {
override fun onSuccess(response: String) {
// 切换到主线程更新 UI
runOnUiThread {
Toast.makeText(this@YourActivity, "上传成功: $response", Toast.LENGTH_SHORT).show()
// 可以在这里解析服务器返回的 JSON
}
}
override fun onFailure(e: IOException) {
// 切换到主线程更新 UI
runOnUiThread {
Toast.makeText(this@YourActivity, "上传失败: ${e.message}", Toast.LENGTH_SHORT).show()
}
}
})
示例 2: 上传纯 JSON 数据
假设我们要向服务器提交一个用户信息对象。
import okhttp3.*
import java.io.IOException
class JsonUploadService {
private val client = OkHttpClient()
private val mediaType = "application/json; charset=utf-8".toMediaType()
interface JsonUploadCallback {
fun onSuccess(response: String)
fun onFailure(e: IOException)
}
fun uploadUser(user: User, callback: JsonUploadCallback) {
// 1. 将 User 对象转换为 JSON 字符串
val jsonBody = user.toJson() // 假设你有一个扩展函数或使用 Gson/Moshi
// 2. 创建 RequestBody
val requestBody = RequestBody.create(mediaType, jsonBody)
// 3. 创建 Request
val request = Request.Builder()
.url("https://your-api-endpoint.com/users")
.post(requestBody)
.build()
// 4. 异步执行
client.newCall(request).enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
callback.onFailure(e)
}
override fun onResponse(call: Call, response: Response) {
response.use {
if (response.isSuccessful) {
callback.onSuccess(response.body!!.string())
} else {
callback.onFailure(IOException("Error: ${response.code}"))
}
}
}
})
}
}
// 假设的 User 数据类
data class User(val name: String, val email: String)
// 假设的 JSON 序列化扩展函数
fun User.toJson(): String {
// 实际项目中请使用 Gson, Moshi 或 Kotlinx Serialization
return """{"name": "$name", "email": "$email"}"""
}
高级主题与最佳实践
处理大文件与进度显示
OkHttp 本身不直接支持上传进度监听,但可以通过自定义 RequestBody 来实现。
- 创建一个
ProgressRequestBody,继承RequestBody。 - 在其
writeTo()方法中,每写入一部分数据,就计算一次进度,并通过回调接口通知 UI 层。 - 在上传时,使用这个自定义的
ProgressRequestBody。
这是一个常见的模式,网上有大量成熟的实现方案可供参考。
在后台执行上传 (使用 WorkManager)
如果用户离开你的应用或关闭了手机屏幕,上传任务可能会被系统杀死,为了保证上传任务的可靠性,应该使用 WorkManager。
- 优点:
- 可靠性:系统会确保任务最终被执行,即使应用被关闭或重启。
- 约束:可以设置约束,仅在 Wi-Fi 下执行”或“仅在充电时执行”。
- 周期性任务:也支持周期性任务。
你需要将你的 OkHttp/Retrofit 代码封装到一个 Worker 或 CoroutineWorker 中,然后由 WorkManager 来调度执行。
错误处理与重试机制
网络是不可靠的,可能会出现各种错误(无网络、超时、服务器 500 等)。
- OkHttp/Retrofit:它们会抛出
IOException或特定的 HTTP 错误,你需要用try-catch或onFailure回调来处理。 - WorkManager:内置了重试机制,Worker 返回
Result.retry(),WorkManager 会在一段时间后自动重试,并且重试间隔会越来越长(指数退避)。
安全性 (HTTPS)
在现代应用中,必须使用 HTTPS。
- 为什么? 它能加密数据传输,防止中间人攻击,保护用户隐私。
- Android 9 (API 28+) 的限制:默认情况下,禁止应用使用不安全的 HTTP 协议,如果你的服务器是 HTTP,你需要:
- 强烈建议:将服务器升级为 HTTPS。
- 临时方案:在
AndroidManifest.xml的<application>标签中添加android:usesCleartextTraffic="true",但这会降低安全性,仅用于开发测试。
调试网络请求
调试网络请求非常重要,尤其是在与后端联调时。
- OkHttp 自带日志:OkHttp 有一个非常棒的内置日志系统,可以打印出请求和响应的详细信息。
// 在 Application 类或初始化 OkHttp 的地方添加
val logging = HttpLoggingInterceptor()
logging.setLevel(HttpLoggingInterceptor.Level.BODY) // 打印请求体和响应体
val client = OkHttpClient.Builder()
.addInterceptor(logging)
.build()
- 其他工具:
- Charles Proxy 或 Fiddler:可以在你的电脑上设置代理,捕获所有通过该设备的 HTTP/HTTPS 流量(需要配置 SSL Pinning)。
- Chrome 开发者工具:在手机上开启 USB 调试后,可以在 Chrome 的
chrome://inspect页面查看网络请求。
希望这份详尽的指南能帮助你顺利地在 Android 上实现数据上传!
