Как отправить сообщение "multipart / form-data" в Android с помощью Volley
кто-нибудь смог выполнить отправку multipart/form-data
сообщение в Android с залпом еще? Я не имел успеха, пытаясь загрузить image/png
используя запрос POST на наш сервер и мне любопытно, если кто-то есть.
Я считаю, что по умолчанию способ сделать это было бы переопределить public byte[] getPostBody()
на Request.java
класс и прикрепить File
там с пустым ключом заголовка для границы. Тем не менее, преобразование моего файла в String
на Map<String, String> postParams
и затем, когда он снова закодирован, кажется тупым и не очень элегантно. Кроме того, я был неудачен в своих попытках. Это действительно единственное, что удерживает нас от перехода в эту библиотеку.
в любом случае, все мысли и ответы очень ценится. Спасибо за помощь.
9 ответов:
Я могу ошибаться в этом, но я думаю, что вам нужно реализовать свой собственный
com.android.volley.toolbox.HttpStack
для этого, потому что по умолчанию (HurlStack
если версия > Gingerbread илиHttpClientStack
) Не разбирайтесь сmultipart/form-data
.Edit:
и действительно, я был неправ. Я смог сделать это с помощью
MultipartEntity
запрос такой:public class MultipartRequest extends Request<String> { private MultipartEntity entity = new MultipartEntity(); private static final String FILE_PART_NAME = "file"; private static final String STRING_PART_NAME = "text"; private final Response.Listener<String> mListener; private final File mFilePart; private final String mStringPart; public MultipartRequest(String url, Response.ErrorListener errorListener, Response.Listener<String> listener, File file, String stringPart) { super(Method.POST, url, errorListener); mListener = listener; mFilePart = file; mStringPart = stringPart; buildMultipartEntity(); } private void buildMultipartEntity() { entity.addPart(FILE_PART_NAME, new FileBody(mFilePart)); try { entity.addPart(STRING_PART_NAME, new StringBody(mStringPart)); } catch (UnsupportedEncodingException e) { VolleyLog.e("UnsupportedEncodingException"); } } @Override public String getBodyContentType() { return entity.getContentType().getValue(); } @Override public byte[] getBody() throws AuthFailureError { ByteArrayOutputStream bos = new ByteArrayOutputStream(); try { entity.writeTo(bos); } catch (IOException e) { VolleyLog.e("IOException writing to ByteArrayOutputStream"); } return bos.toByteArray(); } @Override protected Response<String> parseNetworkResponse(NetworkResponse response) { return Response.success("Uploaded", getCacheEntry()); } @Override protected void deliverResponse(String response) { mListener.onResponse(response); } }
это довольно сырой, но я попробовал его с изображением и простой строки, и это работает. Ответ является заполнителем, не имеет большого смысла в этом случае возвращает строку ответа. У меня были проблемы с использованием Apache httpmime для использования MultipartEntity поэтому я использовал это https://code.google.com/p/httpclientandroidlib/ не знаю, есть ли лучший способ. Надеюсь, это поможет.
Edit
вы можете использовать httpmime без использования httpclientandroidlib, единственная зависимость-httpcore.
Как уже упоминалось в презентации на I / O (около 4:05), залп "ужасен" для больших полезных нагрузок. Как я понимаю, это означает не использовать Volley для получения/отправки (больших) файлов. Глядя на код, кажется, что он даже не предназначен для обработки данных составных форм (например, запрос.java имеет getBodyContentType () с жестко закодированным "application/x-www-form-urlencoded"; HttpClientStack::createHttpRequest () может обрабатывать только байт [] и т. д...). Возможно вы сможете создать реализацию это может обрабатывать multipart, но если бы я был вами, я просто буду использовать HttpClient напрямую с MultipartEntity, например:
HttpPost req = new HttpPost(composeTargetUrl()); MultipartEntity entity = new MultipartEntity(); entity.addPart(POST_IMAGE_VAR_NAME, new FileBody(toUpload)); try { entity.addPart(POST_SESSION_VAR_NAME, new StringBody(uploadSessionId)); } catch (UnsupportedEncodingException e) { throw new RuntimeException(e); } req.setEntity(entity);
возможно, Вам понадобится новый HttpClient (т. е. не встроенный) или даже лучше, используйте Volley с новым HttpClient
полный составной запрос с прогрессом загрузки
import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FilterOutputStream; import java.io.IOException; import java.io.OutputStream; import java.io.UnsupportedEncodingException; import java.util.HashMap; import java.util.Map; import org.apache.http.HttpEntity; import org.apache.http.entity.ContentType; import org.apache.http.entity.mime.HttpMultipartMode; import org.apache.http.entity.mime.MultipartEntityBuilder; import org.apache.http.entity.mime.content.FileBody; import org.apache.http.util.CharsetUtils; import com.android.volley.AuthFailureError; import com.android.volley.NetworkResponse; import com.android.volley.Request; import com.android.volley.Response; import com.android.volley.VolleyLog; import com.beusoft.app.AppContext; public class MultipartRequest extends Request<String> { MultipartEntityBuilder entity = MultipartEntityBuilder.create(); HttpEntity httpentity; private String FILE_PART_NAME = "files"; private final Response.Listener<String> mListener; private final File mFilePart; private final Map<String, String> mStringPart; private Map<String, String> headerParams; private final MultipartProgressListener multipartProgressListener; private long fileLength = 0L; public MultipartRequest(String url, Response.ErrorListener errorListener, Response.Listener<String> listener, File file, long fileLength, Map<String, String> mStringPart, final Map<String, String> headerParams, String partName, MultipartProgressListener progLitener) { super(Method.POST, url, errorListener); this.mListener = listener; this.mFilePart = file; this.fileLength = fileLength; this.mStringPart = mStringPart; this.headerParams = headerParams; this.FILE_PART_NAME = partName; this.multipartProgressListener = progLitener; entity.setMode(HttpMultipartMode.BROWSER_COMPATIBLE); try { entity.setCharset(CharsetUtils.get("UTF-8")); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } buildMultipartEntity(); httpentity = entity.build(); } // public void addStringBody(String param, String value) { // if (mStringPart != null) { // mStringPart.put(param, value); // } // } private void buildMultipartEntity() { entity.addPart(FILE_PART_NAME, new FileBody(mFilePart, ContentType.create("image/gif"), mFilePart.getName())); if (mStringPart != null) { for (Map.Entry<String, String> entry : mStringPart.entrySet()) { entity.addTextBody(entry.getKey(), entry.getValue()); } } } @Override public String getBodyContentType() { return httpentity.getContentType().getValue(); } @Override public byte[] getBody() throws AuthFailureError { ByteArrayOutputStream bos = new ByteArrayOutputStream(); try { httpentity.writeTo(new CountingOutputStream(bos, fileLength, multipartProgressListener)); } catch (IOException e) { VolleyLog.e("IOException writing to ByteArrayOutputStream"); } return bos.toByteArray(); } @Override protected Response<String> parseNetworkResponse(NetworkResponse response) { try { // System.out.println("Network Response "+ new String(response.data, "UTF-8")); return Response.success(new String(response.data, "UTF-8"), getCacheEntry()); } catch (UnsupportedEncodingException e) { e.printStackTrace(); // fuck it, it should never happen though return Response.success(new String(response.data), getCacheEntry()); } } @Override protected void deliverResponse(String response) { mListener.onResponse(response); } //Override getHeaders() if you want to put anything in header public static interface MultipartProgressListener { void transferred(long transfered, int progress); } public static class CountingOutputStream extends FilterOutputStream { private final MultipartProgressListener progListener; private long transferred; private long fileLength; public CountingOutputStream(final OutputStream out, long fileLength, final MultipartProgressListener listener) { super(out); this.fileLength = fileLength; this.progListener = listener; this.transferred = 0; } public void write(byte[] b, int off, int len) throws IOException { out.write(b, off, len); if (progListener != null) { this.transferred += len; int prog = (int) (transferred * 100 / fileLength); this.progListener.transferred(this.transferred, prog); } } public void write(int b) throws IOException { out.write(b); if (progListener != null) { this.transferred++; int prog = (int) (transferred * 100 / fileLength); this.progListener.transferred(this.transferred, prog); } } } }
Пример Использования
protected <T> void uploadFile(final String tag, final String url, final File file, final String partName, final Map<String, String> headerParams, final Response.Listener<String> resultDelivery, final Response.ErrorListener errorListener, MultipartProgressListener progListener) { AZNetworkRetryPolicy retryPolicy = new AZNetworkRetryPolicy(); MultipartRequest mr = new MultipartRequest(url, errorListener, resultDelivery, file, file.length(), null, headerParams, partName, progListener); mr.setRetryPolicy(retryPolicy); mr.setTag(tag); Volley.newRequestQueue(this).add(mr); }
обновление 2015/08/26:
Если вы не хотите использовать устаревший HttpEntity, вот мой рабочий пример кода (проверено с помощью ASP.Net WebAPI)
многопартийность.java
package com.example.volleyapp; 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 com.android.volley.AuthFailureError; import com.android.volley.NetworkResponse; import com.android.volley.Response; import com.android.volley.VolleyError; import com.example.volleyapp.BaseVolleyRequest; import com.example.volleyapp.VolleySingleton; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.DataOutputStream; import java.io.IOException; public class MultipartActivity extends Activity { final Context mContext = this; String mimeType; DataOutputStream dos = null; String lineEnd = "\r\n"; String boundary = "apiclient-" + System.currentTimeMillis(); String twoHyphens = "--"; int bytesRead, bytesAvailable, bufferSize; byte[] buffer; int maxBufferSize = 1024 * 1024; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_multipart); Drawable drawable = ContextCompat.getDrawable(mContext, R.drawable.ic_action_file_attachment_light); Bitmap bitmap = ((BitmapDrawable) drawable).getBitmap(); ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); bitmap.compress(Bitmap.CompressFormat.PNG, 100, byteArrayOutputStream); final byte[] bitmapData = byteArrayOutputStream.toByteArray(); String url = "http://192.168.1.100/api/postfile"; mimeType = "multipart/form-data;boundary=" + boundary; BaseVolleyRequest baseVolleyRequest = new BaseVolleyRequest(1, url, new Response.Listener<NetworkResponse>() { @Override public void onResponse(NetworkResponse response) { } }, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError error) { } }) { @Override public String getBodyContentType() { return mimeType; } @Override public byte[] getBody() throws AuthFailureError { ByteArrayOutputStream bos = new ByteArrayOutputStream(); dos = new DataOutputStream(bos); try { dos.writeBytes(twoHyphens + boundary + lineEnd); dos.writeBytes("Content-Disposition: form-data; name=\"uploaded_file\";filename=\"" + "ic_action_file_attachment_light.png" + "\"" + lineEnd); dos.writeBytes(lineEnd); ByteArrayInputStream fileInputStream = new ByteArrayInputStream(bitmapData); bytesAvailable = fileInputStream.available(); bufferSize = Math.min(bytesAvailable, maxBufferSize); buffer = new byte[bufferSize]; // read file and write it into form... bytesRead = fileInputStream.read(buffer, 0, bufferSize); while (bytesRead > 0) { dos.write(buffer, 0, bufferSize); bytesAvailable = fileInputStream.available(); bufferSize = Math.min(bytesAvailable, maxBufferSize); bytesRead = fileInputStream.read(buffer, 0, bufferSize); } // send multipart form data necesssary after file data... dos.writeBytes(lineEnd); dos.writeBytes(twoHyphens + boundary + twoHyphens + lineEnd); return bos.toByteArray(); } catch (IOException e) { e.printStackTrace(); } return bitmapData; } }; VolleySingleton.getInstance(mContext).addToRequestQueue(baseVolleyRequest); } @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); } }
BaseVolleyRequest.java:
package com.example.volleyapp; 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 com.google.gson.JsonSyntaxException; public class BaseVolleyRequest extends Request<NetworkResponse> { private final Response.Listener<NetworkResponse> mListener; private final Response.ErrorListener mErrorListener; public BaseVolleyRequest(String url, Response.Listener<NetworkResponse> listener, Response.ErrorListener errorListener) { super(0, url, errorListener); this.mListener = listener; this.mErrorListener = errorListener; } public BaseVolleyRequest(int method, String url, Response.Listener<NetworkResponse> listener, Response.ErrorListener errorListener) { super(method, url, errorListener); this.mListener = listener; this.mErrorListener = errorListener; } @Override protected Response<NetworkResponse> parseNetworkResponse(NetworkResponse response) { try { return Response.success( response, HttpHeaderParser.parseCacheHeaders(response)); } catch (JsonSyntaxException e) { return Response.error(new ParseError(e)); } catch (Exception e) { return Response.error(new ParseError(e)); } } @Override protected void deliverResponse(NetworkResponse response) { mListener.onResponse(response); } @Override protected VolleyError parseNetworkError(VolleyError volleyError) { return super.parseNetworkError(volleyError); } @Override public void deliverError(VolleyError error) { mErrorListener.onErrorResponse(error); } }
КОНЕЦ ОБНОВЛЕНИЯ
Это мой рабочий пример кода (тестируется только с файлами малого размера):
public class FileUploadActivity extends Activity { private final Context mContext = this; HttpEntity httpEntity; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_file_upload); Drawable drawable = getResources().getDrawable(R.drawable.ic_action_home); if (drawable != null) { Bitmap bitmap = ((BitmapDrawable) drawable).getBitmap(); ByteArrayOutputStream stream = new ByteArrayOutputStream(); bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream); final byte[] bitmapdata = stream.toByteArray(); String url = "http://10.0.2.2/api/fileupload"; MultipartEntityBuilder builder = MultipartEntityBuilder.create(); builder.setMode(HttpMultipartMode.BROWSER_COMPATIBLE); // Add binary body if (bitmapdata != null) { ContentType contentType = ContentType.create("image/png"); String fileName = "ic_action_home.png"; builder.addBinaryBody("file", bitmapdata, contentType, fileName); httpEntity = builder.build(); MyRequest myRequest = new MyRequest(Request.Method.POST, url, new Response.Listener<NetworkResponse>() { @Override public void onResponse(NetworkResponse response) { try { String jsonString = new String(response.data, HttpHeaderParser.parseCharset(response.headers)); Toast.makeText(mContext, jsonString, Toast.LENGTH_SHORT).show(); } catch (Exception e) { e.printStackTrace(); } } }, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError error) { Toast.makeText(mContext, error.toString(), Toast.LENGTH_SHORT).show(); } }) { @Override public String getBodyContentType() { return httpEntity.getContentType().getValue(); } @Override public byte[] getBody() throws AuthFailureError { ByteArrayOutputStream bos = new ByteArrayOutputStream(); try { httpEntity.writeTo(bos); } catch (IOException e) { VolleyLog.e("IOException writing to ByteArrayOutputStream"); } return bos.toByteArray(); } }; MySingleton.getInstance(this).addToRequestQueue(myRequest); } } } ... } public class MyRequest extends Request<NetworkResponse>
очень простой подход для разработчиков, которые просто хотят отправить параметры POST в составном запросе.
внести следующие изменения в класс, который расширяет запросу.java
сначала определите эти константы:
String BOUNDARY = "s2retfgsGSRFsERFGHfgdfgw734yhFHW567TYHSrf4yarg"; //This the boundary which is used by the server to split the post parameters. String MULTIPART_FORMDATA = "multipart/form-data;boundary=" + BOUNDARY;
добавить вспомогательную функцию для создания тела сообщения для вас:
private String createPostBody(Map<String, String> params) { StringBuilder sbPost = new StringBuilder(); if (params != null) { for (String key : params.keySet()) { if (params.get(key) != null) { sbPost.append("\r\n" + "--" + BOUNDARY + "\r\n"); sbPost.append("Content-Disposition: form-data; name=\"" + key + "\"" + "\r\n\r\n"); sbPost.append(params.get(key).toString()); } } } return sbPost.toString(); }
переопределить getBody () и getBodyContentType
public String getBodyContentType() { return MULTIPART_FORMDATA; } public byte[] getBody() throws AuthFailureError { return createPostBody(getParams()).getBytes(); }
первый ответ на так.
я столкнулся с той же проблемой и нашел код @alex очень полезным. Я сделал несколько простых изменений, чтобы передать столько параметров, сколько необходимо через HashMap, и в основном скопировал
parseNetworkResponse()
из StringRequest. Я искал в интернете и так удивился, узнав, что такая общая задача так редко отвечает. В любом случае, я хотел бы, чтобы код мог помочь:public class MultipartRequest extends Request<String> { private MultipartEntity entity = new MultipartEntity(); private static final String FILE_PART_NAME = "image"; private final Response.Listener<String> mListener; private final File file; private final HashMap<String, String> params; public MultipartRequest(String url, Response.Listener<String> listener, Response.ErrorListener errorListener, File file, HashMap<String, String> params) { super(Method.POST, url, errorListener); mListener = listener; this.file = file; this.params = params; buildMultipartEntity(); } private void buildMultipartEntity() { entity.addPart(FILE_PART_NAME, new FileBody(file)); try { for ( String key : params.keySet() ) { entity.addPart(key, new StringBody(params.get(key))); } } catch (UnsupportedEncodingException e) { VolleyLog.e("UnsupportedEncodingException"); } } @Override public String getBodyContentType() { return entity.getContentType().getValue(); } @Override public byte[] getBody() throws AuthFailureError { ByteArrayOutputStream bos = new ByteArrayOutputStream(); try { entity.writeTo(bos); } catch (IOException e) { VolleyLog.e("IOException writing to ByteArrayOutputStream"); } return bos.toByteArray(); } /** * copied from Android StringRequest class */ @Override protected Response<String> parseNetworkResponse(NetworkResponse response) { String parsed; try { parsed = new String(response.data, HttpHeaderParser.parseCharset(response.headers)); } catch (UnsupportedEncodingException e) { parsed = new String(response.data); } return Response.success(parsed, HttpHeaderParser.parseCacheHeaders(response)); } @Override protected void deliverResponse(String response) { mListener.onResponse(response); }
и вы можете использовать класс в качестве следующее:
HashMap<String, String> params = new HashMap<String, String>(); params.put("type", "Some Param"); params.put("location", "Some Param"); params.put("contact", "Some Param"); MultipartRequest mr = new MultipartRequest(url, new Response.Listener<String>(){ @Override public void onResponse(String response) { Log.d("response", response); } }, new Response.ErrorListener(){ @Override public void onErrorResponse(VolleyError error) { Log.e("Volley Request Error", error.getLocalizedMessage()); } }, f, params); Volley.newRequestQueue(this).add(mr);
другое решение, очень легкое с высокой производительностью с большой полезной нагрузкой:
асинхронная клиентская библиотека Http для Android: http://loopj.com/android-async-http/
private static AsyncHttpClient client = new AsyncHttpClient(); private void uploadFileExecute(File file) { RequestParams params = new RequestParams(); try { params.put("photo", file); } catch (FileNotFoundException e) {} client.post(getUrl(), params, new AsyncHttpResponseHandler() { public void onSuccess(String result) { Log.d(TAG,"uploadFile response: "+result); }; public void onFailure(Throwable arg0, String errorMsg) { Log.d(TAG,"uploadFile ERROR!"); }; } ); }
вот простое решение и полный пример для загрузки файла с помощью Volley Android
1) Gradle Import
compile 'dev.dworks.libs:volleyplus:+'
2) Теперь создайте класс RequestManager
public class RequestManager { private static RequestManager mRequestManager; /** * Queue which Manages the Network Requests :-) */ private static RequestQueue mRequestQueue; // ImageLoader Instance private RequestManager() { } public static RequestManager get(Context context) { if (mRequestManager == null) mRequestManager = new RequestManager(); return mRequestManager; } /** * @param context application context */ public static RequestQueue getnstance(Context context) { if (mRequestQueue == null) { mRequestQueue = Volley.newRequestQueue(context); } return mRequestQueue; } }
3) Теперь создайте класс для обработки запроса на загрузку файла WebService
public class WebService { private RequestQueue mRequestQueue; private static WebService apiRequests = null; public static WebService getInstance() { if (apiRequests == null) { apiRequests = new WebService(); return apiRequests; } return apiRequests; } public void updateProfile(Context context, String doc_name, String doc_type, String appliance_id, File file, Response.Listener<String> listener, Response.ErrorListener errorListener) { SimpleMultiPartRequest request = new SimpleMultiPartRequest(Request.Method.POST, "YOUR URL HERE", listener, errorListener); // request.setParams(data); mRequestQueue = RequestManager.getnstance(context); request.addMultipartParam("token", "text", "tdfysghfhsdfh"); request.addMultipartParam("parameter_1", "text", doc_name); request.addMultipartParam("dparameter_2", "text", doc_type); request.addMultipartParam("parameter_3", "text", appliance_id); request.addFile("document_file", file.getPath()); request.setFixedStreamingMode(true); mRequestQueue.add(request); } }
4) и теперь вызовите метод, как это, чтобы попасть в службу
public class Main2Activity extends AppCompatActivity implements Response.ErrorListener, Response.Listener<String>{ @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main2); Button button=(Button)findViewById(R.id.button); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { uploadData(); } }); } private void uploadData() { WebService.getInstance().updateProfile(getActivity(), "appl_doc", "appliance", "1", mChoosenFile, this, this); } @Override public void onErrorResponse(VolleyError error) { } @Override public void onResponse(String response) { //Your response here } }
Это мой способ сделать это. Это может быть полезно для других:
private void updateType(){ // Log.i(TAG,"updateType"); StringRequest request = new StringRequest(Request.Method.POST, url, new Response.Listener<String>() { @Override public void onResponse(String response) { // running on main thread------- try { JSONObject res = new JSONObject(response); res.getString("result"); System.out.println("Response:" + res.getString("result")); }else{ CustomTast ct=new CustomTast(context); ct.showCustomAlert("Network/Server Disconnected",R.drawable.disconnect); } } catch (Exception e) { e.printStackTrace(); //Log.e("Response", "==> " + e.getMessage()); } } }, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError volleyError) { // running on main thread------- VolleyLog.d(TAG, "Error: " + volleyError.getMessage()); } }) { protected Map<String, String> getParams() { HashMap<String, String> hashMapParams = new HashMap<String, String>(); hashMapParams.put("key", "value"); hashMapParams.put("key", "value"); hashMapParams.put("key", "value")); hashMapParams.put("key", "value"); System.out.println("Hashmap:" + hashMapParams); return hashMapParams; } }; AppController.getInstance().addToRequestQueue(request); }