Publicar solicitud multiparte con Android SDK

Estoy tratando de hacer algo que pensé que sería relativamente simple: subir una imagen a un servidor con el SDK de Android. Me encontraron un montón de código de ejemplo:

http://groups.google.com/group/android-developers/browse_thread/thread/f9e17bbaf50c5fc/46145fcacd450e48

http://linklens.blogspot.com/2009/06/android-multipart-upload.html

Pero ninguno funciona para mí. La confusión que sigo encontrando es lo que realmente se necesita para hacer una solicitud múltiple. ¿Cuál es la forma más simple de tener una carga múltiple (con una imagen) para Android?

¡Cualquier ayuda o consejo sería muy apreciado!

Actualización 29 de abril de 2014:

Mi respuesta es un poco vieja y supongo que preferiría usar algún tipo de biblioteca de alto nivel como Retrofit .


Basado en este blog, se me ocurrió la siguiente solución: http://blog.tacticalnuclearstrike.com/2010/01/using-multipartentity-in-android-applications/

¡Tendrás que descargar bibliotecas adicionales para ejecutar MultipartEntity !

1) Descargue httpcomponents-client-4.1.zip desde http://james.apache.org/download.cgi#Apache_Mime4J y agregue apache-mime4j-0.6.1.jar a su proyecto.

2) Descargue httpcomponents-client-4.1-bin.zip desde http://hc.apache.org/downloads.cgi y agregue httpclient-4.1.jar, httpcore-4.1.jar y httpmime-4.1.jar a su proyecto.

3) Usa el siguiente código de ejemplo.

 private DefaultHttpClient mHttpClient; public ServerCommunication() { HttpParams params = new BasicHttpParams(); params.setParameter(CoreProtocolPNames.PROTOCOL_VERSION, HttpVersion.HTTP_1_1); mHttpClient = new DefaultHttpClient(params); } public void uploadUserPhoto(File image) { try { HttpPost httppost = new HttpPost("some url"); MultipartEntity multipartEntity = new MultipartEntity(HttpMultipartMode.BROWSER_COMPATIBLE); multipartEntity.addPart("Title", new StringBody("Title")); multipartEntity.addPart("Nick", new StringBody("Nick")); multipartEntity.addPart("Email", new StringBody("Email")); multipartEntity.addPart("Description", new StringBody(Settings.SHARE.TEXT)); multipartEntity.addPart("Image", new FileBody(image)); httppost.setEntity(multipartEntity); mHttpClient.execute(httppost, new PhotoUploadResponseHandler()); } catch (Exception e) { Log.e(ServerCommunication.class.getName(), e.getLocalizedMessage(), e); } } private class PhotoUploadResponseHandler implements ResponseHandler { @Override public Object handleResponse(HttpResponse response) throws ClientProtocolException, IOException { HttpEntity r_entity = response.getEntity(); String responseString = EntityUtils.toString(r_entity); Log.d("UPLOAD", responseString); return null; } } 

Como MultiPartEntity está en desuso . ¡Así que aquí está la nueva forma de hacerlo! Y solo necesita httpcore.jar(latest) y httpmime.jar(latest) descargarlos del sitio de Apache.

 try { HttpClient client = new DefaultHttpClient(); HttpPost post = new HttpPost(URL); MultipartEntityBuilder entityBuilder = MultipartEntityBuilder.create(); entityBuilder.setMode(HttpMultipartMode.BROWSER_COMPATIBLE); entityBuilder.addTextBody(USER_ID, userId); entityBuilder.addTextBody(NAME, name); entityBuilder.addTextBody(TYPE, type); entityBuilder.addTextBody(COMMENT, comment); entityBuilder.addTextBody(LATITUDE, String.valueOf(User.Latitude)); entityBuilder.addTextBody(LONGITUDE, String.valueOf(User.Longitude)); if(file != null) { entityBuilder.addBinaryBody(IMAGE, file); } HttpEntity entity = entityBuilder.build(); post.setEntity(entity); HttpResponse response = client.execute(post); HttpEntity httpEntity = response.getEntity(); result = EntityUtils.toString(httpEntity); Log.v("result", result); } catch(Exception e) { e.printStackTrace(); } 

