Funktionierende mehrteilige POST-Anfrage mit Volley und ohne HttpEntity

Lesezeit: 13 Minuten

Funktionierende mehrteilige POST Anfrage mit Volley und ohne HttpEntity
BNK

Dies ist nicht wirklich eine Frage, aber ich möchte hier einen Teil meines Arbeitscodes als Referenz teilen, wenn Sie ihn brauchen.

Wie wir das kennen HttpEntity ist von API22 veraltet und wurde seit API23 vollständig entfernt. Im Moment können wir nicht zugreifen HttpEntity-Referenz auf Android Developer mehr (404). Das Folgende ist also mein funktionierender Beispielcode für POST Multipart Request mit Volley und ohne HttpEntity. Es funktioniert, getestet mit Asp.Net Web API. Natürlich ist der Code vielleicht nur ein einfaches Beispiel, das zwei vorhandene Zeichendateien postet, auch nicht die beste Lösung für alle Fälle und keine gute Abstimmung.

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

AKTUALISIEREN:

Den Textteil finden Sie in der Antwort von @Oscar unten.

  • Ich habe gerade den @Kevin-Kommentar zu folgender Frage kopiert: Einige Server sind SEHR wählerisch. Wenn Sie Probleme haben, fügen Sie ein LEERZEICHEN zwischen “;” hinzu. und “filename=” beim Erstellen von Content-Disposition und “multipart/form-data; border=” + border; 🙂

    – BNK

    28. September 2015 um 1:49 Uhr

  • wenn Sie mimtype hinzufügen möchten: dataOutputStream.writeBytes(“Content-Type: image/jpeg” + lineEnd);

    – Maor Hadad

    28. September 2015 um 2:25 Uhr


  • @MaorHadad: Danke für deinen Kommentar 🙂

    – BNK

    28. September 2015 um 2:34 Uhr

  • Vielen Dank für diese tolle Lösung. Nach dem Update auf AppCompat 23 war dieses Problem ein Ärgernis**

    – Maor Hadad

    28. September 2015 um 16:54 Uhr


  • Lieber BNK, funktioniert das für das Hochladen von Videos?

    – mohsen kamrani

    11. November 2015 um 13:16 Uhr

Funktionierende mehrteilige POST Anfrage mit Volley und ohne HttpEntity
Angga Ari Wijaya

Ich schreibe Ihren Code @RacZo und @BNK modularer und benutzerfreundlicher um

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", "[email protected]");
        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);

Überprüfen Sie voller Code VolleyMultipartRequest bei meinem Kern.

  • Ich möchte nicht in Byte oder String konvertieren. In meinem Fall erwartet serverseitig eine Datei und mehrere Texte (Schlüssel-Wert-Paar, mehrteilige Formulardaten), nicht das Byte oder die Zeichenfolge. ist das möglich?

    – Milon

    30. Dezember 2016 um 19:07 Uhr


  • Ja, Sie können Ihre Daten serverseitig wie mehrteilige Formulardaten in Webformularen behandeln. Tatsächlich ändert der Code die HTTP-Header-Anforderung, um sie an ein ähnliches Webformular anzupassen. Dies ist also die Lösung, nach der Sie suchen …

    – Angga Ari Wijaya

    31. Dezember 2016 um 2:24 Uhr

  • @AhamadullahSaikat Sie haben alles, weil dasselbe in meinem Projekt mehrere Anfragen mit demselben Namen sendet

    – Ricky Patel

    21. Januar 2019 um 13:32 Uhr

  • @AnggaAriWijaya Wenn ich ein Bild hochlade, wird ein ungültiges Bild angezeigt, wenn es vom Server heruntergeladen wird. Bitte helfen Sie mir stecken schlecht

    – Sagar

    7. Dezember 2020 um 9:46 Uhr


  • @Sagar überprüfen Sie die hochgeladene Datei auf Ihrem Server? es ist 0KB oder fehlt?

    – Angga Ari Wijaya

    8. Dezember 2020 um 5:07 Uhr

Funktionierende mehrteilige POST Anfrage mit Volley und ohne HttpEntity
Oskar Salguero

