Volley 및 HttpEntity없이 POST Multipart 요청 작업


106

이것은 실제로 질문이 아니지만 필요할 때 참조 할 수 있도록 여기에서 작업 코드 중 일부를 공유하고 싶습니다.

우리가 알고 있듯이 HttpEntityAPI22에서 더 이상 사용되지 않으며 API23 이후 완전히 제거되었습니다. 현재 Android 개발자의 HttpEntity Reference에 더 이상 액세스 할 수 없습니다 (404). 따라서 다음은 Volley가 있고 HttpEntity가없는 POST Multipart Request에 대한 작업 샘플 코드입니다 . 작동 중이며 Asp.Net Web API. 물론 코드는 두 개의 기존 드로어 블 파일을 게시하는 기본 샘플 일 뿐이며 모든 경우에 가장 적합한 솔루션은 아니며 좋은 조정도 아닙니다.

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

최신 정보:

텍스트 부분은 아래 @Oscar의 답변을 참조하십시오.


1
다음 질문에 @Kevin 주석을 복사했습니다 . 일부 서버는 매우 까다 롭습니다. 문제가있는 경우 ";"사이에 공백을 추가하십시오. Content-Disposition을 빌드 할 때 "filename ="및 "multipart / form-data; boundary ="+ boundary; :)
— BNK 2015 년

1
mimtype을 추가하려면 dataOutputStream.writeBytes ( "Content-Type : image / jpeg"+ lineEnd);
— Maor 하닷

1
@MaorHadad : :) 귀하의 의견을 주셔서 감사합니다
— BNK

1
이 훌륭한 솔루션에 감사드립니다. APPCOMPAT (23)에 업데이트 한 후이 문제는 A **에 통증이었다
— Maor 하닷

1
Dear BNK가 동영상 업로드에 적용 되나요?
— mok

답변:


63

@RacZo 및 @BNK 코드를 더 모듈화하고 사용하기 쉽게 다시 작성합니다.

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

VolleyMultipartRequest내 요점 에서 전체 코드 를 확인하십시오 .


바이트 또는 문자열로 변환하고 싶지 않습니다. 제 경우에는 서버 측에서 바이트 나 문자열이 아닌 파일과 여러 텍스트 (키-값 쌍, 다중 부분 형식 데이터)를 기대합니다. 그게 가능합니까?
— Milon

그렇습니다. 웹 양식의 멀티 파트 양식 데이터처럼 서버 측의 데이터를 처리 할 수 ​​있습니다. 실제로 코드는 http 헤더 요청을 수정하여 유사한 웹 양식에 맞게 수정하므로 원하는 솔루션입니다.
— Angga Ari Wijaya

당신은 아무것도 가지고 @AhamadullahSaikat 내 프로젝트에서 같은 일이 같은 이름을 가진 여러 요청을 전송하기 때문에
— 리키 파텔

20

대답에 추가하고 싶습니다. 본문에 텍스트 필드를 추가하는 방법을 파악하고 다음과 같은 기능을 만들었습니다.

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

꽤 잘 작동하고 있습니다.


내 앱에 @BNK 솔루션을 내장했습니다. 내 전화 라이브러리에서 사진을 선택하고 여러 부분으로 된 양식 데이터를 통해 보내지 만 서버로 보내기까지 몇 초 (~ 10-15)가 걸립니다. 이 오버 헤드를 줄일 수있는 방법이 있습니까? 또는 다른 추천?
— casillas

1
HttpEntity의 사용이 중단 된 후 발리를 사용한 멀티 파트 업로드가 매우 번거로워졌고 PATCH 요청을 구현하는 것이 골칫거리이므로 결국 발리에서 벗어나 모든 앱에서 RetroFit ( square.github.io/retrofit )를 구현했습니다 . RetroFit이 더 나은 이전 버전과의 호환성을 제공하고 앱의 향후 증거를 제공하므로 동일한 작업을 수행하는 것이 좋습니다.
— Oscar Salguero 2015

@RacZo에 동의하지만 OkHttp를 선호합니다. :) 다른 네트워크 요청에는 여전히 Volley를 사용합니다. 나는 github.com/ngocchung/volleynoapache에 게시 된 Google의 발리 제거 Apache lib를 사용자 정의 했지만 Get, Post 및 Multipart에 대해서만 테스트했습니다.
— BNK 2015

Volley 라이브러리를 사용하여 PATCH 요청을 사용해야합니다. 이걸 어떻게 얻을 수 있습니까?
— Jagadesh Seeram

8

utf-8 매개 변수를 보내는 데 어려움을 겪고 있지만 여전히 운이없는 사람들을 위해 내가 가진 문제는 dataOutputStream에 있었고 @RacZo의 코드를 아래 코드로 변경하십시오.

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

다음은 Volley 1.1.1에서 멀티 파트 요청을 허용하는 클래스의 Kotlin 버전입니다.

대부분 @BNK의 솔루션을 기반으로하지만 약간 단순화되었습니다. 특정 성능 문제를 발견하지 못했습니다. 약 3 초 만에 5Mb 사진을 업로드했습니다.

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

안녕하세요. 위의 클래스를 어떻게 사용할 수 있는지에 대한 추가 정보가 있습니까? 예를 들어 .jpg 이미지를 어떻게 업로드 할 수 있습니까?
— Thanasis

0

다중 파트 요청에 대해 더 쉽게 통합 할 수있는 원래 발리 라이브러리의 래퍼를 찾았습니다. 또한 다른 요청 매개 변수와 함께 멀티 파트 데이터 업로드를 지원합니다. 따라서 저는 제가 겪고있는 문제에 직면 할 수있는 미래의 개발자를 위해 제 코드를 공유하고 있습니다 (예 : 다른 매개 변수와 함께 발리를 사용하여 다중 부분 데이터 업로드).

build.gradle파일에 다음 라이브러리를 추가 하십시오.

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

원래 발리 라이브러리를 제거하고build.gradle 대신 유사한 통합 기술을 가진 다중 부분 및 일반 요청을 모두 처리 할 수있는 위의 라이브러리를 사용했습니다.

그런 다음 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);
    }
}

이제 다음과 같은 작업을 실행하십시오.

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

이 라이브러리를 사용하여 한 번에 하나의 파일을 업로드 할 수 있습니다. 그러나 여러 작업을 시작하는 것만으로 여러 파일을 업로드 할 수 있습니다.

도움이 되었기를 바랍니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.