【问题标题】:Android Volley multipart parameter nameAndroid Volley 多部分参数名称
【发布时间】:2016-03-23 16:25:03
【问题描述】:

通过混合使用相关答案中的代码,我设法使用 multipart 进行了调用。但是,我无法使用正确的参数名称发送它。

请求必须是怎样的(取自 iOS 应用):

我的请求看起来如何:

代码:

MultipartRequest 应该是一个基本的多部分请求。

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Matrix;
import android.graphics.RectF;
import android.util.Log;

import com.android.volley.Request;
import com.android.volley.Response;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.File;

import com.android.volley.AuthFailureError;
import com.android.volley.toolbox.StringRequest;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

public class MultipartRequest extends StringRequest {
private final int maxImageWidth = 200;
private final int maxImageHeight = 200;

static String mimeType;
private final File file;
DataOutputStream dos = null;
String lineEnd = "\r\n";
static String boundary = "apiclient-" + System.currentTimeMillis();
String twoHyphens = "--";
int bytesRead, bytesAvailable, bufferSize;
byte[] buffer;
int maxBufferSize = 124 * 124;

public static MultipartRequest newInstance(final PlayEarnAPIImpl.OnPlayEarnAPIResponse listener, File file, Response.Listener<String> responseListener, Response.ErrorListener errorListener, String serviceURL) {
    mimeType = "multipart/form-data;boundary=" + boundary;
    return new MultipartRequest(Request.Method.PUT, serviceURL, responseListener, errorListener, file, serviceURL);
}

public MultipartRequest(int method, String url, Response.Listener<String> listener, Response.ErrorListener errorListener, File file, String serviceURL) {
    super(method, url, listener, errorListener);
    this.file = file;
}

private byte[] decodeFile(File file) {
    BitmapFactory.Options options = new BitmapFactory.Options();
    options.inPreferredConfig = Bitmap.Config.ARGB_8888;
    options.inSampleSize = calculateInSampleSize(options, 400, 400);
    Bitmap bitmap = BitmapFactory.decodeFile(file.getAbsolutePath(), options);
    ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
    bitmap.compress(Bitmap.CompressFormat.PNG, 100, byteArrayOutputStream);
    return byteArrayOutputStream.toByteArray();
}

public static int calculateInSampleSize(
        BitmapFactory.Options options, int reqWidth, int reqHeight) {
    // Raw height and width of image
    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;

        // Calculate the largest inSampleSize value that is a power of 2 and keeps both
        // height and width larger than the requested height and width.
        while ((halfHeight / inSampleSize) > reqHeight
                && (halfWidth / inSampleSize) > reqWidth) {
            inSampleSize *= 2;
        }
    }

    return inSampleSize;
}

//solution 2
private byte[] shrinkImage(File file) {
    try {
        int inWidth = 0;
        int inHeight = 0;

        InputStream in = new FileInputStream(file.getAbsolutePath());

        // decode image size (decode metadata only, not the whole image)
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeStream(in, null, options);
        in.close();
        in = null;

        // save width and height
        inWidth = options.outWidth;
        inHeight = options.outHeight;

        // decode full image pre-resized
        in = new FileInputStream(file.getAbsolutePath());
        options = new BitmapFactory.Options();
        // calc rought re-size (this is no exact resize)
        options.inSampleSize = Math.max(inWidth / maxImageWidth, inHeight / maxImageHeight);
        // decode full image
        Bitmap roughBitmap = BitmapFactory.decodeStream(in, null, options);

        // calc exact destination size
        Matrix m = new Matrix();
        RectF inRect = new RectF(0, 0, roughBitmap.getWidth(), roughBitmap.getHeight());
        RectF outRect = new RectF(0, 0, maxImageWidth, maxImageHeight);
        m.setRectToRect(inRect, outRect, Matrix.ScaleToFit.CENTER);
        float[] values = new float[9];
        m.getValues(values);

        // resize bitmap
        Bitmap resizedBitmap = Bitmap.createScaledBitmap(roughBitmap, (int) (roughBitmap.getWidth() * values[0]), (int) (roughBitmap.getHeight() * values[4]), true);

        // save image

        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        resizedBitmap.compress(Bitmap.CompressFormat.PNG, 100, byteArrayOutputStream);
        return byteArrayOutputStream.toByteArray();
    } catch (IOException e) {
        Log.e("Image", e.getMessage(), e);
    }
    return null;
}

