无论使用哪种技术栈,图片上传的基本流程都是相似的:

- 选择图片:在 Android 端,用户从相册或相机选择图片。
- 处理图片(可选但推荐):为了减少上传时间和服务器存储压力,通常会进行压缩、调整尺寸等操作。
- 构建请求:将图片数据(通常是文件)打包到 HTTP 请求中。
- 发送请求:通过网络将请求发送到服务器。
- 服务器处理:服务器接收并保存图片,可能返回一个 URL 或成功信息。
- 处理响应:Android 端接收服务器的响应,并根据结果更新 UI(如提示成功或失败)。
使用 HttpURLConnection (原生方式)
这是最基础的方式,不依赖任何第三方库,适合学习底层原理。
添加网络权限
在 AndroidManifest.xml 中添加:
<uses-permission android:name="android.permission.INTERNET" /> <!-- 如果需要从网络加载图片,还需 --> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
上传代码示例
import android.util.Log;
import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
public class ImageUploader {
private static final String TAG = "ImageUploader";
private static final String UPLOAD_URL = "http://your-server.com/upload"; // 替换为你的服务器地址
public static void uploadImage(File imageFile) {
HttpURLConnection connection = null;
DataOutputStream outputStream = null;
FileInputStream fileInputStream = null;
try {
URL url = new URL(UPLOAD_URL);
connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("POST");
connection.setDoOutput(true); // 允许输出
connection.setDoInput(true); // 允许输入
connection.setUseCaches(false); // 不使用缓存
connection.setRequestProperty("Connection", "Keep-Alive");
connection.setRequestProperty("ENCTYPE", "multipart/form-data");
connection.setRequestProperty("Content-Type", "multipart/form-data;boundary=" + "*****");
connection.setRequestProperty("uploaded_file", imageFile.getName());
// 准备输出流
outputStream = new DataOutputStream(connection.getOutputStream());
// 写入文件内容
fileInputStream = new FileInputStream(imageFile);
byte[] buffer = new byte[4096];
int bytesRead;
while ((bytesRead = fileInputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead);
}
// 发送请求并获取响应码
int responseCode = connection.getResponseCode();
Log.i(TAG, "HTTP Response code: " + responseCode);
// 读取服务器响应
if (responseCode == HttpURLConnection.HTTP_OK) {
BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
String line;
StringBuilder response = new StringBuilder();
while ((line = reader.readLine()) != null) {
response.append(line);
}
reader.close();
Log.i(TAG, "Server Response: " + response.toString());
// 在这里处理成功逻辑,比如更新UI
} else {
Log.e(TAG, "Upload failed with response code: " + responseCode);
// 在这里处理失败逻辑
}
} catch (IOException e) {
e.printStackTrace();
Log.e(TAG, "Upload error: " + e.getMessage());
} finally {
// 关闭流和连接
if (outputStream != null) {
try {
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fileInputStream != null) {
try {
fileInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (connection != null) {
connection.disconnect();
}
}
}
}
缺点:代码冗长,处理复杂(如边界 boundary),回调处理麻烦,容易出错。
使用 OkHttp (推荐)
OkHttp 是目前 Android 开发中最流行的网络请求库,它更简洁、高效,并且支持异步请求。

添加依赖
在 app/build.gradle 的 dependencies 中添加:
implementation 'com.squareup.okhttp3:okhttp:4.9.3' // 使用最新版本
上传代码示例 (使用 MultipartBody)
OkHttp 使用 MultipartBody 来处理文件上传,非常方便。
import android.util.Log;
import java.io.File;
import okhttp3.MediaType;
import okhttp3.MultipartBody;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
public class OkHttpImageUploader {
private static final String TAG = "OkHttpImageUploader";
private static final String UPLOAD_URL = "http://your-server.com/upload";
private final OkHttpClient client = new OkHttpClient();
public void uploadImage(File imageFile) {
// 1. 创建 RequestBody
RequestBody fileBody = RequestBody.create(
MediaType.parse("image/*"), // 根据图片类型设置,如 "image/jpeg"
imageFile
);
// 2. 构建 MultipartBody
MultipartBody multipartBody = new MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart("uploaded_file", imageFile.getName(), fileBody) // "uploaded_file" 是服务器接收的表单字段名
// 可以添加其他文本参数
// .addFormDataPart("description", "This is a test image")
.build();
// 3. 创建 Request
Request request = new Request.Builder()
.url(UPLOAD_URL)
.post(multipartBody)
.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());
// 在这里处理失败逻辑,比如在主线程更新UI
}
@Override
public void onResponse(okhttp3.Call call, Response response) throws IOException {
// 请求成功
if (response.isSuccessful()) {
String responseBody = response.body().string();
Log.i(TAG, "Server Response: " + responseBody);
// 在这里处理成功逻辑,比如在主线程更新UI
} else {
Log.e(TAG, "Upload failed with code: " + response.code());
// 在这里处理失败逻辑
}
}
});
}
}
优点:代码简洁,异步回调处理清晰,性能好,是目前的主流选择。
使用 Retrofit (高级推荐)
Retrofit 是一个基于 OkHttp 的类型安全的 HTTP 客户端,它将网络请求接口化,使用注解来描述请求,代码可读性和可维护性极高。

