Android: Carga de una foto en Cloudinary con devolución de llamada de progreso en HttpURLConnection

Estoy tratando de modificar la biblioteca de código abierto de cloud, para que pueda escuchar el progreso de la carga de mi foto. La clase de biblioteca contiene una clase de java de MultipartUtility que modifiqué para escuchar el progreso de la subida.

El código original antes de las modificaciones se puede encontrar en github: https://github.com/cloudinary/cloudinary_java/blob/master/cloudinary-android/src/main/java/com/cloudinary/android/MultipartUtility.java

Lo he literalmente modificado para que se parezca al código de otro servicio en la nube CloudFS que soporta el progreso al cargar archivos / imágenes, etc:

Https://github.com/bitcasa/CloudFS-Android/blob/master/app/src/main/java/com/bitcasa/cloudfs/api/MultipartUpload.java

package com.cloudinary.android; import com.cloudinary.Cloudinary; import java.io.*; import java.net.HttpURLConnection; import java.net.URL; import java.util.Map; /** * This utility class provides an abstraction layer for sending multipart HTTP * POST requests to a web server. * * @author www.codejava.net * @author Cloudinary */ public class MultipartUtility { private final String boundary; private static final String LINE_FEED = "\r\n"; private static final String APPLICATION_OCTET_STREAM = "application/octet-stream"; private HttpURLConnection httpConn; private String charset; private OutputStream outputStream; private PrintWriter writer; UploadingCallback uploadingCallback; public final static String USER_AGENT = "CloudinaryAndroid/" + Cloudinary.VERSION; Long filesize; public void setUploadingCallback(UploadingCallback uploadingCallback) { this.uploadingCallback = uploadingCallback; } /** * 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, String boundary, Map<String, String> headers, Long filesize) throws IOException { this.charset = charset; this.boundary = boundary; this.filesize = filesize; URL url = new URL(requestURL); httpConn = (HttpURLConnection) url.openConnection(); httpConn.setDoOutput(true); // indicates POST method httpConn.setDoInput(true); httpConn.setFixedLengthStreamingMode(filesize); //added this in if (headers != null) { for (Map.Entry<String, String> header : headers.entrySet()) { httpConn.setRequestProperty(header.getKey(), header.getValue()); } } httpConn.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary); httpConn.setRequestProperty("User-Agent", USER_AGENT); outputStream = httpConn.getOutputStream(); writer = new PrintWriter(new OutputStreamWriter(outputStream, charset), true); } public MultipartUtility(String requestURL, String charset, String boundary) throws IOException { this(requestURL, charset, boundary, null, 0L); } /** * 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 {@code <input type="file" name="..." />} * @param uploadFile a File to be uploaded * @throws IOException */ public void addFilePart(String fieldName, File uploadFile, String fileName) throws IOException { if (fileName == null) fileName = uploadFile.getName(); FileInputStream inputStream = new FileInputStream(uploadFile); addFilePart(fieldName, inputStream, fileName); } public void addFilePart(String fieldName, File uploadFile) throws IOException { addFilePart(fieldName, uploadFile, "file"); } public void addFilePart(String fieldName, InputStream inputStream, String fileName) throws IOException { if (fileName == null) fileName = "file"; writer.append("--" + boundary).append(LINE_FEED); writer.append("Content-Disposition: form-data; name=\"" + fieldName + "\"; filename=\"" + fileName + "\"").append(LINE_FEED); writer.append("Content-Type: ").append(APPLICATION_OCTET_STREAM).append(LINE_FEED); writer.append("Content-Transfer-Encoding: binary").append(LINE_FEED); writer.append(LINE_FEED); writer.flush(); int progress = 0; byte[] buffer = new byte[4096]; int bytesRead = -1; while ((bytesRead = inputStream.read(buffer)) != -1) { outputStream.write(buffer, 0, bytesRead); progress += bytesRead; /* int percentage = ((progress / filesize.intValue()) * 100);*/ if (uploadingCallback != null) { uploadingCallback.uploadListener(progress); } } outputStream.flush(); writer.flush(); uploadingCallback = null; inputStream.close(); writer.append(LINE_FEED); writer.flush(); } public void addFilePart(String fieldName, InputStream inputStream) throws IOException { addFilePart(fieldName, inputStream, "file"); } /** * 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 HttpURLConnection execute() throws IOException { writer.append("--" + boundary + "--").append(LINE_FEED); writer.close(); return httpConn; } } 

Los cambios que hice fueron agregar en el siguiente a la httpURLConnection como recomendado por este hilo: Cómo implementar la barra de progreso de carga de archivo en android : httpConn.setFixedLengthStreamingMode(filesize);

A continuación, creé una interfaz sencilla para escuchar el progreso de subida:

 public interface UploadingCallback { void uploadListener(int progress); } 

Y luego lo adjunto mientras la HttpURLConnection escribió la foto:

  while ((bytesRead = inputStream.read(buffer)) != -1) { outputStream.write(buffer, 0, bytesRead); progress += bytesRead; /* int percentage = ((progress / filesize.intValue()) * 100);*/ if (uploadingCallback != null) { uploadingCallback.uploadListener(progress); } } 