@Override
public String getBodyContentType() {
    return mimeType;
}

@Override
public byte[] getBody() throws AuthFailureError {
    ByteArrayOutputStream bos = new ByteArrayOutputStream();
    dos = new DataOutputStream(bos);
    byte[] bitmapData = null;
    try {
        //dos.writeBytes(twoHyphens + boundary + lineEnd);
        dos.writeBytes(lineEnd);
        dos.writeBytes(" ------------------12345");
        dos.writeBytes(lineEnd);
        dos.writeBytes("Content-Disposition: form-data; name=\"picture\"; filename=\"file.png\"");
        dos.writeBytes(lineEnd);
        dos.writeBytes("Content-Type: image/png");
        //dos.writeBytes("Content-Disposition: form-data; name=\"picture\";filename=\""
        //        + file.getName() + "\"" + lineEnd);
        dos.writeBytes(lineEnd);
        bitmapData = shrinkImage(this.file);
        ByteArrayInputStream fileInputStream = new ByteArrayInputStream(bitmapData);
        bytesAvailable = fileInputStream.available();

        bufferSize = Math.min(bytesAvailable, maxBufferSize);
        Log.d("MultipartRequest", "bufferSize:" + bufferSize);
        buffer = new byte[bufferSize];

        // read file and write it into form...
        bytesRead = fileInputStream.read(buffer, 0, bufferSize);

        while (bytesRead > 0) {
            dos.write(buffer, 0, bufferSize);
            bytesAvailable = fileInputStream.available();
            bufferSize = Math.min(bytesAvailable, maxBufferSize);
            bytesRead = fileInputStream.read(buffer, 0, bufferSize);
        }

        // send multipart form data necesssary after file data...
        dos.writeBytes(lineEnd);
        dos.writeBytes(twoHyphens + boundary + twoHyphens + lineEnd);

        return bos.toByteArray();

    } catch (IOException e) {
        e.printStackTrace();
    }
    return bitmapData;
}

这是我正在使用的请求:

import com.android.volley.AuthFailureError;
import com.android.volley.Request;
import com.android.volley.Response;
import com.android.volley.VolleyError;
import com.google.gson.Gson;

import java.io.File;
import java.util.Map;

public class ChangeProfileImageRequest extends MultipartRequest {
private static final String UPDATE_PATH = "users/update";
String serviceURL = APIImpl.API_URL + UPDATE_PATH;

public static ChangeProfileImageRequest newInstance(final APIImpl.OnAPIResponse listener, File file) {
    String serviceURL = APIImpl.API_URL + UPDATE_PATH;

    return new ChangeProfileImageRequest(Request.Method.PUT, serviceURL, new ResponseListener(listener), new ErrorListener(listener), file, serviceURL);
}

public ChangeProfileImageRequest(int method, String url, Response.Listener<String> listener, Response.ErrorListener errorListener, File file, String serviceURL) {
    super(method, url, listener, errorListener, file, serviceURL);
}

@Override
public Map<String, String> getHeaders() throws AuthFailureError {
    Map<String, String> headers = new APIImpl().getTokenHeader();
    //headers.put("Accept", "*/*");
    headers.put("Content-Type", "multipart/form-data;boundary=----------------12345");
    AppController.getInstance().addSessionCookie(headers);
    return headers;
}

所以我想这与“无法解码多部分正文”的屏幕截图有关,但我不知道具体问题是什么。同样来自服务器端的图片参数也没有被接收到。

【问题讨论】:

标签: java android android-volley multipartform-data


【解决方案1】:

我设法将 com.squareup.okhttp 添加到 gradle,并稍微更改了this class

基础多部分请求:

import com.android.volley.AuthFailureError;
import com.android.volley.DefaultRetryPolicy;
import com.android.volley.NetworkResponse;
import com.android.volley.Request;
import com.android.volley.Response;
import com.android.volley.RetryPolicy;
import com.android.volley.VolleyLog;
import com.android.volley.toolbox.HttpHeaderParser;

import com.squareup.okhttp.Headers;
import com.squareup.okhttp.MediaType;
import com.squareup.okhttp.MultipartBuilder;
import com.squareup.okhttp.RequestBody;

import android.util.Log;

import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.HashMap;
import java.util.Map;

import okio.Buffer;

/**
 * Multipart request for Google's Volley using Square's OkHttp.
 *
 * @author Hussain Al-Derry
 * @version 1.0
 */
public class VolleyMultipartRequest extends Request<String> {

