- Android 端:
Retrofit+OkHttp(网络请求库) +MultipartBody(用于构建文件上传请求体)。 - 服务器端: Java Spring Boot (因为它是 Java 开发中最流行的后端框架,示例代码清晰易懂),如果你使用其他语言(如 Node.js, Python, PHP),核心逻辑是相通的。
目录
- 核心概念: 了解文件上传的原理。
- 服务器端准备: 用 Spring Boot 搭建一个简单的接收文件接口。
- Android 端实现: 详细步骤,从添加依赖到编写上传代码。
- 进阶与最佳实践: 错误处理、进度显示、多文件上传等。
- 常见问题与解决方案。
核心概念:HTTP 文件上传
在 Web 开发中,文件上传通常使用 POST 请求,并且请求头的 Content-Type 设置为 multipart/form-data。

POST: 使用POST方法可以将数据作为请求体发送,适合传输较大的文件。multipart/form-data: 这种内容类型允许你在一个请求中发送多个部分的数据(文件和文本字段),每个部分之间由一个特殊的边界字符串分隔。MultipartBody: 在 Android 中,我们使用OkHttp的MultipartBody来构建这种复杂的请求体,它会自动处理边界字符串和数据的编码。
服务器端准备 (Spring Boot 示例)
我们需要一个服务器来接收文件,这里是一个简单的 Spring Boot 控制器示例。
1. 添加 Maven 依赖
在你的 pom.xml 文件中确保有 spring-boot-starter-web 依赖。
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
2. 创建文件上传控制器
创建一个 Java 类来处理文件上传请求。
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
@RestController
@RequestMapping("/api/upload")
public class FileUploadController {
// 定义服务器上存储文件的目录
private static final String UPLOADED_FOLDER = "/path/to/your/server/uploads/"; // 请务必修改为你的实际路径
@PostMapping("/single")
public String uploadFile(@RequestParam("file") MultipartFile file) {
if (file.isEmpty()) {
return "Please select a file to upload";
}
try {
// 获取原始文件名
String originalFilename = file.getOriginalFilename();
// 创建目标文件路径
Path destinationPath = Paths.get(UPLOADED_FOLDER + originalFilename);
// 将文件保存到服务器
Files.copy(file.getInputStream(), destinationPath);
return "File uploaded successfully: " + originalFilename;
} catch (IOException e) {
e.printStackTrace();
return "File upload failed: " + e.getMessage();
}
}
// 多文件上传示例
@PostMapping("/multi")
public String uploadMultipleFiles(@RequestParam("files") MultipartFile[] files) {
StringBuilder result = new StringBuilder();
for (MultipartFile file : files) {
if (file.isEmpty()) {
result.append("Skipped empty file. ");
continue;
}
try {
String originalFilename = file.getOriginalFilename();
Path destinationPath = Paths.get(UPLOADED_FOLDER + originalFilename);
Files.copy(file.getInputStream(), destinationPath);
result.append("Successfully uploaded: ").append(originalFilename).append(". ");
} catch (IOException e) {
result.append("Failed to upload: ").append(file.getOriginalFilename()).append(". ");
e.printStackTrace();
}
}
return result.toString();
}
}
重要提示:

- 将
/path/to/your/server/uploads/替换为你服务器上一个有写入权限的真实目录。 - Spring Boot 默认对上传文件的大小有限制(通常是 1MB),你需要在
application.properties或application.yml中增加限制:# application.properties spring.servlet.multipart.max-file-size=10MB spring.servlet.multipart.max-request-size=10MB
Android 端实现 (使用 Retrofit)
1. 添加依赖
在你的 app/build.gradle 文件的 dependencies 代码块中添加以下依赖:
// Retrofit for networking implementation 'com.squareup.retrofit2:retrofit:2.9.0' implementation 'com.squareup.retrofit2:converter-gson:2.9.0' // 如果你还需要解析JSON // OkHttp for logging and other features implementation 'com.squareup.okhttp3:logging-interceptor:4.9.3' implementation 'com.squareup.okhttp3:okhttp:4.9.3'
2. 添加网络权限
在 app/src/main/AndroidManifest.xml 中添加 INTERNET 权限:
<manifest ...>
<uses-permission android:name="android.permission.INTERNET" />
<application ...>
...
</application>
</manifest>
3. 创建 Retrofit 接口
定义一个接口来描述我们的 API。
import okhttp3.MultipartBody;
import okhttp3.RequestBody;
import retrofit2.Call;
import retrofit2.http.Multipart;
import retrofit2.http.POST;
import retrofit2.http.Part;
public interface FileUploadApi {
// 单文件上传
// @Multipart 表示这是一个 multipart/form-data 请求
// @Part("file") 指定了表单中字段的名称,必须与服务器端 @RequestParam("file") 的 "file" 对应
@Multipart
@POST("/api/upload/single")
Call<String> uploadFile(@Part MultipartBody.Part file);
// 多文件上传
@Multipart
@POST("/api/upload/multi")
Call<String> uploadMultipleFiles(@Part List<MultipartBody.Part> files);
// 带其他参数的文件上传
@Multipart
@POST("/api/upload/with-info")
Call<String> uploadFileWithInfo(
@Part("file") MultipartBody.Part file,
@Part("description") RequestBody description
);
}
4. 创建 Retrofit 实例并进行上传
这是上传逻辑的核心,通常在 Activity 或 ViewModel 中调用。