Ich möchte die Antwort nur ergänzen. Ich habe versucht herauszufinden, wie Textfelder an den Körper angehängt werden können, und habe dazu die folgende Funktion erstellt:

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

Es funktioniert ziemlich gut.

  • Ich habe die @BNK-Lösung in meine App eingebettet. Ich wähle ein Foto in meiner Telefonbibliothek aus und sende es über mehrteilige Formulardaten, aber es dauert ein paar Sekunden (~ 10-15), bevor es an den Server gesendet wird. Gibt es eine Möglichkeit, diesen Overhead zu reduzieren? oder eine andere empfehlung?

    – Kasillas

    3. Dezember 2015 um 16:04 Uhr

  • Nach der Einstellung von HttpEntity wurden mehrteilige Uploads mit Volley sehr umständlich, außerdem verursachte die Implementierung von PATCH-Anforderungen Kopfschmerzen, sodass ich mich schließlich von Volley wegbewegte und RetroFit implementierte (square.github.io/retrofit) in allen meinen Apps. Ich würde Ihnen empfehlen, dasselbe zu tun, da RetroFit Ihnen eine bessere Abwärtskompatibilität bietet und Ihre App zukunftssicher macht.

    – Oscar Salguero

    3. Dezember 2015 um 22:16 Uhr

  • Ich stimme @RacZo zu, bevorzuge jedoch OkHttp :), ich verwende immer noch Volley für andere Netzwerkanfragen. Ich habe Googles Volley angepasst, um Apache lib zu entfernen, gepostet an github.com/ngocchung/volleynoapachejedoch nur für Get, Post und Multipart getestet.

    – BNK

    4. Dezember 2015 um 0:21 Uhr

  • Ich muss die PATCH-Anfrage mit der Volley-Bibliothek verwenden. Wie kann ich das erreichen.

    – Jagadesh Seeram

    27. Juni 2016 um 13:55 Uhr

1646180229 514 Funktionierende mehrteilige POST Anfrage mit Volley und ohne HttpEntity
Sepehr Nozaryian

Für diejenigen, die Schwierigkeiten haben, utf-8-Parameter zu senden und immer noch kein Glück haben, war das Problem, das ich hatte, im dataOutputStream und änderte den Code von @RacZo in den folgenden Code:

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

Ich habe einen Wrapper der ursprünglichen Volley-Bibliothek gefunden, der für mehrteilige Anfragen einfacher zu integrieren ist. Es unterstützt auch das Hochladen der mehrteiligen Daten zusammen mit anderen Anforderungsparametern. Daher teile ich meinen Code für zukünftige Entwickler, die möglicherweise auf das Problem stoßen, das ich hatte (dh Hochladen von mehrteiligen Daten mit Volley zusammen mit einigen anderen Parametern).

Fügen Sie die folgende Bibliothek in der build.gradle Datei.

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

Bitte beachten Sie, dass ich die ursprüngliche Volley-Bibliothek aus meiner entfernt habe build.gradle und stattdessen die obige Bibliothek verwendet, die sowohl mehrteilige als auch normale Anforderungen mit ähnlicher Integrationstechnik verarbeiten kann.

Dann musste ich nur noch die folgende Klasse schreiben, die die POST-Anforderungsoperation behandelt.

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

Führen Sie nun die Aufgabe wie folgt aus.

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

Mit dieser Bibliothek können Sie jeweils eine Datei hochladen. Ich konnte jedoch mehrere Dateien hochladen, indem ich einfach mehrere Aufgaben initiierte.

Ich hoffe, das hilft!

Hier ist eine Kotlin-Version einer Klasse, die eine mehrteilige Anfrage mit Volley 1.1.1 ermöglicht.

