Android 文件上传
在 Android 开发中,文件上传是网络编程中的常见需求,通常通过 HTTP POST 请求以 multipart/form-data
格式将文件(如图片、视频、文档等)发送到服务器。Android 提供了多种方式实现文件上传,包括原生的 HttpURLConnection
和第三方库如 OkHttp 和 Retrofit。以下是 Android 文件上传的详细学习指南,涵盖实现方法、代码示例、注意事项和实践建议。
一、文件上传概述
- 目标:将本地文件(如存储在设备上的图片、音频或文本文件)通过 HTTP 请求上传到服务器。
- 常见场景:
- 上传用户头像或照片。
- 提交表单数据(如包含文件的表单)。
- 上传大文件(如视频),可能需要断点续传。
- 协议:通常使用 HTTP POST 请求,
Content-Type
为multipart/form-data
。 - 工具:
- HttpURLConnection:原生,支持简单上传。
- OkHttp:功能强大,支持异步和拦截器。
- Retrofit:基于 OkHttp,简化 API 调用,适合 RESTful 上传。
- 关键点:
- 文件以二进制流形式发送。
- 支持同时上传多个文件或文件与表单数据。
- 异步处理,避免阻塞主线程。
- 处理进度反馈(大文件上传)。
二、准备工作
- 权限声明:
在AndroidManifest.xml
中添加网络和存储权限:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<!-- Android 9 及以下需要 -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
- Android 6.0+:动态请求存储权限。
- Android 10+:使用
Scoped Storage
,优先通过MediaStore
或ACTION_GET_CONTENT
访问文件。
- 文件来源:
- 本地文件:如
/sdcard/image.jpg
或通过Intent
从相册选择。 - 示例文件路径:
/storage/emulated/0/Pictures/image.jpg
。
- 服务器要求:
- 服务器需支持
multipart/form-data
上传。 - 确认上传接口的 URL、参数名(如
file
)和认证方式(如 Token)。
- 依赖添加(如使用 OkHttp 或 Retrofit):
在build.gradle
(app 模块)中添加:
// OkHttp
implementation 'com.squareup.okhttp3:okhttp:4.12.0'
// Retrofit
implementation 'com.squareup.retrofit2:retrofit:2.11.0'
三、文件上传方法详解与代码示例
以下是使用 HttpURLConnection
、OkHttp 和 Retrofit 实现文件上传的详细代码示例。
1. 使用 HttpURLConnection
HttpURLConnection
是 Android 原生的 HTTP 客户端,适合简单文件上传。
- 代码示例(上传单个文件):
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
public class FileUploader {
public static String uploadFile(String serverUrl, String filePath) {
String boundary = "----WebKitFormBoundary" + System.currentTimeMillis();
String lineEnd = "\r\n";
String twoHyphens = "--";
HttpURLConnection conn = null;
DataOutputStream dos = null;
FileInputStream fis = null;
StringBuilder response = new StringBuilder();
try {
// 1. 创建连接
URL url = new URL(serverUrl);
conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("POST");
conn.setDoOutput(true);
conn.setDoInput(true);
conn.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary);
conn.setRequestProperty("Authorization", "Bearer your_token_here");
// 2. 写入文件数据
dos = new DataOutputStream(conn.getOutputStream());
// 写入 multipart 头部
dos.writeBytes(twoHyphens + boundary + lineEnd);
dos.writeBytes("Content-Disposition: form-data; name=\"file\"; filename=\"" + new File(filePath).getName() + "\"" + lineEnd);
dos.writeBytes("Content-Type: application/octet-stream" + lineEnd);
dos.writeBytes(lineEnd);
// 写入文件内容
fis = new FileInputStream(filePath);
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = fis.read(buffer)) != -1) {
dos.write(buffer, 0, bytesRead);
}
dos.writeBytes(lineEnd);
dos.writeBytes(twoHyphens + boundary + twoHyphens + lineEnd);
dos.flush();
// 3. 获取响应
int responseCode = conn.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_OK) {
InputStream inputStream = conn.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
String line;
while ((line = reader.readLine()) != null) {
response.append(line);
}
reader.close();
inputStream.close();
} else {
response.append("Error: ").append(responseCode);
}
return response.toString();
} catch (Exception e) {
e.printStackTrace();
return "Exception: " + e.getMessage();
} finally {
try {
if (fis != null) fis.close();
if (dos != null) dos.close();
if (conn != null) conn.disconnect();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
- 使用示例(异步调用):
new Thread(() -> {
String filePath = "/sdcard/Pictures/image.jpg";
String result = FileUploader.uploadFile("https://api.example.com/upload", filePath);
runOnUiThread(() -> {
Log.d("Upload", result);
});
}).start();
- 说明:
- 使用
multipart/form-data
格式,设置boundary
分隔符。 - 手动构造 multipart 头部和文件内容。
- 适合简单上传,但代码较冗长。
2. 使用 OkHttp
OkHttp 是一个功能强大的第三方库,简化文件上传并支持异步处理。
- 代码示例(上传单个文件和表单数据):
import okhttp3.MediaType;
import okhttp3.MultipartBody;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import java.io.File;
public class OkHttpUploader {
public static String uploadFile(String serverUrl, String filePath, String paramName, String paramValue) {
OkHttpClient client = new OkHttpClient();
File file = new File(filePath);
// 构建 multipart 请求体
RequestBody requestBody = new MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart("param", paramValue) // 表单数据
.addFormDataPart("file", file.getName(),
RequestBody.create(file, MediaType.parse("application/octet-stream")))
.build();
// 构建请求
Request request = new Request.Builder()
.url(serverUrl)
.post(requestBody)
.addHeader("Authorization", "Bearer your_token_here")
.build();
try (Response response = client.newCall(request).execute()) {
if (response.isSuccessful()) {
return response.body() != null ? response.body().string() : "Success";
} else {
return "Error: " + response.code();
}
} catch (Exception e) {
e.printStackTrace();
return "Exception: " + e.getMessage();
}
}
}
- 使用示例(异步调用):
new Thread(() -> {
String filePath = "/sdcard/Pictures/image.jpg";
String result = OkHttpUploader.uploadFile(
"https://api.example.com/upload",
filePath,
"username",
"Alice"
);
runOnUiThread(() -> {
Log.d("Upload", result);
});
}).start();
- 异步调用(使用 OkHttp 的回调):
OkHttpClient client = new OkHttpClient();
File file = new File("/sdcard/Pictures/image.jpg");
RequestBody requestBody = new MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart("file", file.getName(),
RequestBody.create(file, MediaType.parse("application/octet-stream")))
.build();
Request request = new Request.Builder()
.url("https://api.example.com/upload")
.post(requestBody)
.build();
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
e.printStackTrace();
runOnUiThread(() -> Log.e("Upload", "Exception: " + e.getMessage()));
}
@Override
public void onResponse(Call call, Response response) throws IOException {
String result = response.body() != null ? response.body().string() : "Success";
runOnUiThread(() -> Log.d("Upload", result));
}
});
- 说明:
- 使用
MultipartBody
构建请求体,简化multipart/form-data
格式。 - 支持异步回调,适合大文件上传。
- 可通过拦截器添加全局头或监控进度(见下文)。
3. 使用 Retrofit
Retrofit 是基于 OkHttp 的高级封装,适合 RESTful API 文件上传。
- 定义接口:
import okhttp3.MultipartBody;
import okhttp3.RequestBody;
import retrofit2.Call;
import retrofit2.http.Multipart;
import retrofit2.http.POST;
import retrofit2.http.Part;
public interface UploadService {
@Multipart
@POST("upload")
Call<ResponseBody> uploadFile(
@Part MultipartBody.Part file,
@Part("username") RequestBody username
);
}
- 配置 Retrofit:
import okhttp3.ResponseBody;
import retrofit2.Retrofit;
public class RetrofitUploader {
public static String uploadFile(String serverUrl, String filePath, String username) {
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(serverUrl)
.build();
UploadService service = retrofit.create(UploadService.class);
File file = new File(filePath);
RequestBody fileBody = RequestBody.create(file, MediaType.parse("application/octet-stream"));
MultipartBody.Part filePart = MultipartBody.Part.createFormData("file", file.getName(), fileBody);
RequestBody usernameBody = RequestBody.create(username, MediaType.parse("text/plain"));
try {
Call<ResponseBody> call = service.uploadFile(filePart, usernameBody);
Response<ResponseBody> response = call.execute();
if (response.isSuccessful()) {
return response.body() != null ? response.body().string() : "Success";
} else {
return "Error: " + response.code();
}
} catch (Exception e) {
e.printStackTrace();
return "Exception: " + e.getMessage();
}
}
}
- 使用示例(Kotlin 协程):
interface UploadService {
@Multipart
@POST("upload")
suspend fun uploadFile(
@Part file: MultipartBody.Part,
@Part("username") username: RequestBody
): Response<ResponseBody>
}
suspend fun uploadFile(serverUrl: String, filePath: String, username: String): String = withContext(Dispatchers.IO) {
val retrofit = Retrofit.Builder()
.baseUrl(serverUrl)
.build()
val service = retrofit.create(UploadService::class.java)
val file = File(filePath)
val fileBody = RequestBody.create(file, MediaType.parse("application/octet-stream"))
val filePart = MultipartBody.Part.createFormData("file", file.getName(), fileBody)
val usernameBody = RequestBody.create(username, MediaType.parse("text/plain"))
try {
val response = service.uploadFile(filePart, usernameBody)
if (response.isSuccessful) {
response.body()?.string() ?: "Success"
} else {
"Error: ${response.code()}"
}
} catch (e: Exception) {
"Exception: ${e锟大屏蔽 e.message}"
}
}
// 调用
lifecycleScope.launch {
val result = uploadFile("https://api.example.com/", "/sdcard/Pictures/image.jpg", "Alice")
Log.d("Upload", result)
}
- 说明:
- 使用
@Multipart
和@Part
注解简化上传。 - 结合协程,异步处理更优雅。
- 支持复杂表单数据和文件上传。
四、上传进度监听(OkHttp 示例)
对于大文件上传,显示上传进度是常见需求。OkHttp 支持通过自定义 RequestBody
实现进度监听。
- 代码示例:
public class ProgressRequestBody extends RequestBody {
private final File file;
private final MediaType mediaType;
private final ProgressListener listener;
public interface ProgressListener {
void onProgress(long bytesWritten, long totalBytes);
}
public ProgressRequestBody(File file, MediaType mediaType, ProgressListener listener) {
this.file = file;
this.mediaType = mediaType;
this.listener = listener;
}
@Override
public MediaType contentType() {
return media);
@Override
public long contentLength() {
return file.length();
}
@Override
public void writeTo(BufferedSink sink) throws IOException {
Source source = Okio.source(file);
long totalBytes = contentLength();
long bytesWritten = 0;
Buffer buffer = new Buffer();
while (source.read(buffer, 8192) != -1) {
bytesWritten += sink.write(buffer);
listener.onProgress(bytesWritten, totalBytes);
}
source.close();
}
}
public void uploadFileWithProgress(String serverUrl, String filePath) {
OkHttpClient client = new OkHttpClient();
File file = new File(filePath);
ProgressRequestBody progressBody = new ProgressRequestBody(
file,
MediaType.parse("application/octet-stream"),
(bytesWritten, totalBytes) -> {
int progress = (int) (100 * bytesWritten / totalBytes);
runOnUiThread(() -> Log.d("Upload", "Progress: " + progress + "%"));
}
);
RequestBody requestBody = new MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart("file", file.getName(), progressBody)
.build();
Request request = new Request.Builder()
.url(serverUrl)
.post(requestBody)
.build();
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
runOnUiThread(() -> Log.e("Upload", "Exception: " + e.getMessage()));
}
@Override
public void onResponse(Call call, Response response) throws IOException {
String result = response.body() != null ? response.body().string() : "Success";
runOnUiThread(() -> Log.d("Upload", result));
}
});
}
- 使用示例:
new Thread(() -> {
uploadFileWithProgress("https://api.example.com/upload", "/sdcard/Pictures/image.jpg");
}).start();
五、注意事项
- 异步处理:
- 文件上传必须在子线程执行,避免
NetworkOnMainThreadException
. - 使用 Thread、AsyncTask(已废弃)、OkHttp 回调或 Kotlin 协程。
- 权限管理:
- Android 6.0+:动态请求存储权限。
- Android 10+:使用
ACTION_GET_CONTENT
或MediaStore
获取文件 URI。 - 示例(获取文件 URI):
java Intent intent = new Intent(Intent.ACTION_GET_CONTENT); intent.setType("image/*"); startActivityForResult(intent, 100);
- 错误处理:
- 处理
IOException
和 HTTP 错误状态码(4xx、5xx)。 - 示例:
java if (responseCode >= 400) { InputStream errorStream = conn.getErrorStream(); String error = new BufferedReader(new InputStreamReader(errorStream)).lines().collect(Collectors.joining("\n")); Log.e("Error", error); }
- 资源释放:
- 关闭
InputStream
、OutputStream
和HttpURLConnection
。 - OkHttp 和 Retrofit 自动管理连接。
- 文件大小与性能:
- 大文件上传需监控进度,考虑分片上传或断点续传。
- 使用 Gzip 压缩(如果服务器支持):
java conn.setRequestProperty("Content-Encoding", "gzip");
- 安全性:
- 使用 HTTPS 确保数据安全。
- 验证服务器证书,避免信任所有证书。
- 添加认证头(如
Authorization: Bearer token
)。
六、学习建议与实践
- 学习路径:
- 了解
multipart/form-data
格式和 HTTP POST 请求。 - 掌握
HttpURLConnection
手动构造 multipart 请求。 - 学习 OkHttp 的
MultipartBody
和异步回调。 - 使用 Retrofit 简化上传,结合 Kotlin 协程。
- 实现上传进度监听(OkHttp)。
- 实践项目:
- 简单项目:开发一个图片上传应用,选择相册图片并上传到服务器。
- 进阶项目:实现多文件上传,附加表单数据(如用户名)。
- 高级项目:支持大文件分片上传和进度显示。
- 调试工具:
- 使用 Postman 测试上传 API,验证请求格式。
- 使用 Charles 或 Fiddler 抓包,分析 multipart 数据。
- 使用 Android Studio 的 Network Profiler 查看请求详情。
- 推荐资源:
- OkHttp 文档:https://square.github.io/okhttp/
- Retrofit 文档:https://square.github.io/retrofit/
- Android 官方文档:https://developer.android.com/training/basics/network-ops
- 实践 API:使用 Mocky.io 或自建服务器测试上传。
七、总结
- 上传方法:
- HttpURLConnection:手动构造 multipart,适合简单上传。
- OkHttp:使用
MultipartBody
,支持异步和进度监听。 - Retrofit:基于 OkHttp,简化 API 调用,适合 RESTful 上传。
- 核心步骤:配置 multipart 请求、写入文件流、处理响应、释放资源。
- 注意事项:异步处理、权限管理、错误处理、资源释放、HTTPS 安全。
- 推荐:新项目优先使用 Retrofit + OkHttp,结合 Kotlin 协程。
- 实践:实现图片上传,验证服务器响应,添加进度显示。
如果需要更详细的代码示例(如多文件上传、分片上传、断点续传)或特定场景的讲解,请告诉我!