目录
- 核心概念:上传是如何工作的?
- 准备工作:服务器端
- 使用
HttpURLConnection(原生 API)- 代码示例
- 优缺点分析
- 使用
OkHttp(现代网络库)- 代码示例
- 优缺点分析
- 使用
Retrofit+OkHttp(强烈推荐)- 什么是 Retrofit?
- 项目配置
- 定义 API 接口
- 创建 Retrofit 实例
- 上传实现
- 优缺点分析
- 进阶与最佳实践
- 上传进度监听
- 取消上传请求
- 处理大文件与 Multipart
- 权限申请 (Android 6.0+)
- 错误处理
- 如何选择?
核心概念:上传是如何工作的?
Android 客户端上传文件,本质上是 HTTP POST 请求的一种,最常见的方式是 multipart/form-data。

multipart/form-data: 这是一种 MIME 类型,允许你在一个 HTTP 请求中发送多种类型的数据(比如文本字段和文件),它会将表单拆分成多个“部分”(part),每个部分都包含自己的内容类型(Content-Type)和内容(文件内容或文本)。- 请求流程:
- Android 客户端构建一个
POST请求。 - 设置请求头
Content-Type为multipart/form-data,并附带一个boundary(边界字符串)用于分隔不同的数据部分。 - 、文件名、文件类型等信息打包成一个或多个
Part。 - 将所有
Part拼接在一起,通过请求体(Request Body)发送给服务器。 - 服务器端解析这个
multipart请求体,提取出文件并保存。
- Android 客户端构建一个
准备工作:服务器端
在开始写 Android 代码之前,你需要一个可以接收文件上传的服务器端,你可以使用任何后端语言(如 Node.js, Python, Java, PHP)来实现。
这里以一个非常简单的 Node.js (Express) 示例为例,它会将上传的文件保存在服务器的 uploads 目录下。
安装依赖:
npm install express multer
服务器端代码 (server.js):

