POST Çok Parçalı İsteği Volley ile ve HttpEntity olmadan Çalışma


106

Bu gerçekten bir soru değil, ancak ihtiyaç duyduğunuzda başvurmanız için çalışma kodumun bir kısmını burada paylaşmak istiyorum.

Bunu bildiği gibi HttpEntityAPI22 dan kaldırılmış ve comletely API23 beri kaldırılır. Şu anda Android Developer'da artık HttpEntity Reference'a erişemiyoruz (404). Dolayısıyla, Volley ile ve HttpEntity olmadan POST Multipart Request için çalışan örnek kodum aşağıdadır . Çalışıyor, ile test edildi Asp.Net Web API. Elbette, kod belki de var olan iki çekilebilir dosyayı gönderen basit bir örnektir, ayrıca tüm durumlar için en iyi çözüm değildir ve iyi bir ayar değildir.

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

GÜNCELLEME:

Metin bölümü için lütfen aşağıdaki @ Oscar'ın yanıtına bakın.


1
Aşağıdaki soruya @ Kevin yorumunu kopyaladım : Bazı sunucular ÇOK seçici. Sorun yaşıyorsanız ";" arasına bir BOŞLUK ekleyin. ve Content-Disposition ve "multipart / form-data; boundary =" + borderary oluştururken "filename ="; :)
BNK

1
mimtype eklemek istiyorsanız: dataOutputStream.writeBytes ("Content-Type: image / jpeg" + lineEnd);
Maor Hadad

1
@MaorHadad: Yorumunuz için teşekkürler :)
BNK

1
Bu harika çözüm için teşekkür ederim. appcompat 23'e güncelledikten sonra bu sorun a **
Maor Hadad

1
Sevgili BNK, bu video yüklemek için çalışıyor mu?
mok

Yanıtlar:


63

@RacZo ve @BNK kodunuzu daha modüler ve kullanımı kolay gibi yeniden yazıyorum

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

VolleyMultipartRequestBenim ana fikrimde kodun tamamını kontrol edin .


Bayta veya dizeye dönüştürmek istemiyorum. benim durumum için, sunucu tarafı bayt veya dize değil, bir dosya ve birden çok metin (anahtar-değer çifti, çok parçalı form verileri) bekliyor. mümkün mü?
Milon

evet öyle, verilerinizi sunucu tarafında web formundaki çok parçalı form verileri gibi işleyebilirsiniz, aslında kod http başlık isteğini sığacak şekilde değiştirir ve benzer web formudur, bu nedenle aradığınız çözüm budur ...
Angga Ari Wijaya

@AhamadullahSaikat bir şey aldınız çünkü projemdeki aynı şey aynı isimde birden fazla istek gönderiyor
Ricky Patel

20

Sadece cevaba eklemek istiyorum. Metin alanlarını vücuda nasıl ekleyeceğimi anlamaya çalışıyordum ve bunu yapmak için aşağıdaki işlevi yarattım:

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

Oldukça iyi çalışıyor.


@BNK çözümünü uygulamama yerleştirdim. Telefon kitaplığımda bir fotoğraf seçiyorum ve çok parçalı form verileriyle gönderiyorum, ancak sunucuya gönderilmesi birkaç saniye (~ 10-15) sürüyor. Bu yükü azaltmanın bir yolu var mı? veya başka bir öneri?
casillas

1
HttpEntity'nin kullanımdan kaldırılmasından sonra, voleybol ile çok parçalı yüklemeler çok külfetli hale geldi, ayrıca PATCH isteklerini uygulamak bir baş ağrısı oldu, bu yüzden voleyboldan uzaklaştım ve tüm uygulamalarımda RetroFit ( square.github.io/retrofit ) uyguladım . Aynı şeyi yapmanızı tavsiye ederim çünkü RetroFit size daha iyi geriye dönük uyumluluk sağlar ve Uygulamanızı ileriye dönük olarak kanıtlar.
Oscar Salguero

@RacZo'ya katılıyorum, ancak OkHttp'yi tercih ediyorum :), yine de diğer ağ istekleri için Volley kullanıyorum. Google'ın github.com/ngocchung/volleynoapache adresinde yayınlanan Apache kitaplığını kaldıran voleybolunu özelleştirdim , ancak yalnızca Get, Post ve Multipart için test ettim .
BNK

Volley kitaplığını kullanarak PATCH isteğini kullanmam gerekiyor. Bunu nasıl başarabilirim?
Jagadesh Seeram

8

Utf-8 parametrelerini göndermekte zorlananlar ve hala şanssız olanlar için, yaşadığım sorun dataOutputStream'de oldu ve @RacZo kodunu aşağıdaki kodla değiştirin:

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

İşte Volley 1.1.1 ile çok parçalı talebe izin veren bir sınıfın Kotlin sürümü.

Çoğunlukla @ BNK'nın çözümüne dayanır, ancak biraz basitleştirilmiştir. Herhangi bir performans sorunu fark etmedim. Yaklaşık 3 saniyede 5 Mb'lık bir resim yükledim.

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

Selam. Yukarıdaki sınıfı nasıl kullanabileceğimize dair daha fazla bilgi var mı? Örneğin onunla bir .jpg resmini nasıl yükleyebiliriz?
Thanasis

0

Çok parçalı istekler için entegre edilmesi daha kolay olan orijinal voleybol kitaplığının bir paketleyicisini buldum. Ayrıca, çok parçalı verilerin diğer istek parametreleriyle birlikte yüklenmesini de destekler. Bu nedenle, yaşadığım problemle karşılaşabilecek gelecekteki geliştiriciler için kodumu paylaşıyorum (yani, voleybolu kullanarak çok parçalı verileri diğer bazı parametrelerle birlikte yüklemek).

Aşağıdaki kitaplığı build.gradledosyaya ekleyin .

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

Lütfen, orijinal voleybol kütüphanesini kendimden çıkardığımı vebuild.gradle bunun yerine, benzer entegrasyon tekniğine sahip hem çok parçalı hem de normal istekleri karşılayabilen yukarıdaki kütüphaneyi kullandım.

Sonra POST istek işlemini işleyen aşağıdaki sınıfı yazmam gerekti.

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

Şimdi görevi aşağıdaki gibi yürütün.

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

Bu kitaplığı kullanarak bir seferde bir dosya yükleyebilirsiniz. Ancak, birden çok görevi başlatarak birden çok dosya yüklemeyi başarabildim.

Umarım yardımcı olur!

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.