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.
- Accediendo simultáneamente a diferentes miembros del mismo objeto en Java
- Diferencias de rendimiento entre SQLite en Android e iOS
- Vista de lista dinámica - Patrón de diseño
- Simula la memoria baja en el dispositivo real de Android
- Está llamando libgdx SpriteBatch método de inicio y fin varias veces caro?
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!
- Android: rendimiento de gson
- Ionic: transiciones lentas en la aplicación android instalada
- ¿Cómo puedo restar estas listas más rápido?
- Reaccionar transiciones androides nativas son muy lentas
- Gradle construye increíblemente lento
- ¿Es un RelativeLayout más caro que un LinearLayout?
- Eficacia de la consulta con Parse que utiliza una tienda local en Android
- Rendimiento JPG vs WebP en Android
¿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.
- ¿El campo de dirección almacenará varios números de teléfono cuando envío un sms a varios números de teléfono al mismo tiempo?
- Llamadas de método de burla usando power mockito – org.powermock.api.mockito.ClassNotPreparedException