    /* Used for debugging */
    private static final String TAG = VolleyMultipartRequest.class.getSimpleName();

    /* MediaTypes */
    public static final MediaType MEDIA_TYPE_JPEG = MediaType.parse("image/jpeg");
    public static final MediaType MEDIA_TYPE_PNG = MediaType.parse("image/png");
    public static final MediaType MEDIA_TYPE_TEXT_PLAIN = MediaType.parse("text/plain");

    private MultipartBuilder mBuilder = new MultipartBuilder();
    private final Response.Listener<String> mListener;
    private RequestBody mRequestBody;

    public VolleyMultipartRequest(int method, String url,
                                  Response.ErrorListener errorListener,
                                  Response.Listener<String> listener) {
        super(method, url, errorListener);
        mListener = listener;
        mBuilder.type(MultipartBuilder.FORM);
    }

    /**
     * Adds a collection of string values to the request.
     *
     * @param mParams {@link HashMap} collection of values to be added to the request.
     */
    public void addStringParams(HashMap<String, String> mParams) {
        for (Map.Entry<String, String> entry : mParams.entrySet()) {
            mBuilder.addPart(
                    Headers.of("Content-Disposition", "form-data; name=\"" + entry.getKey() + "\""),
                    RequestBody.create(MEDIA_TYPE_TEXT_PLAIN, entry.getValue()));
        }
    }

    /**
     * Adds a single value to the request.
     *
     * @param key   String - the field name.
     * @param value String - the field's value.
     */
    public void addStringParam(String key, String value) {
        mBuilder.addPart(
                Headers.of("Content-Disposition", "form-data; name=\"" + key + "\""),
                RequestBody.create(MEDIA_TYPE_TEXT_PLAIN, value));
    }

    /**
     * Adds a binary attachment to the request.
     *
     * @param content_type {@link MediaType} - the type of the attachment.
     * @param fileName     String - the attachment field name.
     * @param value        {@link File} - the file to be attached.
     */
    public void addAttachment(MediaType content_type, String paramName, String fileName, File value) {
        mBuilder.addFormDataPart(paramName, fileName, RequestBody.create(content_type, value));
    }

    /**
     * Builds the request.
     * Must be called before adding the request to the Volley request queue.
     */
    public void buildRequest() {
        mRequestBody = mBuilder.build();
    }

    @Override
    public String getBodyContentType() {
        return mRequestBody.contentType().toString();
    }

    @Override
    public byte[] getBody() throws AuthFailureError {
        Buffer buffer = new Buffer();
        try {
            mRequestBody.writeTo(buffer);
        } catch (IOException e) {
            Log.e(TAG, e.toString());
            VolleyLog.e("IOException writing to ByteArrayOutputStream");
        }
        return buffer.readByteArray();
    }

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