import android.util.Log;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import okhttp3.MediaType;
import okhttp3.MultipartBody;
import okhttp3.RequestBody;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
public class MainActivity extends AppCompatActivity {
private static final String TAG = "FileUpload";
private static final String BASE_URL = "http://your-server-ip:8080/"; // 请替换为你的服务器地址
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button uploadButton = findViewById(R.id.btn_upload);
uploadButton.setOnClickListener(v -> uploadSingleFile());
}
private void uploadSingleFile() {
// 1. 选择要上传的文件 (这里以选择手机上的一个图片为例)
// 实际项目中,你可能通过 Intent.ACTION_GET_CONTENT 让用户选择文件
File file = new File(getExternalFilesDir(null), "test_image.jpg");
if (!file.exists()) {
Toast.makeText(this, "File not found!", Toast.LENGTH_SHORT).show();
return;
}
// 2. 创建 RequestBody
// "image/*" 是文件的 MIME 类型,可以根据文件类型修改
RequestBody requestFile = RequestBody.create(MediaType.parse("image/*"), file);
// 3. 创建 MultipartBody.Part
MultipartBody.Part body = MultipartBody.Part.createFormData("file", file.getName(), requestFile);
// 4. 创建 Retrofit 实例
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build();
// 5. 创建 API 接口的实例
FileUploadApi uploadApi = retrofit.create(FileUploadApi.class);
// 6. 发起异步上传请求
Call<String> call = uploadApi.uploadFile(body);
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());
Toast.makeText(MainActivity.this, "Upload Success: " + response.body(), Toast.LENGTH_LONG).show();
} else {
// 服务器返回了错误状态码
Log.e(TAG, "Upload failed with error code: " + response.code());
Toast.makeText(MainActivity.this, "Upload Failed: Server Error", Toast.LENGTH_SHORT).show();
}
}
@Override
public void onFailure(Call<String> call, Throwable t) {
// 请求失败,如网络问题、解析错误等
Log.e(TAG, "Upload failed: ", t);
Toast.makeText(MainActivity.this, "Upload Failed: " + t.getMessage(), Toast.LENGTH_SHORT).show();
}
});
}
}
进阶与最佳实践
1. 显示上传进度
Retrofit 默认不提供进度回调,但我们可以通过 OkHttp 的 Interceptor 来实现。
-
创建进度监听接口
public interface ProgressListener { void onProgress(long bytesWritten, long contentLength); } -
创建 ProgressRequestBody
import okhttp3.MediaType; import okhttp3.RequestBody; import okio.Buffer; import okio.BufferedSink; import okio.ForwardingSink; import okio.Okio; import okio.Sink; import java.io.IOException; public class ProgressRequestBody extends RequestBody { private final RequestBody requestBody; private final ProgressListener progressListener; public ProgressRequestBody(RequestBody requestBody, ProgressListener progressListener) { this.requestBody = requestBody; this.progressListener = progressListener; } @Override public MediaType contentType() { return requestBody.contentType(); } @Override public void writeTo(BufferedSink sink) throws IOException { BufferedSink bufferedSink = Okio.buffer(new ForwardingSink(sink) { private long bytesWritten = 0L; private long contentLength = 0L; @Override public void write(Buffer source, long byteCount) throws IOException { super.write(source, byteCount); if (contentLength == 0) { contentLength = contentLength(); } bytesWritten += byteCount; progressListener.onProgress(bytesWritten, contentLength); } }); requestBody.writeTo(bufferedSink); bufferedSink.flush(); } } -
在上传时使用
// 在 MainActivity 中 private void uploadFileWithProgress() { File file = new File(getExternalFilesDir(null), "test_image.jpg"); RequestBody requestFile = RequestBody.create(MediaType.parse("image/*"), file); // 使用 ProgressRequestBody 包装 ProgressRequestBody progressRequestBody = new ProgressRequestBody(requestFile, new ProgressListener() { @Override public void onProgress(long bytesWritten, long contentLength) { // 在这里更新 UI 显示进度 // 注意:不能在子线程更新 UI,需要 runOnUiThread int progress = (int) ((bytesWritten * 100) / contentLength); runOnUiThread(() -> { // progressBar.setProgress(progress); Log.d(TAG, "Upload Progress: " + progress + "%"); }); } }); MultipartBody.Part body = MultipartBody.Part.createFormData("file", file.getName(), progressRequestBody); // ... 后续的 Retrofit 调用和 enqueue 与之前相同 }
2. 上传多个文件
只需将多个 MultipartBody.Part 放入一个 List 中,然后调用接口中的多文件上传方法即可。
private void uploadMultipleFiles() {
List<File> filesToUpload = new ArrayList<>();
filesToUpload.add(new File(getExternalFilesDir(null), "file1.jpg"));
filesToUpload.add(new File(getExternalFilesDir(null), "file2.png"));
List<MultipartBody.Part> parts = new ArrayList<>();
for (File file : filesToUpload) {
RequestBody requestFile = RequestBody.create(MediaType.parse("image/*"), file);
MultipartBody.Part part = MultipartBody.Part.createFormData("files", file.getName(), requestFile);
parts.add(part);
}
Retrofit retrofit = new Retrofit.Builder().baseUrl(BASE_URL).build();
FileUploadApi api = retrofit.create(FileUploadApi.class);
Call<String> call = api.uploadMultipleFiles(parts);
call.enqueue(...); // 与之前相同的 enqueue 回调
}
3. 添加其他表单字段
有时上传文件时还需要附带其他信息(如用户ID、描述等),这可以通过 @Part 注解添加 RequestBody 来实现。
// 在接口中定义
@Multipart
@POST("/api/upload/with-info")
Call<String> uploadFileWithInfo(
@Part("file") MultipartBody.Part file,
@Part("userId") RequestBody userId,
@Part("description") RequestBody description
);
// 在调用时构建 RequestBody
String userId = "12345";
String description = "This is a test image";
RequestBody userIdBody = RequestBody.create(MediaType.parse("text/plain"), userId);
RequestBody descBody = RequestBody.create(MediaType.parse("text/plain"), description);
// ... 创建 file body ...
MultipartBody.Part filePart = MultipartBody.Part.createFormData("file", file.getName(), requestFile);
Call<String> call = api.uploadFileWithInfo(filePart, userIdBody, descBody);
常见问题与解决方案
-
java.net.UnknownHostException:- 原因: 无法连接到服务器。
- 解决:
- 检查
BASE_URL是否正确,包括 IP 地址和端口号。 - 确保手机和服务器在同一个局域网内(如果是在本地开发测试)。
- 确认服务器正在运行,并且防火墙没有阻止你的端口。
- 检查
-
java.net.SocketTimeoutException:-
原因: 读取服务器响应超时。
-
解决:
-
文件太大或网络太慢,导致默认的超时时间不够。
-
可以在
OkHttpClient中设置超时时间:OkHttpClient okHttpClient = new OkHttpClient.Builder() .connectTimeout(30, TimeUnit.SECONDS) // 连接超时 .writeTimeout(2, TimeUnit.MINUTES) // 写入超时 .readTimeout(2, TimeUnit.MINUTES) // 读取超时 .build(); Retrofit retrofit = new Retrofit.Builder() .baseUrl(BASE_URL) .client(okHttpClient) .build();
-
-
-
android.os.NetworkOnMainThreadException:- 原因: 在主线程(UI线程)中执行了网络操作。
- 解决: 网络请求(如
call.enqueue())必须在子线程中执行。enqueue方法本身是异步的,但如果你在主线程调用它,Retrofit 会抛出这个异常,通常我们直接在主线程的onClick中调用enqueue,因为它内部会处理线程切换,所以这个异常不常出现,但如果同步调用call.execute(),则必须在子线程。
-
服务器返回 413
Request Entity Too Large:- 原因: 上传的文件大小超过了服务器配置的限制。
- 解决: 在 Spring Boot 的
application.properties中增加spring.servlet.multipart.max-file-size和spring.servlet.multipart.max-request-size的值。
-
Content-Type不匹配:- 原因: Android 端设置的
MediaType与服务器端期望的不一致。 - 解决: 确保你为文件设置的
MediaType是正确的(如image/jpeg用于.jpg文件,application/pdf用于.pdf文件),如果不确定,可以使用application/octet-stream作为通用类型。
- 原因: Android 端设置的
希望这份详细的指南能帮助你顺利实现 Android 文件上传功能!
