使用Volley上传Image作为Multipart请求?

时间:2016-11-08 05:47:08

标签: java android android-volley

我正在尝试使用Volley发布我的图像,但我无法将我的图像上传到服务器。我总是得到 com.volley.ServerError 。当我使用Fiddler捕获图像上传请求时,它会给我一个500状态/错误代码。

以下是我从图库中选择图片的代码:

@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);

    if (data == null) {
        Toast.makeText(getActivity(), "No Image Selected", Toast.LENGTH_SHORT).show();
    } else if (requestCode == Constants.CHOICE_AVATAR_FROM_GALLERY && resultCode == getActivity().RESULT_OK) {
        Bitmap avatar = getBitmapFromData(data);


        RestClient restClient = new RestClient(getActivity());
  restClient.stringRequest(Constants.PROFILE_IMAGE_UPDATE_REQUEST_ID, Constants.PROFILE_UPDATE_IMAGE_URL, this, imageData);



        byte[] inputData=null;
        try {
            Bundle extra = data.getExtras();
            Uri _uri= Uri.parse(extra.get("src_uri").toString());
            InputStream iStream = getActivity().getContentResolver().openInputStream(_uri);
            inputData = getBytes(iStream);
            size = (inputData.length)/(1024*1024);

            Log.d("ImageArray", "uri:   " + size);
        } catch (Exception e) {
            e.printStackTrace();
        }

        if(size < 3){

            imageView.setImageBitmap(avatar);
            Map<String, String> dataMap = new HashMap<>();
            dataMap.put("Email", Utility.sessionEmail(getActivity()));

            restClient.multiPartImageUploadRequest(Constants.PROFILE_IMAGE_UPDATE_REQUEST_ID,
                    url, this, dataMap, inputData);

        }else {
            imageView.setImageResource(R.drawable.ic_camera);
            Toast.makeText(getActivity(), "Image size shuld not exceed 3 MB", Toast.LENGTH_SHORT).show();
        }

    }


}

public String getPath(Uri uri) {
    Cursor cursor = getActivity().getContentResolver().query(uri, null, null, null, null);
    cursor.moveToFirst();
    String document_id = cursor.getString(0);
    document_id = document_id.substring(document_id.lastIndexOf(":") + 1);
    cursor.close();



    cursor = getActivity().getContentResolver().query(
            android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
            null, MediaStore.Images.Media._ID + " = ? ", new String[]{document_id}, null);
    cursor.moveToFirst();
    String path = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DATA));
    cursor.close();

    return path;
}

public void selectImage(View view) {

    Intent intent = new Intent(Intent.ACTION_GET_CONTENT);

    intent.setType("image/*");
    intent.setAction(Intent.ACTION_GET_CONTENT);
    startActivityForResult(getCropIntent(intent), Constants.CHOICE_AVATAR_FROM_GALLERY);
}


private Intent getCropIntent(Intent intent) {

    intent.putExtra("crop", "true");
    intent.putExtra("aspectX", 1);
    intent.putExtra("aspectY", 1);
    intent.putExtra("outputX", 320);
    intent.putExtra("outputY", 320);
    intent.putExtra("return-data", true);

    return intent;
}

public Bitmap getBitmapFromData(Intent data) {
    Bitmap photo = null;
    Uri photoUri = data.getData();


    if (photoUri != null) {
        photo = BitmapFactory.decodeFile(photoUri.getPath());
        path = getPath(photoUri);
        uri = photoUri;
        Log.d("URI", "" + photoUri);
    }
    if (photo == null) {
        Bundle extra = data.getExtras();
        if (extra != null) {
            photo = (Bitmap) extra.get("data");
            ByteArrayOutputStream stream = new ByteArrayOutputStream();
            photo.compress(Bitmap.CompressFormat.JPEG, 90, stream);

        }
    }
    return photo;
}

这是处理上传 POST 请求

