You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何用Android Volley库向Django 2.0服务器上传图片?

嘿,你已经把Django后端的核心模块(模型、序列化器、视图、URL)都搭好了,接下来用Volley实现图片上传其实很清晰,我来给你拆解具体步骤:

用Volley实现Android到Django的图片上传方案

Android端实现(Volley自定义Multipart请求)

Volley默认的Request不支持multipart/form-data(文件上传的标准格式),所以我们需要先自定义一个MultipartRequest类来同时处理表单参数和图片文件:

import com.android.volley.AuthFailureError;
import com.android.volley.NetworkResponse;
import com.android.volley.Request;
import com.android.volley.Response;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.HttpHeaderParser;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.Map;

public class MultipartRequest extends Request<String> {
    private final Response.Listener<String> mListener;
    private final Map<String, String> mParams;
    private final Map<String, byte[]> mFileParams;
    private final String mBoundary = "apiclient-" + System.currentTimeMillis();
    private final String mTwoHyphens = "--";
    private final String mLineEnd = "\r\n";

    public MultipartRequest(String url, Map<String, String> params, Map<String, byte[]> fileParams,
                           Response.Listener<String> listener, Response.ErrorListener errorListener) {
        super(Method.POST, url, errorListener);
        this.mListener = listener;
        this.mParams = params;
        this.mFileParams = fileParams;
    }

    @Override
    public String getBodyContentType() {
        return "multipart/form-data; boundary=" + mBoundary;
    }

