Làm việc Yêu cầu Multipart POST với Volley và không có HttpEntity


106

Đây thực sự không phải là một câu hỏi, tuy nhiên, tôi muốn chia sẻ một số mã làm việc của tôi ở đây để bạn tham khảo khi cần.

Như chúng ta biết, tính năng HttpEntitynày không được dùng nữa từ API22 và bị xóa hoàn toàn kể từ API23. Hiện tại, chúng tôi không thể truy cập HttpEntity Reference trên Android Developer nữa (404). Vì vậy, sau đây là mã mẫu làm việc của tôi cho Yêu cầu POST Multipart với Volley và không có HttpEntity . Nó đang hoạt động, được thử nghiệm với Asp.Net Web API. Tất nhiên, mã có lẽ chỉ là một mẫu cơ bản đăng hai tệp có thể vẽ tồn tại, cũng không phải là giải pháp tốt nhất cho mọi trường hợp và không được điều chỉnh tốt.

MultipartActivity.java:

package com.example.multipartvolley;

import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.support.v4.content.ContextCompat;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.Toast;

import com.android.volley.NetworkResponse;
import com.android.volley.Response;
import com.android.volley.VolleyError;

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


public class MultipartActivity extends Activity {

    private final Context context = this;
    private final String twoHyphens = "--";
    private final String lineEnd = "\r\n";
    private final String boundary = "apiclient-" + System.currentTimeMillis();
    private final String mimeType = "multipart/form-data;boundary=" + boundary;
    private byte[] multipartBody;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_multipart);

        byte[] fileData1 = getFileDataFromDrawable(context, R.drawable.ic_action_android);
        byte[] fileData2 = getFileDataFromDrawable(context, R.drawable.ic_action_book);

        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        DataOutputStream dos = new DataOutputStream(bos);
        try {
            // the first file
            buildPart(dos, fileData1, "ic_action_android.png");
            // the second file
            buildPart(dos, fileData2, "ic_action_book.png");
            // send multipart form data necesssary after file data
            dos.writeBytes(twoHyphens + boundary + twoHyphens + lineEnd);
            // pass to multipart body
            multipartBody = bos.toByteArray();
        } catch (IOException e) {
            e.printStackTrace();
        }

        String url = "http://192.168.1.100/api/postfile";
        MultipartRequest multipartRequest = new MultipartRequest(url, null, mimeType, multipartBody, new Response.Listener<NetworkResponse>() {
            @Override
            public void onResponse(NetworkResponse response) {
                Toast.makeText(context, "Upload successfully!", Toast.LENGTH_SHORT).show();
            }
        }, new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError error) {
                Toast.makeText(context, "Upload failed!\r\n" + error.toString(), Toast.LENGTH_SHORT).show();
            }
        });

        VolleySingleton.getInstance(context).addToRequestQueue(multipartRequest);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.menu_multipart, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();

        //noinspection SimplifiableIfStatement
        if (id == R.id.action_settings) {
            return true;
        }

        return super.onOptionsItemSelected(item);
    }

    private void buildPart(DataOutputStream dataOutputStream, byte[] fileData, String fileName) throws IOException {
        dataOutputStream.writeBytes(twoHyphens + boundary + lineEnd);
        dataOutputStream.writeBytes("Content-Disposition: form-data; name=\"uploaded_file\"; filename=\""
                + fileName + "\"" + lineEnd);
        dataOutputStream.writeBytes(lineEnd);

        ByteArrayInputStream fileInputStream = new ByteArrayInputStream(fileData);
        int bytesAvailable = fileInputStream.available();

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

        // read file and write it into form...
        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);
    }

    private byte[] getFileDataFromDrawable(Context context, int id) {
        Drawable drawable = ContextCompat.getDrawable(context, id);
        Bitmap bitmap = ((BitmapDrawable) drawable).getBitmap();
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        bitmap.compress(Bitmap.CompressFormat.PNG, 0, byteArrayOutputStream);
        return byteArrayOutputStream.toByteArray();
    }
}

MultipartRequest.java:

package com.example.multipartvolley;

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

import java.util.Map;

class MultipartRequest extends Request<NetworkResponse> {
    private final Response.Listener<NetworkResponse> mListener;
    private final Response.ErrorListener mErrorListener;
    private final Map<String, String> mHeaders;
    private final String mMimeType;
    private final byte[] mMultipartBody;

