Podría estar equivocado en esto, pero creo que debe implementar el suyo propio com.android.volley.toolbox.HttpStack
para esto porque los predeterminados ( HurlStack
si la versión> Gingerbread o HttpClientStack
) no funcionan multipart/form-data
.
Editar:
Y de hecho estaba equivocado. Pude hacerlo usando una MultipartEntity
Solicitud como esta:
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);
}
}
Es bastante crudo, pero lo probé con una imagen y una cadena simple y funciona. La respuesta es un marcador de posición, no tiene mucho sentido devolver una Cadena de respuesta en este caso. Tuve problemas al usar Apache HTTPime para usar MultipartEntity, así que usé este https://code.google.com/p/httpclientandroidlib/ no sé si hay una mejor manera. Espero eso ayude.
Editar
Puede usar httpmime sin usar httpclientandroidlib, la única dependencia es httpcore.
Como se mencionó en la presentación de I / O (aproximadamente a las 4:05), Volley "es terrible" para cargas útiles grandes. Según tengo entendido, eso significa no usar Volley para recibir / enviar archivos (grandes). Mirando el código, parece que ni siquiera está diseñado para manejar datos de formularios de varias partes (por ejemplo, Request.java tiene getBodyContentType () con "application / x-www-form-urlencoded" codificada; HttpClientStack :: createHttpRequest () solo puede manejar bytes [], etc ...). Probablemente podrá crear una implementación que pueda manejar multiparte, pero si yo fuera usted, solo usaría HttpClient directamente con MultipartEntity como:
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);
Es posible que necesite un HttpClient más nuevo (es decir, no el integrado) o incluso mejor, use Volley con un HttpClient más nuevo
fuente
ACTUALIZACIÓN 26/08/2015:
Si no desea utilizar HttpEntity obsoleto, aquí está mi código de muestra de trabajo (probado con ASP.Net WebAPI)
MultipartActivity.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); } }
FIN DE ACTUALIZACIÓN
Este es mi código de muestra de trabajo (solo probado con archivos de tamaño pequeño):
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>
fuente
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); } } } }
Uso de la muestra
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); }
fuente
Un enfoque muy simple para el desarrollador que solo desea enviar parámetros POST en una solicitud de varias partes.
Primero defina estas constantes:
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;
Agregue una función auxiliar para crear un cuerpo de publicación para usted:
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(); }
Anular getBody () y getBodyContentType
public String getBodyContentType() { return MULTIPART_FORMDATA; } public byte[] getBody() throws AuthFailureError { return createPostBody(getParams()).getBytes(); }
fuente
sbPost.append("--" + BOUNDARY + "--");
justo antes de regresar, ya que la API que estoy usando requiere una etiqueta de cierrePrimera respuesta en SO.
Encontré el mismo problema y el código de @alex me resultó muy útil. He realizado algunas modificaciones simples para pasar tantos parámetros como sea necesario a través de HashMap, y básicamente he copiado
parseNetworkResponse()
de StringRequest. He buscado en línea y me sorprendió descubrir que una tarea tan común rara vez se responde. De todos modos, desearía que el código pudiera ayudar: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); }
Y puede usar la clase de la siguiente manera:
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);
fuente
Otra solución, muy ligera con alto rendimiento con carga útil grande:
Biblioteca de cliente Http asincrónico de 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!"); }; } ); }
fuente
Aquí hay una solución simple y un ejemplo completo para cargar archivos usando Volley Android
1) Importación de Gradle
compile 'dev.dworks.libs:volleyplus:+'
2) Ahora cree un administrador de solicitudes de clase
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) Ahora cree una clase para manejar la solicitud de carga de File 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) Y ahora llame al método así para acceder al servicio
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 } }
fuente
Esta es mi forma de hacerlo. Puede ser útil para otros:
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); }
fuente