凌峰创科服务平台

Android图片上传服务器,如何实现?

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

Android图片上传服务器,如何实现?-图1
(图片来源网络,侵删)
  1. 选择图片:在 Android 端,用户从相册或相机选择图片。
  2. 处理图片(可选但推荐):为了减少上传时间和服务器存储压力,通常会进行压缩、调整尺寸等操作。
  3. 构建请求:将图片数据(通常是文件)打包到 HTTP 请求中。
  4. 发送请求:通过网络将请求发送到服务器。
  5. 服务器处理:服务器接收并保存图片,可能返回一个 URL 或成功信息。
  6. 处理响应: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 开发中最流行的网络请求库,它更简洁、高效,并且支持异步请求。

Android图片上传服务器,如何实现?-图2
(图片来源网络,侵删)

添加依赖

app/build.gradledependencies 中添加:

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 客户端,它将网络请求接口化,使用注解来描述请求,代码可读性和可维护性极高。

Android图片上传服务器,如何实现?-图3
(图片来源网络,侵删)

添加依赖

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 进行异步编程。

图片处理(压缩与缩放)

直接上传原图可能会导致文件过大,上传缓慢且消耗流量,通常在上传前需要进行压缩。

使用 GlidePicasso 进行压缩

这两个库在加载图片时提供了简单的压缩方法。

使用 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,也可以手动使用 BitmapFactoryByteArrayOutputStream 来压缩。

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 线程。

分享:
扫描分享到社交APP
上一篇
下一篇