的方法
 public void multiPartImageUploadRequest(int id, String url, ResponseHandler handler, Map<String, String> data, byte[] image){

    int request_id = id;
    final ResponseHandler responseHandler = handler;
    final Map<String, String> params = data;
    final byte[] imageData = image;

    pDialog = new ProgressDialog(context);
    pDialog.setMessage("Uploading Image...");
    pDialog.show();

    VolleyMultipartRequest request = new VolleyMultipartRequest(Request.Method.POST, url,
            new Response.Listener<NetworkResponse>() {
        @Override
        public void onResponse(NetworkResponse response) {
            String resultResponse = new String(response.data);
            responseHandler.success(resultResponse, Constants.PROFILE_IMAGE_UPDATE_REQUEST_ID);
            pDialog.dismiss();

        }
    }, new Response.ErrorListener() {
        @Override
        public void onErrorResponse(VolleyError error) {
            responseHandler.failure(error, Constants.PROFILE_IMAGE_UPDATE_REQUEST_ID);
            pDialog.dismiss();
            error.printStackTrace();
        }
    }) {
        @Override
        protected Map<String, String> getParams() {
            return params;
        }

        @Override
        protected Map<String, DataPart> getByteData() {
            Map<String, DataPart> ImageParams = new HashMap<>();
            // file name could found file base or direct access from real path
            // for now just get bitmap data from ImageView
            ImageParams.put("", new DataPart("file_avatar.jpg", imageData, "image/jpeg"));

            return ImageParams;
        }
    };
    request.setRetryPolicy(new DefaultRetryPolicy(30*1000, 1, 1.0f));

    RestController.getInstance().addToRequestQueue(request);



}

VolleyMultipartRequest.java

public class VolleyMultipartRequest extends Request<NetworkResponse> {


    private final String twoHyphens = "--";
    private final String lineEnd = "\r\n";
    private final String boundary = "apiclient-" + System.currentTimeMillis();

    private Response.Listener<NetworkResponse> mListener;
    private Response.ErrorListener mErrorListener;
    private Map<String, String> mHeaders;


    /**
     * Default constructor with predefined header and post method.
     *
     * @param url           request destination
     * @param headers       predefined custom header
     * @param listener      on success achieved 200 code from request
     * @param errorListener on error http or library timeout
     */
    public VolleyMultipartRequest(String url, Map<String, String> headers,
                                  Response.Listener<NetworkResponse> listener,
                                  Response.ErrorListener errorListener) {
        super(Method.POST, url, errorListener);
        this.mListener = listener;
        this.mErrorListener = errorListener;
        this.mHeaders = headers;
    }

    /**
     * Constructor with option method and default header configuration.
     *
     * @param method        method for now accept POST and GET only
     * @param url           request destination
     * @param listener      on success event handler
     * @param errorListener on error event handler
     */
    public VolleyMultipartRequest(int method, String url,
                                  Response.Listener<NetworkResponse> listener,
                                  Response.ErrorListener errorListener) {
        super(method, url, errorListener);
        this.mListener = listener;
        this.mErrorListener = errorListener;
    }

    @Override
    public Map<String, String> getHeaders() throws AuthFailureError {
        return (mHeaders != null) ? mHeaders : super.getHeaders();
    }

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

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