    @Override
    public Request<?> setRetryPolicy(RetryPolicy retryPolicy) {
        return super.setRetryPolicy(retryPolicy);
    }

    @Override
    protected void deliverResponse(String response) {
        if (mListener != null) {
            mListener.onResponse(response);
        }
    }
}

并根据我的特定要求对其进行子类化:

public class ChangeProfileImageRequest extends VolleyMultipartRequest {
private static final String UPDATE_PATH = "users/update";

public static ChangeProfileImageRequest newInstance(final APIImpl.OnAPIResponse listener, File file) {
    String serviceURL = APIImpl.API_URL + UPDATE_PATH;

    //return new ChangeProfileImageRequest(Request.Method.PUT, serviceURL, new ResponseListener(listener), new ErrorListener(listener), file, serviceURL);
    ChangeProfileImageRequest request = new ChangeProfileImageRequest(Method.PUT, serviceURL, new ErrorListener(listener), new ResponseListener(listener));
    request.addAttachment(MEDIA_TYPE_PNG, "picture", "file.png", file);

    return request;
}

public ChangeProfileImageRequest(int method, String url, Response.ErrorListener errorListener, Response.Listener<String> listener) {
    super(method, url, errorListener, listener);
}

@Override
public Map<String, String> getHeaders() throws AuthFailureError {
    Map<String, String> headers = new APIImpl().getTokenHeader();
    headers.put("Accept", "*/*");
    AppController.getInstance().addSessionCookie(headers);
    return headers;
}

//ErrorListener and ResponseListener classes

并将其添加到 Volley:

public void updateProfileImage(APIImpl.OnAPIResponse listener, File image) {
    ChangeProfileImageRequest request = ChangeProfileImageRequest.newInstance(listener, image);
    request.buildRequest();
    // Access the RequestQueue through your singleton class.
    request.setRetryPolicy(new DefaultRetryPolicy(
            0,
            DefaultRetryPolicy.DEFAULT_MAX_RETRIES,
            DefaultRetryPolicy.DEFAULT_BACKOFF_MULT));
    VolleySingleton.getInstance().addToRequestQueue(request);
}

不过,我会接受只使用 Volley 的答案。我想仅仅因为一个请求不起作用就使用 Volley 和 OkHttp 并不理想。

【讨论】:

  • 请问APIImpl和AppController是什么?
  • @GurupadMamadapur 我认为这与问题无关,但APIImpl 只是我必须做的外观的实现,因为我从虚拟调用开始,因为服务器不是完毕。 AppController 是继承自 MultiDexApplication 的类
【解决方案2】:

使用 Apache 的 MultipartEntityBuilder,然后您可以只使用 HttpEntity 来提供您的 getBodyContentType() 和 getBody() 即:

...

    private HttpEntity mEntity;

    @Override
    public String getBodyContentType() {
        return mEntity.getContentType().getValue();
    }

    @Override
    public byte[] getBody() throws AuthFailureError {
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        try {
            mEntity.writeTo(outputStream);
        } catch (IOException e) {
            VolleyLog.e("IOException @ " + getClass().getSimpleName());
        }
        return outputStream.toByteArray();
    }

    public void setEntity(HttpEntity entity) {
        this.mEntity = entity;
    }
}

【讨论】:

  • 嗨@Submersed,感谢您的回复。据我所知,org.apache.http.* 已被弃用,不应使用。
  • @David Hackro 它仍然支持多部分请求的相同格式,因此对他已经在使用的东西进行 -1 并没有多大意义。首先查看弃用的实际原因。无论哪种方式,在我看来,它仍然比当前的解决方案更可取。
  • ASOP 正在远离 Http 库以支持 UrlConnection,这并不意味着 apache 本身已经弃用了整个包——文档是否将整个类标记为弃用?没有。
猜你喜欢
  • 2013-08-19
  • 2019-08-01
  • 2016-08-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-05-05
  • 1970-01-01
相关资源
最近更新 更多