    @Override
    public byte[] getBody() throws AuthFailureError {
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        DataOutputStream dos = new DataOutputStream(bos);

        try {
            // 添加表单参数(发送方、接收方手机号)
            if (mParams != null && !mParams.isEmpty()) {
                for (Map.Entry<String, String> entry : mParams.entrySet()) {
                    buildTextPart(dos, entry.getKey(), entry.getValue());
                }
            }

            // 添加图片文件
            if (mFileParams != null && !mFileParams.isEmpty()) {
                for (Map.Entry<String, byte[]> entry : mFileParams.entrySet()) {
                    buildFilePart(dos, entry.getKey(), entry.getValue());
                }
            }

            // 结束请求标记
            dos.writeBytes(mTwoHyphens + mBoundary + mTwoHyphens + mLineEnd);
            return bos.toByteArray();
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
    }

    private void buildTextPart(DataOutputStream dos, String paramName, String paramValue) throws IOException {
        dos.writeBytes(mTwoHyphens + mBoundary + mLineEnd);
        dos.writeBytes("Content-Disposition: form-data; name=\"" + paramName + "\"" + mLineEnd);
        dos.writeBytes(mLineEnd);
        dos.writeBytes(paramValue + mLineEnd);
    }

    private void buildFilePart(DataOutputStream dos, String paramName, byte[] fileBytes) throws IOException {
        dos.writeBytes(mTwoHyphens + mBoundary + mLineEnd);
        dos.writeBytes("Content-Disposition: form-data; name=\"" + paramName + "\"; filename=\"uploaded_image.jpg\"" + mLineEnd);
        dos.writeBytes("Content-Type: image/jpeg" + mLineEnd); // 根据实际图片类型调整(比如png)
        dos.writeBytes(mLineEnd);

        ByteArrayInputStream bis = new ByteArrayInputStream(fileBytes);
        byte[] buffer = new byte[1024];
        int bytesRead;
        while ((bytesRead = bis.read(buffer)) != -1) {
            dos.write(buffer, 0, bytesRead);
        }
        dos.writeBytes(mLineEnd);
    }

    @Override
    protected Response<String> parseNetworkResponse(NetworkResponse response) {
        String responseString = "";
        if (response != null) {
            try {
                responseString = new String(response.data, HttpHeaderParser.parseCharset(response.headers));
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
        }
        return Response.success(responseString, HttpHeaderParser.parseCacheHeaders(response));
    }

    @Override
    protected void deliverResponse(String response) {
        mListener.onResponse(response);
    }
}

调用自定义请求上传图片

假设你已经从相册/相机获取到了图片的Uri,接下来把图片转成字节流,搭配表单参数发送请求:

// 1. 准备表单参数(发送方、接收方手机号)
Map<String, String> formParams = new HashMap<>();
formParams.put("sender_phone", "138xxxx1234"); // 替换为实际发送方手机号
formParams.put("receiver_phone", "139xxxx5678"); // 替换为实际接收方手机号

// 2. 将图片Uri转为字节数组(工具方法)
Uri imageUri = ...; // 你的图片Uri
byte[] imageBytes = null;
try {
    imageBytes = getImageBytesFromUri(imageUri);
} catch (IOException e) {
    e.printStackTrace();
    Toast.makeText(this, "读取图片失败", Toast.LENGTH_SHORT).show();
    return;
}

Map<String, byte[]> fileParams = new HashMap<>();
fileParams.put("image", imageBytes); // 这个key要和Django视图接收的字段名一致

// 3. 发送Volley请求
String uploadApiUrl = "http://你的服务器IP:端口/api/upload-image/"; // 替换为你的实际API地址
MultipartRequest uploadRequest = new MultipartRequest(
        uploadApiUrl,
        formParams,
        fileParams,
        response -> {
            // 上传成功回调
            Log.d("UploadSuccess", "服务器返回:" + response);
            Toast.makeText(this, "图片上传完成", Toast.LENGTH_SHORT).show();
        },
        error -> {
            // 上传失败回调
            Log.e("UploadError", "错误信息:" + error.getMessage());
            Toast.makeText(this, "上传失败,请检查网络", Toast.LENGTH_SHORT).show();
        }
);

// 添加到Volley请求队列
Volley.newRequestQueue(this).add(uploadRequest);

图片Uri转字节数组的工具方法

private byte[] getImageBytesFromUri(Uri uri) throws IOException {
    InputStream inputStream = getContentResolver().openInputStream(uri);
    ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
    byte[] buffer = new byte[1024];
    int bytesRead;
    while ((bytesRead = inputStream.read(buffer)) != -1) {
        outputStream.write(buffer, 0, bytesRead);
    }
    inputStream.close();
    return outputStream.toByteArray();
}

Django端配合调整

确保你的视图能正确处理multipart/form-data请求,接收图片并保存到指定目录,同时创建Exchange记录:

from django.http import JsonResponse
from .models import Exchange
from .serializers import ExchangeSerializer
import os
from django.conf import settings

def upload_image(request):
    if request.method == 'POST':
        # 获取表单参数和图片文件
        sender_phone = request.POST.get('sender_phone')
        receiver_phone = request.POST.get('receiver_phone')
        image_file = request.FILES.get('image')

        # 参数校验
        if not all([sender_phone, receiver_phone, image_file]):
            return JsonResponse({'error': '缺少必要参数'}, status=400)

        # 保存图片到服务器指定目录(比如media/images下)
        save_dir = os.path.join(settings.MEDIA_ROOT, 'images')
        os.makedirs(save_dir, exist_ok=True)
        relative_path = os.path.join('images', image_file.name)
        full_save_path = os.path.join(save_dir, image_file.name)

        with open(full_save_path, 'wb+') as destination:
            for chunk in image_file.chunks():
                destination.write(chunk)

        # 创建Exchange记录
        exchange = Exchange.objects.create(
            sender_phone=sender_phone,
            receiver_phone=receiver_phone,
            img=relative_path  # 存储相对路径,方便后续访问
        )
        serializer = ExchangeSerializer(exchange)
        return JsonResponse(serializer.data, status=201)
    return JsonResponse({'error': '仅支持POST请求'}, status=405)

另外要确保Django配置了媒体文件路径:

# settings.py
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
MEDIA_URL = '/media/'

并在主URL中添加媒体文件访问路由(开发阶段使用):

# urls.py
from django.conf import settings
from django.conf.urls.static import static

urlpatterns = [
    # 你的其他路由
    path('api/upload-image/', views.upload_image, name='upload-image'),
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

关键注意事项

  • 权限配置:在AndroidManifest.xml中添加必要权限,Android 6.0+还需动态申请存储权限:
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.READ_MEDIA_IMAGES" /> <!-- Android 13+ -->
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <!-- 旧版本Android -->
    
  • 图片压缩:大尺寸图片会导致上传缓慢甚至失败,建议上传前对图片进行压缩(缩小分辨率或降低质量)。
  • 服务器地址:Android模拟器访问本地Django服务器要用10.0.2.2代替localhost,真机需确保和服务器在同一局域网或服务器有公网IP。

内容的提问来源于stack exchange,提问作者mohamadrezach

火山引擎 最新活动