        try {
            // populate text payload
            Map<String, String> params = getParams();
            if (params != null && params.size() > 0) {
                textParse(dos, params, getParamsEncoding());
            }

            // populate data byte payload
            Map<String, DataPart> data = getByteData();
            if (data != null && data.size() > 0) {
                dataParse(dos, data);
            }

            // close multipart form data after text and file data
            dos.writeBytes(twoHyphens + boundary + twoHyphens + lineEnd);

            return bos.toByteArray();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * Custom method handle data payload.
     *
     * @return Map data part label with data byte
     * @throws AuthFailureError
     */
    protected Map<String, DataPart> getByteData() throws AuthFailureError {
        return null;
    }

    @Override
    protected Response<NetworkResponse> parseNetworkResponse(NetworkResponse response) {
        try {
            return Response.success(
                    response,
                    HttpHeaderParser.parseCacheHeaders(response));
        } catch (Exception e) {
            return Response.error(new ParseError(e));
        }
    }

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

    @Override
    public void deliverError(VolleyError error) {
        mErrorListener.onErrorResponse(error);
    }

    /**
     * Parse string map into data output stream by key and value.
     *
     * @param dataOutputStream data output stream handle string parsing
     * @param params           string inputs collection
     * @param encoding         encode the inputs, default UTF-8
     * @throws IOException
     */
    private void textParse(DataOutputStream dataOutputStream, Map<String, String> params, String encoding) throws IOException {
        try {
            for (Map.Entry<String, String> entry : params.entrySet()) {
                buildTextPart(dataOutputStream, entry.getKey(), entry.getValue());
            }
        } catch (UnsupportedEncodingException uee) {
            throw new RuntimeException("Encoding not supported: " + encoding, uee);
        }
    }

    /**
     * Parse data into data output stream.
     *
     * @param dataOutputStream data output stream handle file attachment
     * @param data             loop through data
     * @throws IOException
     */
    private void dataParse(DataOutputStream dataOutputStream, Map<String, DataPart> data) throws IOException {
        for (Map.Entry<String, DataPart> entry : data.entrySet()) {
            buildDataPart(dataOutputStream, entry.getValue(), entry.getKey());
        }
    }

    /**
     * Write string data into header and data output stream.
     *
     * @param dataOutputStream data output stream handle string parsing
     * @param parameterName    name of input
     * @param parameterValue   value of input
     * @throws IOException
     */
    private void buildTextPart(DataOutputStream dataOutputStream, String parameterName, String parameterValue) throws IOException {
        dataOutputStream.writeBytes(twoHyphens + boundary + lineEnd);
        dataOutputStream.writeBytes("Content-Disposition: form-data; name=\"" + parameterName + "\"" + lineEnd);
        //dataOutputStream.writeBytes("Content-Type: text/plain; charset=UTF-8" + lineEnd);
        dataOutputStream.writeBytes(lineEnd);
        dataOutputStream.writeBytes(parameterValue + lineEnd);
    }

    /**
     * Write data file into header and data output stream.
     *
     * @param dataOutputStream data output stream handle data parsing
     * @param dataFile         data byte as DataPart from collection
     * @param inputName        name of data input
     * @throws IOException
     */
    private void buildDataPart(DataOutputStream dataOutputStream, DataPart dataFile, String inputName) throws IOException {
        dataOutputStream.writeBytes(twoHyphens + boundary + lineEnd);
        dataOutputStream.writeBytes("Content-Disposition: form-data; name=\"" +
                inputName + "\"; filename=\"" + dataFile.getFileName() + "\"" + lineEnd);
        if (dataFile.getType() != null && !dataFile.getType().trim().isEmpty()) {
            dataOutputStream.writeBytes("Content-Type: " + dataFile.getType() + lineEnd);
            Log.d("Name", inputName + "      " + dataFile.getFileName());
        }
        dataOutputStream.writeBytes(lineEnd);

        ByteArrayInputStream fileInputStream = new ByteArrayInputStream(dataFile.getContent());
        int bytesAvailable = fileInputStream.available();

        int maxBufferSize = 1024 * 1024;
        int bufferSize = Math.min(bytesAvailable, maxBufferSize);
        byte[] buffer = new byte[bufferSize];

        int bytesRead = fileInputStream.read(buffer, 0, bufferSize);

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

        dataOutputStream.writeBytes(lineEnd);
    }

    /**
     * Simple data container use for passing byte file
     */
    public class DataPart {
        private String fileName;
        private byte[] content;
        private String type;

        /**
         * Default data part
         */
        public DataPart() {
        }

        /**
         * Constructor with data.
         *
         * @param name label of data
         * @param data byte data
         */
        public DataPart(String name, byte[] data) {
            fileName = name;
            content = data;
        }

        /**
         * Constructor with mime data type.
         *
         * @param name     label of data
         * @param data     byte data
         * @param mimeType mime data like "image/jpeg"
         */
        public DataPart(String name, byte[] data, String mimeType) {
            fileName = name;
            content = data;
            type = mimeType;
        }

        /**
         * Getter file name.
         *
         * @return file name
         */
        public String getFileName() {
            return fileName;
        }

        /**
         * Setter file name.
         *
         * @param fileName string file name
         */
        public void setFileName(String fileName) {
            this.fileName = fileName;
        }

        /**
         * Getter content.
         *
         * @return byte file data
         */
        public byte[] getContent() {
            return content;
        }

        /**
         * Setter content.
         *
         * @param content byte file data
         */
        public void setContent(byte[] content) {
            this.content = content;
        }

        /**
         * Getter mime type.
         *
         * @return mime type
         */
        public String getType() {
            return type;
        }

        /**
         * Setter mime type.
         *
         * @param type mime type
         */
        public void setType(String type) {
            this.type = type;
        }
    }
}

1 个答案:

答案 0 :(得分:1)

因此,了解究竟是什么错误需要一些调试,我可以分享的是我的经验。

免责声明:我自己已经开始使用Retrofit2.0,并建议任何人都这样做,因为它更快,更容易使用,并且记录更多(并且已经记录好了) ),这是我之前与Volley的经历

在我的情况下,我需要将图像与文本字段一起上传,我构建的MultiPart类有点不同:

主要的是我使用MultiPartEntityBuilder来简化内容,而类本身扩展Request<String>,因为那是响应类型(通常更容易处理),所以这将是构造函数:

public MultiPartImageRequest(String url, String filePath, Response.Listener<String> listener, Response.ErrorListener errorListener)
{
    super(Method.POST, url, errorListener);
    this.listener = listener;
    this.entityBuilder = MultipartEntityBuilder.create();
    this.httpEntity = new MultipartEntity();
    setShouldCache(false);
    this.file = new File(filePath);

    this.entityBuilder.setMode(HttpMultipartMode.BROWSER_COMPATIBLE);
    this.entityBuilder.setBoundary(generateBoundary());

    buildMultipartImageEntity();
}

自己创建边界,并使用包含文件的二进制体和我的文本字段构建实体,给出相应的内容类型,如下所示:

private void buildMultipartImageEntity()
{
    try
    {
        ContentType contentType = ContentType.create("image/jpeg");

        entityBuilder.addBinaryBody("userfile", file, contentType, file.getName());
        entityBuilder.addTextBody("userid", String.valueOf(SettingsManager.getUserID()), ContentType.TEXT_PLAIN);
        httpEntity = entityBuilder.build();

    }
    catch (Exception e)
    {
        VolleyLog.e("UnsupportedEncodingException");
    }
}

private static final char[] MULTIPART_CHARS = "-_1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray();

private String generateBoundary()
{
    StringBuilder buffer = new StringBuilder();
    Random rand = new Random();
    int count = rand.nextInt(11) + 30;

    for (int i = 0; i < count; ++i)
    {
        buffer.append(MULTIPART_CHARS[rand.nextInt(MULTIPART_CHARS.length)]);
    }

    return buffer.toString();
}

从那里开始,getBodyContentType和getBody使用httpEntity对象:

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

@Override
public byte[] getBody() throws AuthFailureError
{
    ByteArrayOutputStream bos = new ByteArrayOutputStream();
    try
    {
        httpEntity.writeTo(bos);
    }
    catch (IOException e)
    {
        VolleyLog.e("IOException writing to ByteArrayOutputStream");
    }
    return bos.toByteArray();
}

我发现另一件事是需要在 utf-8 中解析响应,但在您的情况下可能不需要(取决于响应):

@Override
protected Response<String> parseNetworkResponse(NetworkResponse response)
{
    try
    {
        String responseBody = new String(response.data, "utf-8");
        return (Response.success(responseBody, getCacheEntry()));
    }
    catch (UnsupportedEncodingException e)
    {
         VolleyLog.e("UnsupportedEncodingException");
        return (Response.success("Uploaded, problem with url return", getCacheEntry()));
    }
}

希望这有助于任何方式。