const express = require('express');
const multer = require('multer');
const path = require('path');
const fs = require('fs');
const app = express();
const port = 3000;
// 确保上传目录存在
const uploadDir = 'uploads';
if (!fs.existsSync(uploadDir)) {
fs.mkdirSync(uploadDir);
}
// 配置 multer 存储
const storage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, 'uploads/'); // 文件保存目录
},
filename: function (req, file, cb) {
// 使用原始文件名,也可以自定义
cb(null, file.originalname);
}
});
const upload = multer({ storage: storage });
// 文件上传接口
// 'file' 是前端表单中 file input 的 name 属性
app.post('/upload', upload.single('file'), (req, res) => {
if (!req.file) {
return res.status(400).send('No file uploaded.');
}
console.log('File uploaded:', req.file);
res.status(200).send('File uploaded successfully!');
});
app.listen(port, () => {
console.log(`Server listening at http://localhost:${port}`);
});
运行这个服务器,它将在 http://localhost:3000 上监听,并等待 /upload 请求。
方案一:使用 HttpURLConnection (原生 API)
这是最基础的方式,不需要引入第三方库,但代码相对繁琐。
代码示例
import android.os.Bundle;
import android.util.Log;
import androidx.appcompat.app.AppCompatActivity;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
public class MainActivity extends AppCompatActivity {
private static final String TAG = "FileUpload";
private static final String SERVER_URL = "http://10.0.2.2:3000/upload"; // Android 模拟器访问 localhost
// private static final String SERVER_URL = "http://your_real_server_ip:3000/upload"; // 真机访问
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 模拟一个要上传的文件,通常来自用户选择
File file = new File(getExternalFilesDir(null), "test_image.jpg");
// 注意:这里需要先有一个文件,你可以把一张图片放到手机模拟器的对应目录
// 或者从其他地方复制过来
uploadFile(file);
}
public void uploadFile(File file) {
if (!file.exists()) {
Log.e(TAG, "File does not exist: " + file.getAbsolutePath());
return;
}
String boundary = "*****" + Long.toString(System.currentTimeMillis()) + "*****";
String lineEnd = "\r\n";
String twoHyphens = "--";
try {
URL url = new URL(SERVER_URL);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setDoInput(true);
conn.setDoOutput(true);
conn.setUseCaches(false);
conn.setRequestMethod("POST");
conn.setRequestProperty("Connection", "Keep-Alive");
conn.setRequestProperty("ENCTYPE", "multipart/form-data");
conn.setRequestProperty("Content-Type", "multipart/form-data;boundary=" + boundary);
conn.setRequestProperty("file", file.getName()); // 'file' 必须和服务器端 multer 的 field name 一致
DataOutputStream dos = new DataOutputStream(conn.getOutputStream());
// 添加文件部分
dos.writeBytes(twoHyphens + boundary + lineEnd);
dos.writeBytes("Content-Disposition: form-data; name=\"file\"; filename=\"" + file.getName() + "\"" + lineEnd);
dos.writeBytes("Content-Type: image/jpeg" + lineEnd); // 根据文件类型设置
dos.writeBytes(lineEnd);
// 读取文件并发送
FileInputStream fis = new FileInputStream(file);
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = fis.read(buffer)) != -1) {
dos.write(buffer, 0, bytesRead);
}
fis.close();
dos.writeBytes(lineEnd);
dos.writeBytes(twoHyphens + boundary + twoHyphens + lineEnd);
// 获取服务器响应
int responseCode = conn.getResponseCode();
Log.d(TAG, "HTTP Response code: " + responseCode);
if (responseCode == HttpURLConnection.HTTP_OK) {
Log.d(TAG, "File uploaded successfully");
} else {
Log.e(TAG, "File upload failed. Response code: " + responseCode);
}
dos.flush();
dos.close();
} catch (IOException e) {
e.printStackTrace();
Log.e(TAG, "Error uploading file: " + e.getMessage());
}
}
}
优缺点分析
- 优点:
- 无需任何第三方依赖。
- 适合理解 HTTP 协议底层工作原理。
- 缺点:
- 代码冗长且易错: 手动拼接
multipart请求体非常繁琐,容易出错。 - 功能有限: 难以处理进度监听、取消请求、并发等复杂场景。
- 性能一般: 默认配置下性能不如 OkHttp。
- 代码冗长且易错: 手动拼接
方案二:使用 OkHttp (现代网络库)
OkHttp 是目前 Android 上最流行的网络库之一,它更高效、更易用。
代码示例
import android.os.Bundle;
import android.util.Log;
import androidx.appcompat.app.AppCompatActivity;
import java.io.File;
import java.io.IOException;
import okhttp3.MediaType;
import okhttp3.MultipartBody;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
public class OkHttpUploadActivity extends AppCompatActivity {
private static final String TAG = "OkHttpUpload";
private static final String SERVER_URL = "http://10.0.2.2:3000/upload";
private OkHttpClient client;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
client = new OkHttpClient();
File file = new File(getExternalFilesDir(null), "test_image.jpg");
uploadFileWithOkHttp(file);
}
public void uploadFileWithOkHttp(File file) {
// 1. 创建 MultipartBody.Part
// "file" 是表单字段名,必须和服务器端一致
RequestBody fileBody = RequestBody.create(file, MediaType.parse("image/jpeg"));
MultipartBody.Part filePart = MultipartBody.Part.createFormData("file", file.getName(), fileBody);
// 2. (可选) 添加其他文本字段
RequestBody descriptionBody = RequestBody.create("This is a test image", MediaType.parse("text/plain"));
// 3. 构建请求
Request request = new Request.Builder()
.url(SERVER_URL)
.post(new MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addPart(filePart)
.addFormDataPart("description", "This is a test image") // 添加文本字段
.build())
.build();
// 4. 异步执行请求
client.newCall(request).enqueue(new okhttp3.Callback() {
@Override
public void onFailure(okhttp3.Call call, IOException e) {
Log.e(TAG, "Upload failed: " + e.getMessage());
runOnUiThread(() -> {
// 在主线程更新UI
});
}
@Override
public void onResponse(okhttp3.Call call, Response response) throws IOException {
if (response.isSuccessful()) {
Log.d(TAG, "Upload successful: " + response.body().string());
} else {
Log.e(TAG, "Upload failed with code: " + response.code());
}
response.body().close();
}
});
}
}
优缺点分析
- 优点:
- API 简洁: 使用
MultipartBody.Builder可以轻松构建multipart请求。 - 性能卓越: 支持连接池、GZIP 压缩等,性能很好。
- 支持异步/同步:
enqueue()用于异步,execute()用于同步。 - 功能强大: 内置拦截器、缓存等机制。
- API 简洁: 使用
- 缺点:
- 需要添加依赖。
- 对于复杂的 API 管理和类型安全,不如 Retrofit。
方案三:使用 Retrofit + OkHttp (强烈推荐)
Retrofit 不是一个网络请求库,而是一个 RESTful API 的类型安全 HTTP 客户端,它将网络请求接口化,让你用类似调用方法的方式发起网络请求,它底层默认使用 OkHttp。