    public MultipartRequest(String url, Map<String, String> headers, String mimeType, byte[] multipartBody, Response.Listener<NetworkResponse> listener, Response.ErrorListener errorListener) {
        super(Method.POST, url, errorListener);
        this.mListener = listener;
        this.mErrorListener = errorListener;
        this.mHeaders = headers;
        this.mMimeType = mimeType;
        this.mMultipartBody = multipartBody;
    }

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

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

    @Override
    public byte[] getBody() throws AuthFailureError {
        return mMultipartBody;
    }

    @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);
    }
}

CẬP NHẬT:

Về phần văn bản, vui lòng tham khảo câu trả lời của @ Oscar bên dưới.


1
Tôi vừa sao chép bình luận của @Kevin ở câu hỏi sau : Một số máy chủ RẤT kén. Nếu bạn gặp sự cố, hãy thêm dấu cách giữa ";" và "filename =" khi xây dựng Nội dung-Bố trí và "đa phần / biểu mẫu-dữ liệu; ranh giới =" + ranh giới; :)
BNK

1
nếu bạn muốn thêm mimtype: dataOutputStream.writeBytes ("Content-Type: image / jpeg" + lineEnd);
Maor Hadad

1
@MaorHadad: cảm ơn vì nhận xét của bạn :)
BNK

1
Cảm ơn bạn cho giải pháp tuyệt vời này. sau khi cập nhật lên appcompat 23 vấn đề này là nỗi đau trong một **
Maor Hadad

1
BNK thân mến, điều này có hoạt động để tải lên video không?
mok

Câu trả lời:


63

Tôi viết lại mã của bạn @RacZo và @BNK mô-đun hơn và dễ sử dụng như

VolleyMultipartRequest multipartRequest = new VolleyMultipartRequest(Request.Method.POST, url, new Response.Listener<NetworkResponse>() {
    @Override
    public void onResponse(NetworkResponse response) {
        String resultResponse = new String(response.data);
        // parse success output
    }
}, new Response.ErrorListener() {
    @Override
    public void onErrorResponse(VolleyError error) {                
        error.printStackTrace();
    }
}) {
    @Override
    protected Map<String, String> getParams() {
        Map<String, String> params = new HashMap<>();
        params.put("api_token", "gh659gjhvdyudo973823tt9gvjf7i6ric75r76");
        params.put("name", "Angga");
        params.put("location", "Indonesia");
        params.put("about", "UI/UX Designer");
        params.put("contact", "angga@email.com");
        return params;
    }

    @Override
    protected Map<String, DataPart> getByteData() {
        Map<String, DataPart> params = new HashMap<>();
        // file name could found file base or direct access from real path
        // for now just get bitmap data from ImageView
        params.put("avatar", new DataPart("file_avatar.jpg", AppHelper.getFileDataFromDrawable(getBaseContext(), mAvatarImage.getDrawable()), "image/jpeg"));
        params.put("cover", new DataPart("file_cover.jpg", AppHelper.getFileDataFromDrawable(getBaseContext(), mCoverImage.getDrawable()), "image/jpeg"));

        return params;
    }
};

VolleySingleton.getInstance(getBaseContext()).addToRequestQueue(multipartRequest);

Kiểm tra toàn bộ mã VolleyMultipartRequesttại ý chính của tôi .


Tôi không muốn chuyển đổi thành byte hoặc chuỗi. đối với trường hợp của tôi, phía máy chủ yêu cầu một tệp và nhiều văn bản (cặp khóa-giá trị, dữ liệu biểu mẫu nhiều phần), không phải byte hoặc chuỗi. điều đó có thể không?
Milon

đúng vậy, bạn có thể xử lý dữ liệu của mình trong máy chủ như dữ liệu biểu mẫu nhiều phần trong biểu mẫu web, thực ra mã được sửa đổi yêu cầu tiêu đề http để phù hợp với biểu mẫu web tương tự vì vậy đó là giải pháp bạn đang tìm kiếm ...
Angga Ari Wijaya

@AhamadullahSaikat bạn có bất cứ điều gì vì điều tương tự trong dự án của tôi gửi nhiều yêu cầu với cùng một tên
Ricky Patel

20

Chỉ muốn thêm vào câu trả lời. Tôi đang cố gắng tìm cách nối các trường văn bản vào nội dung và tạo hàm sau để thực hiện việc đó:

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);
}

Nó đang hoạt động khá tốt.


Tôi đã nhúng giải pháp @BNK vào ứng dụng của mình. Tôi đang chọn ảnh trong thư viện điện thoại của mình và gửi ảnh qua dữ liệu biểu mẫu nhiều phần, nhưng phải mất vài giây (~ 10-15) trước khi ảnh gửi đến máy chủ. Có cách nào để giảm chi phí này không? hoặc bất kỳ khuyến nghị nào khác?
casillas

1
Sau khi ngừng sử dụng HttpEntity, quá trình tải lên nhiều phần với volley trở nên rất cồng kềnh, cộng với việc thực hiện các yêu cầu PATCH là một vấn đề đau đầu, vì vậy cuối cùng tôi đã rời bỏ volley và triển khai RetroFit ( square.github.io/retrofit ) trong tất cả các ứng dụng của mình. Tôi khuyên bạn nên làm như vậy vì RetroFit cung cấp cho bạn khả năng tương thích ngược tốt hơn và các bằng chứng trong tương lai cho Ứng dụng của bạn.
Oscar Salguero

Tôi đồng ý với @RacZo, tuy nhiên tôi thích OkHttp hơn :), tôi vẫn sử dụng Volley cho các yêu cầu mạng khác. Tôi đã tùy chỉnh volley của Google xóa Apache lib, được đăng lên github.com/ngocchung/volleynoapache , tuy nhiên chỉ được thử nghiệm cho Get, Post và Multipart.
BNK

Tôi cần sử dụng yêu cầu PATCH bằng cách sử dụng thư viện Volley. Làm sao tôi có thể hiểu được điều này.
Jagadesh Seeram

8

Đối với những người đang gặp khó khăn trong việc gửi tham số utf-8 mà vẫn không gặp may, vấn đề tôi gặp phải là trong dataOutputStream và thay đổi mã của @RacZo thành mã dưới đây:

private void buildTextPart(DataOutputStream dataOutputStream, String parameterName, String parameterValue) throws IOException {
        dataOutputStream.writeBytes(twoHyphens + boundary + lineEnd);
        dataOutputStream.writeBytes("Content-Disposition: form-data; name=\"");
        dataOutputStream.write(parameterName.getBytes("UTF-8"));
        dataOutputStream.writeBytes(lineEnd);
        dataOutputStream.writeBytes("Content-Type: text/plain; charset=UTF-8" + lineEnd);
        dataOutputStream.writeBytes(lineEnd);
        dataOutputStream.write(parameterValue.getBytes("UTF-8"));
        dataOutputStream.writeBytes(lineEnd);
    } 

2

Đây là phiên bản Kotlin của một lớp cho phép yêu cầu nhiều phần với Volley 1.1.1.

Nó chủ yếu dựa trên giải pháp của @ BNK nhưng được đơn giản hóa rất nhiều. Tôi không nhận thấy bất kỳ vấn đề hiệu suất cụ thể nào. Tôi đã tải lên một bức ảnh 5Mb trong khoảng 3 giây.

class MultipartWebservice(context: Context) {

    private var queue: RequestQueue? = null

    private val boundary = "apiclient-" + System.currentTimeMillis()
    private val mimeType = "multipart/form-data;boundary=$boundary"

    init {
        queue = Volley.newRequestQueue(context)
    }

    fun sendMultipartRequest(
        method: Int,
        url: String,
        fileData: ByteArray,
        fileName: String,
        listener: Response.Listener<NetworkResponse>,
        errorListener: Response.ErrorListener
    ) {

        // Create multi part byte array
        val bos = ByteArrayOutputStream()
        val dos = DataOutputStream(bos)
        buildMultipartContent(dos, fileData, fileName)
        val multipartBody = bos.toByteArray()

        // Request header, if needed
        val headers = HashMap<String, String>()
        headers["API-TOKEN"] = "458e126682d577c97d225bbd73a75b5989f65e977b6d8d4b2267537019ad9d20"

        val request = MultipartRequest(
            method,
            url,
            errorListener,
            listener,
            headers,
            mimeType,
            multipartBody
        )

        queue?.add(request)

    }

    @Throws(IOException::class)
    private fun buildMultipartContent(dos: DataOutputStream, fileData: ByteArray, fileName: String) {

        val twoHyphens = "--"
        val lineEnd = "\r\n"

        dos.writeBytes(twoHyphens + boundary + lineEnd)
        dos.writeBytes("Content-Disposition: form-data; name=\"file\"; filename=\"$fileName\"$lineEnd")
        dos.writeBytes(lineEnd)
        dos.write(fileData)
        dos.writeBytes(lineEnd)
        dos.writeBytes(twoHyphens + boundary + twoHyphens + lineEnd)
    }

    class MultipartRequest(
        method: Int,
        url: String,
        errorListener: Response.ErrorListener?,
        private var listener: Response.Listener<NetworkResponse>,
        private var headers: MutableMap<String, String>,
        private var mimeType: String,
        private var multipartBody: ByteArray
    ) : Request<NetworkResponse>(method, url, errorListener) {

        override fun getHeaders(): MutableMap<String, String> {
            return if (headers.isEmpty()) super.getHeaders() else headers
        }

        override fun getBodyContentType(): String {
            return mimeType
        }

        override fun getBody(): ByteArray {
            return multipartBody
        }

        override fun parseNetworkResponse(response: NetworkResponse?): Response<NetworkResponse> {
            return try {
                Response.success(response, HttpHeaderParser.parseCacheHeaders(response))
            } catch (e: Exception) {
                Response.error(ParseError(e))
            }
        }

        override fun deliverResponse(response: NetworkResponse?) {
            listener.onResponse(response)
        }
    }
}

Chào. Bất kỳ thông tin nào khác về cách chúng tôi có thể sử dụng lớp trên? Ví dụ: làm thế nào chúng tôi có thể tải lên một hình ảnh .jpg với nó?
Thanasis

0

Tôi đã tìm thấy một trình bao bọc của thư viện volley ban đầu dễ tích hợp hơn cho các yêu cầu nhiều phần. Nó cũng hỗ trợ tải lên dữ liệu nhiều phần cùng với các thông số yêu cầu khác. Do đó, tôi đang chia sẻ mã của mình cho các nhà phát triển trong tương lai, những người có thể gặp phải sự cố mà tôi đang gặp phải (tức là tải lên dữ liệu nhiều phần bằng cách sử dụng volley cùng với một số thông số khác).

Thêm thư viện sau vào build.gradletệp.

dependencies {
    compile 'dev.dworks.libs:volleyplus:+'
}

Xin lưu ý rằng, tôi đã xóa thư viện bóng chuyền ban đầu khỏi build.gradle và sử dụng thư viện trên để thay thế, thư viện này có thể xử lý cả các yêu cầu nhiều phần và bình thường có kỹ thuật tích hợp tương tự.

Sau đó, tôi chỉ cần viết lớp sau để xử lý hoạt động yêu cầu POST.

public class POSTMediasTask {
    public void uploadMedia(final Context context, String filePath) {

        String url = getUrlForPOSTMedia(); // This is a dummy function which returns the POST url for you
        SimpleMultiPartRequest multiPartRequestWithParams = new SimpleMultiPartRequest(Request.Method.POST, url,
                new Response.Listener<String>() {
                    @Override
                    public void onResponse(String response) {
                        Log.d("Response", response);
                        // TODO: Do something on success
                    }
                }, new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError error) {
                // TODO: Handle your error here
            }
        });

        // Add the file here
        multiPartRequestWithParams.addFile("file", filePath);

        // Add the params here
        multiPartRequestWithParams.addStringParam("param1", "SomeParamValue1");
        multiPartRequestWithParams.addStringParam("param2", "SomeParamValue2");

        RequestQueue queue = Volley.newRequestQueue(context);
        queue.add(multiPartRequestWithParams);
    }
}

Bây giờ thực hiện tác vụ như sau.

new POSTMediasTask().uploadMedia(context, mediaPath);

Bạn có thể tải lên từng tệp một bằng cách sử dụng thư viện này. Tuy nhiên, tôi có thể quản lý để tải lên nhiều tệp, chỉ bằng cách bắt đầu nhiều tác vụ.

Hy vọng rằng sẽ giúp!

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.