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.