项目配置 (build.gradle)
dependencies {
// Retrofit & OkHttp
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0' // 用于解析 JSON 响应
implementation 'com.squareup.okhttp3:logging-interceptor:4.9.3' // 用于打印日志
}
定义 API 接口
创建一个接口,定义你的上传方法。
import okhttp3.MultipartBody;
import okhttp3.RequestBody;
import retrofit2.Call;
import retrofit2.http.Multipart;
import retrofit2.http.POST;
import retrofit2.Part;
public interface FileUploadService {
// @Multipart 表示这是一个 multipart/form-data 请求
// @POST 指定请求路径
// @Part("file") 中的 "file" 是表单字段名,必须和服务器端一致
@Multipart
@POST("/upload")
Call<ResponseBody> uploadFile(@Part MultipartBody.Part file, @Part("description") RequestBody description);
}
创建 Retrofit 实例
import okhttp3.OkHttpClient;
import okhttp3.logging.HttpLoggingInterceptor;
import retrofit2.Retrofit;
import retrofit2.converter.scalars.ScalarsConverterFactory;
import java.util.concurrent.TimeUnit;
public class RetrofitClient {
private static Retrofit retrofit = null;
public static Retrofit getClient(String baseUrl) {
if (retrofit == null) {
// 创建 OkHttp 客户端
HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
logging.setLevel(HttpLoggingInterceptor.Level.BODY); // 打印详细日志
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(logging)
.connectTimeout(30, TimeUnit.SECONDS)
.writeTimeout(30, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.build();
// 创建 Retrofit 实例
retrofit = new Retrofit.Builder()
.baseUrl(baseUrl)
.client(client)
// ScalarsConverterFactory 用于处理纯文本响应
.addConverterFactory(ScalarsConverterFactory.create())
// 如果服务器返回 JSON,使用 GsonConverterFactory
// .addConverterFactory(GsonConverterFactory.create())
.build();
}
return retrofit;
}
}
上传实现
import android.os.Bundle;
import android.util.Log;
import androidx.appcompat.app.AppCompatActivity;
import java.io.File;
import okhttp3.MediaType;
import okhttp3.MultipartBody;
import okhttp3.RequestBody;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
import retrofit2.Retrofit;
public class RetrofitUploadActivity extends AppCompatActivity {
private static final String TAG = "RetrofitUpload";
private static final String SERVER_URL = "http://10.0.2.2:3000/";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
File file = new File(getExternalFilesDir(null), "test_image.jpg");
Retrofit retrofit = RetrofitClient.getClient(SERVER_URL);
FileUploadService service = retrofit.create(FileUploadService.class);
// 1. 准备文件 Part
RequestBody fileBody = RequestBody.create(file, MediaType.parse("image/jpeg"));
MultipartBody.Part filePart = MultipartBody.Part.createFormData("file", file.getName(), fileBody);
// 2. 准备文本字段 Part
RequestBody description = RequestBody.create("This is a description", MediaType.parse("text/plain"));
// 3. 发起请求
Call<String> call = service.uploadFile(filePart, description);
call.enqueue(new Callback<String>() {
@Override
public void onResponse(Call<String> call, Response<String> response) {
if (response.isSuccessful()) {
Log.d(TAG, "Upload successful: " + response.body());
} else {
Log.e(TAG, "Upload failed with code: " + response.code());
}
}
@Override
public void onFailure(Call<String> call, Throwable t) {
Log.e(TAG, "Upload failed: " + t.getMessage());
}
});
}
}
优缺点分析
- 优点:
- 类型安全: 将 API 定义为 Java 接口,编译时检查。
- 代码简洁: 将网络请求与业务逻辑分离,代码结构清晰。
- 易于维护: 修改 API 只需修改接口定义。
- 功能强大: 支持同步/异步、RxJava、Kotlin 协程等。
- 生态系统完善: 拥有大量转换器(Gson, Jackson, Protobuf 等)和拦截器。
- 缺点:
- 引入的依赖较多。
- 有一定的学习成本,需要理解接口注解和 Retrofit 的工作方式。
进阶与最佳实践
上传进度监听
Retrofit 本身不直接支持进度监听,但可以结合 OkHttp 的 Interceptor 来实现。
-
创建进度回调接口
public interface ProgressListener { void onProgress(long bytesWritten, long contentLength); } -
创建 ProgressRequestBody
public class ProgressRequestBody extends RequestBody { // ... 实现细节,参考 OkHttp 官方示例或第三方库如 "com.github.ihsanbal:LoggingInterceptor" // 这个类会包装原始的 RequestBody,并在写入数据时回调 ProgressListener } -
在 Retrofit 接口中使用
@Multipart @POST("/upload") Call<ResponseBody> uploadFile(@Part MultipartBody.Part file, @Part("description") RequestBody description); // 将文件 Part 包装成 ProgressRequestBody
取消上传请求
非常简单,只需要保存 Call 对象,然后在需要时调用 call.cancel()。
private Call<String> uploadCall;
// ...
uploadCall = service.uploadFile(filePart, description);
uploadCall.enqueue(...);
// 在 Activity/Fragment 的 onDestroy 或取消按钮点击事件中
@Override
protected void onDestroy() {
super.onDestroy();
if (uploadCall != null && !uploadCall.isCanceled()) {
uploadCall.cancel();
}
}
处理大文件与 Multipart
Retrofit/OkHttp 的 MultipartBody 内部使用流式处理,不会一次性将整个文件加载到内存中,因此非常适合大文件上传,服务器端也必须配置好以接收大文件(Nginx 的 client_max_body_size)。
权限申请 (Android 6.0+)
如果要从外部存储(如 Environment.getExternalStorageDirectory())读取文件,必须在 AndroidManifest.xml 中声明权限,并在运行时动态请求。
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
错误处理
使用 try-catch 处理本地 IO 异常,在 Callback 的 onFailure 中处理网络异常和服务器返回的错误状态码,对于服务器返回的错误,可以定义一个统一的错误处理机制。
如何选择?
| 方案 | 适用场景 | 推荐指数 |
|---|---|---|
HttpURLConnection |
学习目的、极简应用、对依赖有严格要求的项目 | ★☆☆☆☆ |
OkHttp |
需要直接进行网络操作,但不希望代码过于冗长,适用于需要拦截器、自定义客户端等中高级功能的场景。 | ★★★☆☆ |
Retrofit + OkHttp |
绝大多数 App 的首选,特别是当你的 App 有多个 API 接口时,它能提供最好的代码结构、可维护性和类型安全。 | ★★★★★ |
最终建议:
对于任何新的 Android 项目,直接从 Retrofit + OkHttp 开始,它能为你节省大量的开发时间,并构建出更健壮、更易于维护的网络层,只有在你确定只需要一个简单的、一次性的上传功能,并且不想引入任何额外依赖时,才考虑使用 HttpURLConnection。