Aquí está la solución LIGHT WEIGHTED que funcionó para mí sin HTTPCore externo y tales libs. Me enfrentaba al problema de los métodos de 64 K, por lo que no me queda otra opción para evitar las bibliotecas HTTPCore

 import java.util.List; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.net.HttpURLConnection; import java.net.URL; import java.net.URLConnection; import java.util.ArrayList; import java.util.List; /** * This utility class provides an abstraction layer for sending multipart HTTP * POST requests to a web server. * * @author www.codejava.net */ public class MultipartUtility { private final String boundary; private static final String LINE_FEED = "\r\n"; private HttpURLConnection httpConn; private String charset; private OutputStream outputStream; private PrintWriter writer; /** * This constructor initializes a new HTTP POST request with content type * is set to multipart/form-data * * @param requestURL * @param charset * @throws IOException */ public MultipartUtility(String requestURL, String charset) throws IOException { this.charset = charset; // creates a unique boundary based on time stamp boundary = "===" + System.currentTimeMillis() + "==="; URL url = new URL(requestURL); httpConn = (HttpURLConnection) url.openConnection(); httpConn.setUseCaches(false); httpConn.setDoOutput(true); // indicates POST method httpConn.setDoInput(true); httpConn.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary); httpConn.setRequestProperty("User-Agent", "CodeJava Agent"); httpConn.setRequestProperty("Test", "Bonjour"); outputStream = httpConn.getOutputStream(); writer = new PrintWriter(new OutputStreamWriter(outputStream, charset), true); } /** * Adds a form field to the request * * @param name field name * @param value field value */ public void addFormField(String name, String value) { writer.append("--" + boundary).append(LINE_FEED); writer.append("Content-Disposition: form-data; name=\"" + name + "\"") .append(LINE_FEED); writer.append("Content-Type: text/plain; charset=" + charset).append( LINE_FEED); writer.append(LINE_FEED); writer.append(value).append(LINE_FEED); writer.flush(); } /** * Adds a upload file section to the request * * @param fieldName name attribute in  * @param uploadFile a File to be uploaded * @throws IOException */ public void addFilePart(String fieldName, File uploadFile) throws IOException { String fileName = uploadFile.getName(); writer.append("--" + boundary).append(LINE_FEED); writer.append( "Content-Disposition: form-data; name=\"" + fieldName + "\"; filename=\"" + fileName + "\"") .append(LINE_FEED); writer.append( "Content-Type: " + URLConnection.guessContentTypeFromName(fileName)) .append(LINE_FEED); writer.append("Content-Transfer-Encoding: binary").append(LINE_FEED); writer.append(LINE_FEED); writer.flush(); FileInputStream inputStream = new FileInputStream(uploadFile); byte[] buffer = new byte[4096]; int bytesRead = -1; while ((bytesRead = inputStream.read(buffer)) != -1) { outputStream.write(buffer, 0, bytesRead); } outputStream.flush(); inputStream.close(); writer.append(LINE_FEED); writer.flush(); } /** * Adds a header field to the request. * * @param name - name of the header field * @param value - value of the header field */ public void addHeaderField(String name, String value) { writer.append(name + ": " + value).append(LINE_FEED); writer.flush(); } /** * Completes the request and receives response from the server. * * @return a list of Strings as response in case the server returned * status OK, otherwise an exception is thrown. * @throws IOException */ public List finish() throws IOException { List response = new ArrayList(); writer.append(LINE_FEED).flush(); writer.append("--" + boundary + "--").append(LINE_FEED); writer.close(); // checks server's status code first int status = httpConn.getResponseCode(); if (status == HttpURLConnection.HTTP_OK) { BufferedReader reader = new BufferedReader(new InputStreamReader( httpConn.getInputStream())); String line = null; while ((line = reader.readLine()) != null) { response.add(line); } reader.close(); httpConn.disconnect(); } else { throw new IOException("Server returned non-OK status: " + status); } return response; } } 

USO

 private void uploadMedia() { try { String charset = "UTF-8"; File uploadFile1 = new File("/sdcard/myvideo.mp4"); String requestURL = Data.BASE_URL+Data.URL_UPLOAD_REACTION_TEST; MultipartUtility multipart = new MultipartUtility(requestURL, charset); // multipart.addHeaderField("User-Agent", "CodeJava"); // multipart.addHeaderField("Test-Header", "Header-Value"); multipart.addFormField("friend_id", "Cool Pictures"); multipart.addFormField("userid", "Java,upload,Spring"); multipart.addFilePart("uploadedfile", uploadFile1); List response = multipart.finish(); Log.v("rht", "SERVER REPLIED:"); for (String line : response) { Log.v("rht", "Line : "+line); } } catch (Exception e) { e.printStackTrace(); } } 

Código PHP para aceptar la carga

  

Más fácil, ligero (32k) y mucho más rendimiento:

Biblioteca de Android Asynchronous Http Client: http://loopj.com/android-async-http/

Implementación:

Cómo enviar un POST “multipart / form-data” en Android con Volley

Prueba esto:

  public void SendMultipartFile() { Log.d(TAG, "UPLOAD: SendMultipartFile"); DefaultHttpClient httpclient = new DefaultHttpClient(); HttpPost httppost = new HttpPost(  ); File file = new File("/sdcard/spider.jpg"); Log.d(TAG, "UPLOAD: setting up multipart entity"); MultipartEntity mpEntity = new MultipartEntity(HttpMultipartMode.BROWSER_COMPATIBLE); Log.d(TAG, "UPLOAD: file length = " + file.length()); Log.d(TAG, "UPLOAD: file exist = " + file.exists()); try { mpEntity.addPart("datafile", new FileBody(file, "application/octet")); mpEntity.addPart("id", new StringBody("1")); } catch (UnsupportedEncodingException e1) { Log.d(TAG, "UPLOAD: UnsupportedEncodingException"); e1.printStackTrace(); } httppost.setEntity(mpEntity); Log.d(TAG, "UPLOAD: executing request: " + httppost.getRequestLine()); Log.d(TAG, "UPLOAD: request: " + httppost.getEntity().getContentType().toString()); HttpResponse response; try { Log.d(TAG, "UPLOAD: about to execute"); response = httpclient.execute(httppost); Log.d(TAG, "UPLOAD: executed"); HttpEntity resEntity = response.getEntity(); Log.d(TAG, "UPLOAD: respose code: " + response.getStatusLine().toString()); if (resEntity != null) { Log.d(TAG, "UPLOAD: " + EntityUtils.toString(resEntity)); } if (resEntity != null) { resEntity.consumeContent(); } } catch (ClientProtocolException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } 

Recomiendo Loopj.

Lo he utilizado con éxito para cargar varios archivos a la vez, incluidos diferentes tipos de mime. Simplemente haz esto:

 File myVideo = new File("/path/to/myvideo.mp4"); File myPic = new File("/path/to/mypic.jpg"); RequestParams params = new RequestParams(); try { params.put("profile_picture", myPic); params.put("my_video", myVideo); } catch(FileNotFoundException e) {} 

Para archivos grandes o muchos, puede que tenga que boost el tiempo de espera, de lo contrario se utiliza el tiempo de espera predeterminado, que puede ser demasiado corto:

 client.setTimeout(500000) //make this the appropriate timeout in milliseconds 

Por favor, consulte estos enlaces para obtener una descripción completa de loopj y cómo usarlo, de lejos la biblioteca http async más fácil que he encontrado:

http://loopj.com/android-async-http/ http://loopj.com/android-async-http/doc/com/loopj/android/http/AsyncHttpClient.html

Elimine toda su httpclient, dependencia de httpmime y agregue esta compile 'commons-httpclient:commons-httpclient:3.1' dependencia compile 'commons-httpclient:commons-httpclient:3.1' . Esta dependencia ha incorporado MultipartRequestEntity para que pueda cargar fácilmente uno o más archivos en el servidor

 public class FileUploadUrlConnection extends AsyncTask { private Context context; private String url; private List files; public FileUploadUrlConnection(Context context, String url, List files) { this.context = context; this.url = url; this.files = files; } @Override protected String doInBackground(String... params) { HttpClient client = new HttpClient(); PostMethod post = new PostMethod(url); HttpClientParams connectionParams = new HttpClientParams(); post.setRequestHeader(// Your header goes here ); try { Part[] parts = new Part[files.size()]; for (int i=0; i 

También puede agregar el tiempo de espera de solicitud y respuesta

 client.getParams().setParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, 10000); client.getParams().setParameter(CoreConnectionPNames.SO_TIMEOUT, 10000); 

Puedo recomendar la biblioteca Ion, use 3 dependencias y puede encontrar los tres archivos jar en estos dos sitios:
https://github.com/koush/ion#jars (ion y androidasync)

https://code.google.com/p/google-gson/downloads/list (gson)

 try { Ion.with(this, "http://sofes.miximages.com/android/testfile.jpg")) .asString() .setCallback(new FutureCallback() { @Override public void onCompleted(Exception e, String result) { System.out.println(result); }}); } catch(Exception e) { // Do something about exceptions System.out.println("exception: " + e); } 

esto se ejecutará de manera asíncrona y la callback se ejecutará en el hilo de la interfaz de usuario una vez que se reciba una respuesta. Recomiendo encarecidamente que vaya a https://github.com/koush/ion para obtener más información.

Aquí hay un enfoque simple si está utilizando la biblioteca AOSP Volley .

Extienda la clase Request siguiente manera-

 public class MultipartRequest extends Request { private static final String FILE_PART_NAME = "file"; private final Response.Listener mListener; private final Map mFilePart; private final Map mStringPart; MultipartEntityBuilder entity = MultipartEntityBuilder.create(); HttpEntity httpentity; public MultipartRequest(String url, Response.ErrorListener errorListener, Response.Listener listener, Map file, Map mStringPart) { super(Method.POST, url, errorListener); mListener = listener; mFilePart = file; this.mStringPart = mStringPart; entity.setMode(HttpMultipartMode.BROWSER_COMPATIBLE); buildMultipartEntity(); } public void addStringBody(String param, String value) { mStringPart.put(param, value); } private void buildMultipartEntity() { for (Map.Entry entry : mFilePart.entrySet()) { // entity.addPart(entry.getKey(), new FileBody(entry.getValue(), ContentType.create("image/jpeg"), entry.getKey())); try { entity.addBinaryBody(entry.getKey(), Utils.toByteArray(new FileInputStream(entry.getValue())), ContentType.create("image/jpeg"), entry.getKey() + ".JPG"); } catch (FileNotFoundException e) { e.printStackTrace(); } } for (Map.Entry entry : mStringPart.entrySet()) { if (entry.getKey() != null && entry.getValue() != null) { 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 = entity.build(); httpentity.writeTo(bos); } catch (IOException e) { VolleyLog.e("IOException writing to ByteArrayOutputStream"); } return bos.toByteArray(); } @Override protected Response parseNetworkResponse(NetworkResponse response) { Log.d("Response", new String(response.data)); return Response.success(new String(response.data), getCacheEntry()); } @Override protected void deliverResponse(String response) { mListener.onResponse(response); } } 

Puede crear y agregar una solicitud como-

 Map params = new HashMap<>(); params.put("name", name.getText().toString()); params.put("email", email.getText().toString()); params.put("user_id", appPreferences.getInt( Utils.PROPERTY_USER_ID, -1) + ""); params.put("password", password.getText().toString()); params.put("imageName", pictureName); Map files = new HashMap<>(); files.put("photo", new File(Utils.LOCAL_RESOURCE_PATH + pictureName)); MultipartRequest multipartRequest = new MultipartRequest(Utils.BASE_URL + "editprofile/" + appPreferences.getInt(Utils.PROPERTY_USER_ID, -1), new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError error) { // TODO Auto-generated method stub Log.d("Error: ", error.toString()); FugaDialog.showErrorDialog(ProfileActivity.this); } }, new Response.Listener() { @Override public void onResponse(String jsonResponse) { JSONObject response = null; try { Log.d("jsonResponse: ", jsonResponse); response = new JSONObject(jsonResponse); } catch (JSONException e) { e.printStackTrace(); } try { if (response != null && response.has("statusmessage") && response.getBoolean("statusmessage")) { updateLocalRecord(); } } catch (JSONException e) { e.printStackTrace(); } FugaDialog.dismiss(); } }, files, params); RequestQueue queue = Volley.newRequestQueue(this); queue.add(multipartRequest); 

Para la posteridad, no vi a okhttp mencionado. Publicación relacionada.

Básicamente construyes el cuerpo usando un MultipartBody.Builder, y luego publicas esto en una solicitud.

Ejemplo en kotlin:

  val body = MultipartBody.Builder() .setType(MultipartBody.FORM) .addFormDataPart( "file", file.getName(), RequestBody.create(MediaType.parse("image/png"), file) ) .addFormDataPart("timestamp", Date().time.toString()) .build() val request = Request.Builder() .url(url) .post(body) .build() httpClient.newCall(request).enqueue(object : okhttp3.Callback { override fun onFailure(call: Call?, e: IOException?) { ... } override fun onResponse(call: Call?, response: Response?) { ... } }) 
 public class Multipart{ private final Map headrs; private String url; private HttpURLConnection con; private OutputStream os; private String delimiter = "--"; private String boundary = "TRR" + Long.toString(System.currentTimeMillis()) + "TRR"; public Multipart (String url, Map headers) { this.url = url; this.headrs = headers; } public void connectForMultipart() throws Exception { con = (HttpURLConnection) (new URL(url)).openConnection(); con.setRequestMethod("POST"); con.setDoInput(true); con.setDoOutput(true); con.setRequestProperty("Connection", "Keep-Alive"); for (Map.Entry entry : headrs.entrySet()) { con.setRequestProperty(entry.getKey(), entry.getValue()); } con.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary); con.connect(); os = con.getOutputStream(); } public void addFormPart(String paramName, String value) throws Exception { writeParamData(paramName, value); } public void addFilePart(String paramName, String fileName, byte[] data) throws Exception { os.write((delimiter + boundary + "\r\n").getBytes()); os.write(("Content-Disposition: form-data; name=\"" + paramName + "\"; filename=\"" + fileName + "\"\r\n").getBytes()); os.write(("Content-Type: application/octet-stream\r\n").getBytes()); os.write(("Content-Transfer-Encoding: binary\r\n").getBytes()); os.write("\r\n".getBytes()); os.write(data); os.write("\r\n".getBytes()); } public void finishMultipart() throws Exception { os.write((delimiter + boundary + delimiter + "\r\n").getBytes()); } public String getResponse() throws Exception { InputStream is = con.getInputStream(); byte[] b1 = new byte[1024]; StringBuffer buffer = new StringBuffer(); while (is.read(b1) != -1) buffer.append(new String(b1)); con.disconnect(); return buffer.toString(); } private void writeParamData(String paramName, String value) throws Exception { os.write((delimiter + boundary + "\r\n").getBytes()); os.write("Content-Type: text/plain\r\n".getBytes());//;charset=utf-8 os.write(("Content-Disposition: form-data; name=\"" + paramName + "\"\r\n").getBytes()); ; os.write(("\r\n" + value + "\r\n").getBytes()); } } 

Luego llama abajo

 Multipart multipart = new Multipart(url__, map); multipart .connectForMultipart(); multipart .addFormPart(entry.getKey(), entry.getValue()); multipart .addFilePart(KeyName, "FileName", imagedata); multipart .finishMultipart();