添加依赖
在 app/build.gradle 中添加:
// OkHttp (Retrofit 依赖它) implementation 'com.squareup.okhttp3:okhttp:4.9.3' // Retrofit implementation 'com.squareup.retrofit2:retrofit:2.9.0' // 用于将 JSON 响应转换为对象 (Gson) implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
定义接口和上传服务
a. 定义 API 接口
import okhttp3.MultipartBody;
import okhttp3.RequestBody;
import retrofit2.Call;
import retrofit2.http.Multipart;
import retrofit2.http.POST;
import retrofit2.http.Part;
public interface UploadApiService {
@Multipart
@POST("upload") // 相对于基础URL的路径
Call<UploadResponse> uploadImage(
@Part("uploaded_file") RequestBody file // "uploaded_file" 是表单字段名
);
}
b. 定义响应模型类 (UploadResponse.java)
假设服务器返回 JSON 格式如 {"code": 200, "message": "success", "url": "..."}
public class UploadResponse {
private int code;
private String message;
private String url;
// Getters and Setters
public int getCode() { return code; }
public void setCode(int code) { this.code = code; }
public String getMessage() { return message; }
public void setMessage(String message) { this.message = message; }
public String getUrl() { return url; }
public void setUrl(String url) { this.url = url; }
}
创建 Retrofit 实例并发起请求
import android.util.Log;
import java.io.File;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
public class RetrofitImageUploader {
private static final String TAG = "RetrofitImageUploader";
private static final String BASE_URL = "http://your-server.com/"; // 结尾要有斜杠
public void uploadImage(File imageFile) {
// 1. 创建 Retrofit 实例
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build();
// 2. 创建 API Service 实例
UploadApiService apiService = retrofit.create(UploadApiService.class);
// 3. 创建 RequestBody
RequestBody fileBody = RequestBody.create(
okhttp3.MediaType.parse("image/*"),
imageFile
);
// 4. 调用接口方法,得到 Call 对象
Call<UploadResponse> call = apiService.uploadImage(fileBody);
// 5. 异步执行请求
call.enqueue(new retrofit2.Callback<UploadResponse>() {
@Override
public void onResponse(Call<UploadResponse> call, retrofit2.Response<UploadResponse> response) {
if (response.isSuccessful()) {
UploadResponse uploadResponse = response.body();
if (uploadResponse != null) {
Log.i(TAG, "Upload Success! URL: " + uploadResponse.getUrl());
// 处理成功逻辑
}
} else {
Log.e(TAG, "Upload failed with code: " + response.code());
// 处理失败逻辑
}
}
@Override
public void onFailure(Call<UploadResponse> call, Throwable t) {
Log.e(TAG, "Upload failed: " + t.getMessage());
// 处理失败逻辑
}
});
}
}
优点:
- 类型安全:接口定义清晰,编译时检查。
- 代码简洁:通过注解定义请求,代码量少。
- 易于维护:接口和实现分离,易于扩展。
- 强大的集成:可以轻松集成 RxJava 或 Coroutines 进行异步编程。
图片处理(压缩与缩放)
直接上传原图可能会导致文件过大,上传缓慢且消耗流量,通常在上传前需要进行压缩。
使用 Glide 或 Picasso 进行压缩
这两个库在加载图片时提供了简单的压缩方法。
使用 Glide 的示例:
import com.bumptech.glide.Glide;
import com.bumptech.glide.request.RequestOptions;
// ...
// 在 Activity/Fragment 中
Glide.with(this)
.asBitmap() // 指定为 Bitmap
.load(imageUri) // 图片的 Uri
.apply(new RequestOptions().override(1024, 1024)) // 设置目标尺寸
.into(new CustomTarget<Bitmap>() {
@Override
public void onResourceReady(@NonNull Bitmap resource, @Nullable Transition<? super Bitmap> transition) {
// resource 就是压缩后的 Bitmap
// 将 Bitmap 转换为 File 或直接上传
File compressedFile = bitmapToFile(resource);
// 然后调用之前定义的上传方法
OkHttpImageUploader.uploadImage(compressedFile);
}
@Override
public void onLoadFailed(@Nullable Drawable errorDrawable) {
super.onLoadFailed(errorDrawable);
// 处理加载失败
}
});
手动压缩
如果你不想用 Glide,也可以手动使用 BitmapFactory 和 ByteArrayOutputStream 来压缩。
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.util.Log;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
public class ImageUtils {
public static File compressImage(File imageFile, int maxWidth, int maxHeight, int quality) {
try {
// 1. 解码图片
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(imageFile.getAbsolutePath(), options);
// 2. 计算采样率
options.inSampleSize = calculateInSampleSize(options, maxWidth, maxHeight);
// 3. 使用采样率解码图片
options.inJustDecodeBounds = false;
Bitmap bitmap = BitmapFactory.decodeFile(imageFile.getAbsolutePath(), options);
// 4. 压缩并保存到临时文件
File compressedFile = new File(imageFile.getParent(), "compressed_" + imageFile.getName());
ByteArrayOutputStream stream = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.JPEG, quality, stream); // quality: 0-100
byte[] bytes = stream.toByteArray();
FileOutputStream fos = new FileOutputStream(compressedFile);
fos.write(bytes);
fos.close();
return compressedFile;
} catch (IOException e) {
Log.e("ImageUtils", "Error compressing image", e);
return null;
}
}
private static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
final int halfHeight = height / 2;
final int halfWidth = width / 2;
while ((halfHeight / inSampleSize) >= reqHeight
&& (halfWidth / inSampleSize) >= reqWidth) {
inSampleSize *= 2;
}
}
return inSampleSize;
}
}
服务器端 (Node.js/Express 示例)
为了让示例完整,这里提供一个简单的 Node.js 服务器来接收文件。
安装依赖
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, Date.now() + '-' + file.originalname);
}
});
const upload = multer({ storage: storage });
// 上传接口
app.post('/upload', upload.single('uploaded_file'), (req, res) => {
if (!req.file) {
return res.status(400).json({ code: 400, message: 'No file uploaded.' });
}
// 文件上传成功,返回文件信息
const fileUrl = `/uploads/${req.file.filename}`;
res.json({
code: 200,
message: 'File uploaded successfully!',
url: fileUrl
});
});
// 静态文件服务,用于访问上传的图片
app.use('/uploads', express.static(path.join(__dirname, 'uploads')));
app.listen(port, () => {
console.log(`Server is running at http://localhost:${port}`);
});
运行 node server.js,你的服务器就在 http://localhost:3000 上运行了,可以接收你 Android App 发来的文件。
总结与建议
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| HttpURLConnection | 原生,无依赖 | 代码繁琐,易出错,回调处理麻烦 | 学习网络基础,或项目有极简依赖要求 |
| OkHttp | 简洁高效,异步回调,社区成熟 | 相比 Retrofit,接口定义稍显原始 | 大多数网络请求场景,特别是文件上传 |
| Retrofit | 类型安全,代码优雅,易于维护和扩展 | 需要学习接口定义和注解 | 中大型项目,API 较多,追求代码质量和可维护性 |
我的建议是:
- 对于新项目,直接使用 Retrofit + OkHttp 的组合,这是目前 Android 开发的最佳实践,能让你写出高质量、易于维护的网络代码。
- 如果只是做一个简单的工具或 Demo,不想引入 Retrofit,OkHttp 是一个非常好的选择。
- 除非有特殊原因,否则不要再用
HttpURLConnection手动实现文件上传了。
别忘了处理网络相关的异常,并在后台线程中执行网络操作,避免阻塞 UI 线程。