El código se ejecutó, pero el progreso de la subida no parece ser medido correctamente. La foto era de unos 365 kb y la subida tardó unos 10 segundos (inicié la subida a 17: 56: 55.481 y por 17: 56: 55.554 se hizo, eso es poco más de 0.7 segundos). No creo que mi conexión a Internet sea tan rápida y espero que tarde al menos 5 segundos. Tengo la sensación de que está midiendo el tiempo que tomó para escribir la foto en el búfer en lugar del tiempo que tomó para enviarlo a los servidores de cloudinary.

¿Cómo puedo obtenerlo para medir el tiempo que tarda en cargar la foto para que pueda usar los datos de mi barra de progreso?

 04-24 17:56:55.481 28306-28725/com.a upload 4096 04-24 17:56:55.486 28306-28725/com.a upload 8192 04-24 17:56:55.486 28306-28725/com.a upload 12288 04-24 17:56:55.486 28306-28725/com.a upload 16384 04-24 17:56:55.487 28306-28725/com.a upload 20480 04-24 17:56:55.487 28306-28725/com.a upload 24576 04-24 17:56:55.487 28306-28725/com.a upload 28672 04-24 17:56:55.487 28306-28725/com.a upload 32768 04-24 17:56:55.491 28306-28725/com.a upload 36864 04-24 17:56:55.492 28306-28725/com.a upload 40960 04-24 17:56:55.493 28306-28725/com.a upload 45056 04-24 17:56:55.493 28306-28725/com.a upload 49152 04-24 17:56:55.493 28306-28725/com.a upload 53248 04-24 17:56:55.493 28306-28725/com.a upload 57344 04-24 17:56:55.494 28306-28725/com.a upload 61440 04-24 17:56:55.494 28306-28725/com.a upload 65536 04-24 17:56:55.494 28306-28725/com.a upload 69632 04-24 17:56:55.494 28306-28725/com.a upload 73728 04-24 17:56:55.494 28306-28725/com.a upload 77824 04-24 17:56:55.495 28306-28725/com.a upload 81920 04-24 17:56:55.495 28306-28725/com.a upload 86016 04-24 17:56:55.495 28306-28725/com.a upload 90112 04-24 17:56:55.495 28306-28725/com.a upload 94208 04-24 17:56:55.495 28306-28725/com.a upload 98304 04-24 17:56:55.495 28306-28725/com.a upload 102400 04-24 17:56:55.495 28306-28725/com.a upload 106496 04-24 17:56:55.496 28306-28725/com.a upload 110592 04-24 17:56:55.496 28306-28725/com.a upload 114688 04-24 17:56:55.496 28306-28725/com.a upload 118784 04-24 17:56:55.497 28306-28725/com.a upload 122880 04-24 17:56:55.498 28306-28725/com.a upload 126976 04-24 17:56:55.498 28306-28725/com.a upload 131072 04-24 17:56:55.498 28306-28725/com.a upload 135168 04-24 17:56:55.498 28306-28725/com.a upload 139264 04-24 17:56:55.499 28306-28725/com.a upload 143360 04-24 17:56:55.506 28306-28725/com.a upload 147456 04-24 17:56:55.510 28306-28725/com.a upload 151552 04-24 17:56:55.510 28306-28725/com.a upload 155648 04-24 17:56:55.514 28306-28725/com.a upload 159744 04-24 17:56:55.515 28306-28725/com.a upload 163840 04-24 17:56:55.517 28306-28725/com.a upload 167936 04-24 17:56:55.517 28306-28725/com.a upload 172032 04-24 17:56:55.518 28306-28725/com.a upload 176128 04-24 17:56:55.518 28306-28725/com.a upload 180224 04-24 17:56:55.518 28306-28725/com.a upload 184320 04-24 17:56:55.519 28306-28725/com.a upload 188416 04-24 17:56:55.519 28306-28725/com.a upload 192512 04-24 17:56:55.519 28306-28725/com.a upload 196608 04-24 17:56:55.519 28306-28725/com.a upload 200704 04-24 17:56:55.520 28306-28725/com.a upload 204800 04-24 17:56:55.525 28306-28725/com.a upload 208896 04-24 17:56:55.526 28306-28725/com.a upload 212992 04-24 17:56:55.527 28306-28725/com.a upload 217088 04-24 17:56:55.530 28306-28725/com.a upload 221184 04-24 17:56:55.530 28306-28725/com.a upload 225280 04-24 17:56:55.530 28306-28725/com.a upload 229376 04-24 17:56:55.530 28306-28725/com.a upload 233472 04-24 17:56:55.530 28306-28725/com.a upload 237568 04-24 17:56:55.531 28306-28725/com.a upload 241664 04-24 17:56:55.532 28306-28725/com.a upload 245760 04-24 17:56:55.532 28306-28725/com.a upload 249856 04-24 17:56:55.532 28306-28725/com.a upload 253952 04-24 17:56:55.533 28306-28725/com.a upload 258048 04-24 17:56:55.533 28306-28725/com.a upload 262144 04-24 17:56:55.535 28306-28725/com.a upload 266240 04-24 17:56:55.540 28306-28725/com.a upload 270336 04-24 17:56:55.540 28306-28725/com.a upload 274432 04-24 17:56:55.541 28306-28725/com.a upload 278528 04-24 17:56:55.541 28306-28725/com.a upload 282624 04-24 17:56:55.543 28306-28725/com.a upload 286720 04-24 17:56:55.545 28306-28725/com.a upload 290816 04-24 17:56:55.545 28306-28725/com.a upload 294912 04-24 17:56:55.547 28306-28725/com.a upload 299008 04-24 17:56:55.547 28306-28725/com.a upload 303104 04-24 17:56:55.547 28306-28725/com.a upload 307200 04-24 17:56:55.547 28306-28725/com.a upload 311296 04-24 17:56:55.547 28306-28725/com.a upload 315392 04-24 17:56:55.548 28306-28725/com.a upload 319488 04-24 17:56:55.548 28306-28725/com.a upload 323584 04-24 17:56:55.548 28306-28725/com.a upload 327680 04-24 17:56:55.548 28306-28725/com.a upload 331776 04-24 17:56:55.549 28306-28725/com.a upload 335872 04-24 17:56:55.549 28306-28725/com.a upload 339968 04-24 17:56:55.549 28306-28725/com.a upload 344064 04-24 17:56:55.550 28306-28725/com.a upload 348160 04-24 17:56:55.550 28306-28725/com.a upload 352256 04-24 17:56:55.551 28306-28725/com.a upload 356352 04-24 17:56:55.551 28306-28725/com.a upload 360448 04-24 17:56:55.552 28306-28725/com.a upload 364544 04-24 17:56:55.554 28306-28725/com.a upload 365790 

Para probar esto por sí mismo, tendrá que crear una cuenta gratuita en el sitio web cloudinary para obtener su cloudname la cloudname para que pueda conectar su SDK de Android a sus servicios para una carga directa sin firmar de android directamente a sus servidores.

EDITAR:

Esto es lo que he probado y todavía salta de 0 a 100% en 0,7 segundos cuando la carga realmente termina en 7 segundos:

  while ((bytesRead = inputStream.read(buffer)) != -1) { outputStream.write(buffer, 0, bytesRead); progress += bytesRead; Log.d("MultiPart", "file transferred so far: " + progress); if (uploadingCallback != null) { uploadingCallback.uploadListener(progress); } Log.d("Flushing", "flush the writer"); outputStream.flush(); writer.flush(); } 

Hay un problema en el uso del método flush () y el tiempo que llama a callback de actualización ().

Como se puede ver en el código cada vez que se lee parte de la imagen, se escribe en el búfer de salida, pero eso no significa que se envía al servidor, se puede almacenar en búfer, y más tarde write'n al servidor .

Usted tiene dos opciones, o llamada outputStream.flush () después de cada outputStream.write (), pero que va a matar el rendimiento de la carga, ya que perdería los beneficios de la memoria intermedia.

O puede llamar a su updateCallback () después del outputStream.flush () al final de su método. Porque después de outputStream.flush () estás seguro de que los datos están en el servidor, y que el progreso ha terminado.

Para más información sobre el color, vea este hilo ¿Cuál es el propósito de flush () en secuencias de Java?

Este es un tiro en la oscuridad porque no he probado en un entorno Android, sin embargo, yo recomendaría probar lo siguiente.

En lugar de utilizar una longitud fija use setChunkedStreamingMode

 //httpConn.setFixedLengthStreamingMode(filesize); httpConn.setChunkedStreamingMode(4096); // or whatever size you see fit 

Haciendo esto debería activar parte de la solicitud para que se envíe cada vez que envíe 4096 bytes de datos y esencialmente vaciar el buffer interno.


También podría tratar de limpiar manualmente el búfer después de cada escritura, esto podría ralentizar la carga del archivo, especialmente si se descarga a menudo, sin embargo, probablemente solucionar su problema. Es posible que acabe jugando con tamaños de búfer para encontrar un punto dulce.

 while ((bytesRead = inputStream.read(buffer)) != -1) { outputStream.write(buffer, 0, bytesRead); progress += bytesRead; /* int percentage = ((progress / filesize.intValue()) * 100);*/ if (uploadingCallback != null) { uploadingCallback.uploadListener(progress); } // trigger the stream to write its data outputStream.flush(); } 

Con cualquiera de estos cambios probablemente desearía que el usuario elija establecer su propio tamaño de búfer en lugar de pasar el tamaño total del archivo. EG cambie su constructor a lo siguiente:

 MultipartUtility(String requestURL, String charset, String boundary, Map<String, String> headers, int chunkSize) 
  • Cómo utilizar Leak Canary
  • Cifrar y descifrar con el modo AES ECB 'BadPaddingException' en algunos casos
  • Adview (admob) con carga de datos asincrónica
  • Android / Java regex match Más signo
  • API de Youtube: busca videos reproducibles en dispositivos móviles
  • Gson convierte una matriz de objetos de datos en json - Android
  • ¿Cómo puedo abrir el calendario desde mi aplicación?
  • Cómo comparar el tiempo en java / android (dado entrada en cadenas)?
  • Establecer setChunkedStreamingMode en HttpURLConnection no puede entregar datos al servidor
  • Java.io.IOException: No se han encontrado problemas de autenticación
  • Uso de un servicio de Android para manejar una conexión de red
  • FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.