使用Volley和没有HttpEntity工作POST多部分请求

时间:2015-08-27 03:26:00

标签: java android asp.net-web-api android-volley httpentity

这不是一个真正的问题,但是,我想在这里分享一些我的工作代码供您参考。

我们知道从API22弃用了HttpEntity,并且自API23以来已完全删除了Asp.Net Web API。目前,我们无法再访问HttpEntity Reference on Android Developer(404)。因此,以下是我的工作示例代码 POST Multipart Request with Volley and without HttpEntity 。它正在使用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(); } } 进行测试。当然,代码可能只是发布两个现有可绘制文件的基本示例,也不是所有情况下的最佳解决方案,也不是很好的调优。

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

MultipartRequest.java:

<script type="text/javascript">
function add(){
    var _tr = document.createElement("tr");
    var _textNode = document.createTextNode("Success");
    _tr.appendChild(_textNode);
    var _child = document.getElementById("botTr");
    var parentDiv = _child.parentNode;

    parentDiv.insertBefore(_tr, botTr);
}
</script>

更新

有关文字部分,请参阅下面的@ Oscar答案。

5 个答案:

答案 0 :(得分:61)

我重写你的代码@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);

gist

处查看完整代码VolleyMultipartRequest

答案 1 :(得分:19)

只想添加答案。我试图想出如何将文本字段附加到正文并创建以下函数来执行此操作:

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

工作得很好。

答案 2 :(得分:4)

对于那些努力发送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);
    } 

答案 3 :(得分:2)

这里是类的Kotlin版本,允许Volley 1.1.1进行多部分请求。

它主要基于@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)
        }
    }
}

答案 4 :(得分:1)

我找到了原始凌空库的包装,对于多部分请求而言,该包装更易于集成。它还支持上载多部分数据以及其他请求参数。因此,我将代码共享给将来的开发人员,这些开发人员可能会遇到我遇到的问题(即使用volley和其他一些参数上传多部分数据)。

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

您可以使用此库一次上传一个文件。但是,我可以通过启动多个任务来设法上传多个文件。

希望有帮助!