Es basiert hauptsächlich auf der Lösung von @BNK, ist jedoch leicht vereinfacht. Besondere Leistungsprobleme sind mir nicht aufgefallen. Ich habe ein 5-MB-Bild in etwa 3 Sekunden hochgeladen.

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

  • Hallo. Irgendwelche weiteren Informationen darüber, wie wir die obige Klasse verwenden können? Wie können wir zum Beispiel ein .jpg-Bild damit hochladen?

    – Thanasis

    16. Dezember 2019 um 16:40 Uhr

  • Ich stelle nur sicher, dass ich verstehe, wie das funktioniert. Erstellt dies eine temporäre Datei namens fileNameoder lädt es die genannte Datei hoch fileName?

    – SMBiggs

    1. August 2021 um 17:45 Uhr


  • Immer noch nicht in der Lage, dies zum Laufen zu bringen. Soweit ich das beurteilen kann, sendet dies den gesamten Inhalt in einem Stück – nicht wie erwartet in mehreren Stücken. Mein Server kann keine großen Stücke akzeptieren, deshalb versuche ich, mehrteilige POST-Anforderungen zu verwenden. Könnten Sie bitte mehr Informationen oder ein Beispiel geben?

    – SMBiggs

    8. August 2021 um 6:14 Uhr

1646180230 937 Funktionierende mehrteilige POST Anfrage mit Volley und ohne HttpEntity
Schoaib Anwar

Hier ist eine sehr vereinfachte Kotlin-Version für Multipart Volley POST Request

    private fun saveProfileAccount(concern: String, selectedDate: String, WDH: String, details: String, fileExtention: String) {

    val multipartRequest: VolleyMultipartRequest =
        object : VolleyMultipartRequest(
            Request.Method.POST, APIURLS.WhistleBlower,
            Response.Listener<JSONObject> { response ->

            },
            Response.ErrorListener { error ->

                error.printStackTrace()
            }) {

            @Throws(AuthFailureError::class)
            override fun getHeaders(): MutableMap<String, String>{
                val params: MutableMap<String, String> = HashMap()
                params[ConstantValues.XAppApiKey] = APIURLS.XAppApiValue
                return params
            }

            override fun getParams(): Map<String, String>? {
                val params: MutableMap<String, String> = HashMap()
                val sharedPreferences: SharedPreferences = requireActivity().getSharedPreferences(Prefs.PREF_NAME, 0)
                val userId = Prefs.getStringPref(sharedPreferences, Prefs.USER_ID)
                val userName = Prefs.getStringPref(sharedPreferences, Prefs.FULL_NAME)

                params["PersonId"] = userId.toString()
                params["PersonName"] = userName.toString()
                params["Concern"] = concern
                params["DateOfHappen"] = selectedDate
                params["WhereHappened"] = WDH
                params["Ext"] = fileExtention
                

                return params
            }


            override fun getByteData(): Map<String, DataPart>? {
                val params: MutableMap<String, DataPart> = HashMap()
                // file name could found file base or direct access from real path
                // for now just get bitmap data from ImageView

                params["cover"] = DataPart(
                    "sd.pdf",
                    byteArray,
                    "doc/pdf"
                )
                return params
            }
        }

    AppSingleton.getInstance(requireContext())?.addToRequestQueue(multipartRequest)
}

  • Hallo. Irgendwelche weiteren Informationen darüber, wie wir die obige Klasse verwenden können? Wie können wir zum Beispiel ein .jpg-Bild damit hochladen?

    – Thanasis

    16. Dezember 2019 um 16:40 Uhr

  • Ich stelle nur sicher, dass ich verstehe, wie das funktioniert. Erstellt dies eine temporäre Datei namens fileNameoder lädt es die genannte Datei hoch fileName?

    – SMBiggs

    1. August 2021 um 17:45 Uhr


  • Immer noch nicht in der Lage, dies zum Laufen zu bringen. Soweit ich das beurteilen kann, sendet dies den gesamten Inhalt in einem Stück – nicht wie erwartet in mehreren Stücken. Mein Server kann keine großen Stücke akzeptieren, deshalb versuche ich, mehrteilige POST-Anforderungen zu verwenden. Könnten Sie bitte mehr Informationen oder ein Beispiel geben?

    – SMBiggs

    8. August 2021 um 6:14 Uhr

906360cookie-checkFunktionierende mehrteilige POST-Anfrage mit Volley und ohne HttpEntity

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy