HttpUrlConnection de Android: Publicar Multipart

Estoy tratando de publicar un archivo de prueba en un servlet de resorte en despliegue en tomcat usando Android. Estoy desarrollando en Android 4.1.2, pero he verificado el mismo problema en 4.0.3.

El problema es que la carga del archivo requiere mucho tiempo (unos 70 segundos para un archivo de 4MB), también en la red local. El tiempo es equiparable usando una conexión 3g. He excluido que podría ser un problema de servidor: la ejecución de la misma llamada con curl que se tarda 1/2 segundos, y utilizando apache como backend resultados son los mismos.

El uso de HttpClient funciona bien.

Estoy usando Spring Android RestClient 1.0.1.RELEASE y, dada la versión de Android y el hecho de que no estoy reemplazando el comportamiento predeterminado, utiliza HttpUrlConnection en lugar de HttpClient para hacer solicitudes http.

También he implementado mi ClientHttpRequestFactory personalizado con el fin de manipular algunos detalles de la conexión SSL y he definido mi propia aplicación de ClientHttpRequestInterceptor con el fin de modificar el encabezado de autenticación.

También he establecido setBufferRequestBody(false) para evitar OutOfMemoryException en archivos grandes. Pero esta propiedad no tiene efectos sobre el tiempo requerido.

MyClientHttpRequestFactory :

 public class MyClientHttpRequestFactory extends SimpleClientHttpRequestFactory{ @Override protected void prepareConnection(HttpURLConnection connection, String httpMethod) throws IOException { super.prepareConnection(connection, httpMethod); connection.setConnectTimeout(240 * 1000); connection.setReadTimeout(240 * 1000); if ("post".equals(httpMethod.toLowerCase())) { setBufferRequestBody(false); }else { setBufferRequestBody(true); } } @Override protected HttpURLConnection openConnection(URL url, Proxy proxy) throws IOException { final HttpURLConnection httpUrlConnection = super.openConnection(url, proxy); if (url.getProtocol().toLowerCase().equals("https") && settings.selfSignedCert().get()) { try { ((HttpsURLConnection)httpUrlConnection).setSSLSocketFactory(getSSLSocketFactory()); ((HttpsURLConnection)httpUrlConnection).setHostnameVerifier(new NullHostnameVerifier()); } catch (Exception e) { MyLog.e(LOG_TAG, "OpenConnection", e); } } return httpUrlConnection; } 

MyClientHttpRequestInterceptor :

 public class MyClientHttpRequestInterceptor implements ClientHttpRequestInterceptor { @Override public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException { final HttpHeaders headers = request.getHeaders(); headers.setAuthorization(new HttpBasicAuthentication( settings.username().get(), settings.password().get())); if (settings.enable_gzip().get()) { headers.setAcceptEncoding(ContentCodingType.GZIP); } return execution.execute(request, body); } } 

Y aquí mi llamada de descanso:

 List<ClientHttpRequestInterceptor> interceptors = Arrays.asList((ClientHttpRequestInterceptor)myClientHttpRequestInterceptor); RestTemplate restTemplate = new RestTemplate(); restTemplate.getMessageConverters().add(new FormHttpMessageConverter()); restTemplate.getMessageConverters().add(new StringHttpMessageConverter()); restTemplate.getMessageConverters().add(new MappingJackson2HttpMessageConverter()); restTemplate.setInterceptors(interceptors); MultiValueMap<String, Object> parts = new LinkedMultiValueMap<String, Object>(); parts.add("file", new FileSystemResource("/sdcard/test/4MB_file")); HttpEntity<MultiValueMap> requestEntity = new HttpEntity<MultiValueMap>(parts); restTemplate.exchange(myUrl, HttpMethod.POST, requestEntity, Integer.class).getBody(); } 

Mirando el código fuente de Spring Android, las siguientes líneas de código que mi petición está pasando son:

 public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException { HttpURLConnection connection = openConnection(uri.toURL(), this.proxy); prepareConnection(connection, httpMethod.name()); if (this.bufferRequestBody) { return new SimpleBufferingClientHttpRequest(connection); } else { return new SimpleStreamingClientHttpRequest(connection, this.chunkSize); } } 

Debido a this.bufferRequestBody es false , return new SimpleStreamingClientHttpRequest(connection, this.chunkSize); Se ejecuta (con chunkSize = 0)

 SimpleStreamingClientHttpRequest(HttpURLConnection connection, int chunkSize) { this.connection = connection; this.chunkSize = chunkSize; // Bugs with reusing connections in Android versions older than Froyo (2.2) if (olderThanFroyo) { System.setProperty("http.keepAlive", "false"); } } 

y entonces:

 ClientHttpRequest delegate = requestFactory.createRequest(request.getURI(), request.getMethod()); delegate.getHeaders().putAll(request.getHeaders()); if (body.length > 0) { FileCopyUtils.copy(body, delegate.getBody()); } return delegate.execute(); 

De aquí es todo el subsistema de Android, creo.

He descargado el tráfico tcp y lo analizo:

 POST /urlWherePost HTTP/1.1 Content-Type: multipart/form-data;boundary=nKwsP85ZyyzSDuAqozCTuZOSxwF1jLAtd0FECUPF Authorization: Basic xxxxxxxxxxxxxxxxxxxxxxxxxx= Accept-Encoding: gzip User-Agent: Dalvik/1.6.0 (Linux; U; Android 4.1.2; sdk Build/MASTER) Host: 192.168.168.225:8080 Connection: Keep-Alive Content-Length: 4096225 --nKwsP85ZyyzSDuAqozCTuZOSxwF1jLAtd0FECUPF Content-Disposition: form-data; name="file"; filename="4MB_file" Content-Type: application/octet-stream Content-Length: 4096000 

He intentado volver a crear solicitud similar con curl:

 curl --verbose -H "Connection: Keep-Alive" -H "Content-Type: multipart/form-data" -H "Accept-Encoding: gzip" -H "Content-Disposition: form-data; name=\"file\"; filename=\"4MB_file\"" -H "Content-Type: application/octet-stream" --user xxx:xxx -X POST --form file=@4MB_file http://192.168.168.225:8080/urlWherePost 

Pero con curl el poste es aceptable.

Publicar datos json no es un problema (tal vez pequeño tamaño de cuerpo). Pero cuando intento enviar archivos "grandes", el tiempo aumenta.

Buscando en shell DDMS, en Estadísticas de red También he encontrado que el rendimiento de la red nunca supera los 250 kb en TX. Parece que hay un cuello de botella, pero ¿cómo investigarlo? ¿Dónde puedo mirar, qué parámetro puedo cambiar?

Gracias por cualquier sugerencia!

¿Ha intentado usar el método MultipartEntity? Tuve el mismo problema al descargar cantidades grandes de datos de JSON del servidor, pero cambié a este método y cogí todos los datos que el servidor me proporcionó.

 HttpClient httpclient = new DefaultHttpClient(); HttpPost httppost = new HttpPost("http://myurl.com"); try { MultipartEntity entity = new MultipartEntity(); entity.addPart("type", new StringBody("json")); entity.addPart("data", new JSONObject(data)); httppost.setEntity(entity); HttpResponse response = httpclient.execute(httppost); } catch (ClientProtocolException e) { } catch (IOException e) { } catch (JSONException e){ } 

Para cargar archivos grandes, puede utilizar esta biblioteca android-async-http

Para simplificar y usar, recomiendo esta lib https://github.com/koush/ion . Lo uso en mi proyecto y funciona perfectamente.

  • Formato de audio apropiado para efectos de sonido en teléfonos Android con respecto a la eficiencia
  • ¿Qué es "pools de asignación para objetos temporales"?
  • Obtener la intensidad de la señal de WIFI y datos móviles
  • Procesamiento lento de lotes de modelos en libGDX
  • Hilo de fondo que bloquea la interfaz de usuario durante varios segundos
  • Obtención de velocidad mediante el API de ubicación de Google Play
  • Vista de reciclador - cambia el tamaño de la vista de elemento mientras se desplaza (para efecto de carrusel)
  • Cómo configurar el color de fondo para Textview de forma programática para otro archivo xml?
  • ¿Por qué el perro String.format de android es lento?
  • Consumo mínimo de batería para la supervisión del umbral del micrófono en Android
  • Desempeño de Android: Strings vs. Enums vs. Static Final Ints
  • FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.