2011-01-18 10 views
12

Tengo un cliente de Android que se comunica con el servidor a través de extremos REST-ful y JSON. Debido a esto, tengo la necesidad de recuperar la respuesta completa del servidor antes de convertirla en Hash. Tengo este código en lugar de hacer eso (que se encuentra en algún lugar de Internet):Android: Convertir flujo a cadena sin quedarse sin memoria

private static String convertStreamToString(InputStream is) { 

    BufferedReader reader = new BufferedReader(new InputStreamReader(is)); 
    StringBuilder sb = new StringBuilder(); 

    String line = null; 
    try { 
     while ((line = reader.readLine()) != null) { 
      sb.append(line + "\n"); 
     } 
    } catch (IOException e) { 
     e.printStackTrace(); 
    } finally { 
     try { 
      is.close(); 
     } catch (IOException e) { 
      e.printStackTrace(); 
     } 
    } 
    return sb.toString(); 
} 

El código funciona en su mayor parte, sin embargo estoy viendo informes de accidentes en el campo de los clientes con una excepción OutOfMemory en el línea:

while ((line = reader.readLine()) != null) { 

el seguimiento de pila completo es:

java.lang.RuntimeException: An error occured while executing doInBackground() 
    at android.os.AsyncTask$3.done(AsyncTask.java:200) 
    at java.util.concurrent.FutureTask$Sync.innerSetException(FutureTask.java:273) 
    at java.util.concurrent.FutureTask.setException(FutureTask.java:124) 
    at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:307) 
    at java.util.concurrent.FutureTask.run(FutureTask.java:137) 
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1068) 
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:561) 
    at java.lang.Thread.run(Thread.java:1102) 
Caused by: java.lang.OutOfMemoryError 
    at java.lang.String.(String.java:468) 
    at java.lang.AbstractStringBuilder.toString(AbstractStringBuilder.java:659) 
    at java.lang.StringBuilder.toString(StringBuilder.java:664) 
    at java.io.BufferedReader.readLine(BufferedReader.java:448) 
    at com.appspot.myapp.util.RestClient.convertStreamToString(RestClient.java:303) 
    at com.appspot.myapp.util.RestClient.executeRequest(RestClient.java:281) 
    at com.appspot.myapp.util.RestClient.Execute(RestClient.java:178) 
    at com.appspot.myapp.$LoadProfilesTask.doInBackground(GridViewActivity.java:1178) 
    at com.appspot.myapp.$LoadProfilesTask.doInBackground(GridViewActivity.java:1) 
    at android.os.AsyncTask$2.call(AsyncTask.java:185) 
    at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:305) 
    ... 4 more 

Mi pregunta: ¿hay alguna manera de resolver este problema, aparte de enviar fragmentos más pequeños de datos desde el servidor?

Gracias!

+2

no veo la necesidad de tener toda la respuesta en la memoria a la vez. ¿Podrías profundizar en eso? –

+1

Estaba usando la clase org.json.JSONObject, que viene de serie con Android, y carece de una API de transmisión. Pero, resulta que Jackson tiene una API de transmisión que investigaré ... – esilver

Respuesta

3

En general, la respuesta es no, pero sin duda puede ajustar las condiciones que hacen que se quede sin memoria. En particular, si envía una longitud de cadena antes de la secuencia, podrá crear un StringBuilder con el tamaño de matriz correcto dentro de ella. Las matrices no se pueden cambiar de tamaño después de la creación, por lo que si se agota la capacidad de matriz en StringBuilder, la implementación tiene que asignar una nueva matriz (generalmente dos veces el tamaño para evitar demasiados cambios de tamaño) y luego copiar los contenidos de la matriz anterior. Considere el flujo de tamaño X, para cambiar el tamaño de StringBuilder que acaba de ser una capacidad X-1, necesita casi X * 3 cantidad de memoria. El tamaño de StringBuilder tal que se evitan los tamaños le permitirá exprimir grandes flujos en la memoria.

Otra cosa que puede querer hacer es ajustar la cantidad de memoria disponible para el proceso de su servidor. Use un interruptor como -Xmx1024m cuando inicie el proceso del servidor.

Por supuesto, sería mucho mejor revisar su algoritmo para no requerir que toda la secuencia se mantenga en la memoria. Le permitirá manejar más clientes con la misma cantidad de hardware.

+0

Sí, creo que tu "no" "la respuesta es la más correcta aquí ... intentaré preasignar un StringBuffer inicial más grande, y voy a analizar el uso de un analizador de transmisión en tiempo real, Jackson. – esilver

+0

@ Konstantin cómo usar un conmutador como -Xmx1024m al iniciar el proceso del servidor? – flexdroid

1

Existen diferentes formas de abordar este tipo de problemas. Una forma sería usar una función hash que no requiera que toda la secuencia esté en la memoria, es decir, que le proporcione un carácter o bloque de caracteres a la vez. Otra es reducir el tamaño de la respuesta.

Si no puede hacer eso y necesita toda la secuencia, entonces evitaría usar readLine() y simplemente llamaría a read() en la corriente de entrada almacenada y anexaría el carácter que obtiene de la lectura al generador de cadenas. Esto reducirá la cantidad de cadenas que está creando y descartando de manera espectacular. (Una simple optimización para el código anterior es sacar la nueva línea en la llamada a append(); también está creando otra cadena allí innecesariamente). Además, si tiene alguna idea de cuánto tiempo será la cadena resultante, también puede establecer la capacidad inicial del generador de cadenas en la construcción para que sepa de inmediato si se quedará sin memoria.

Una vez que hayas logrado ir más allá tienes que empezar a dividir la cadena en bloques que almacenas en el sistema de archivos ... se complica bastante rápido.

+0

Voy a analizar un analizador de transmisión ahora - Jackson - quizás eso ayude ... – esilver

2

Android tiene restricciones sobre la cantidad máxima de memoria que puede asignar a su aplicación. Podría considerar leer la secuencia sobre la marcha y no guardar todo en una cadena si la respuesta es muy grande. Pero debes considerar seguir las mejores prácticas.

Debe almacenar los datos en una base de datos sqlite o en un archivo normal.No es una buena práctica hacer lo que está haciendo, ya que el usuario puede presionar el botón de inicio o recibir una llamada cuando está en el medio de guardar la respuesta. Es mejor usar una base de datos para que pueda regresar al estado donde fue interrumpido. Entonces tampoco tienes que preocuparte por quedarte sin memoria.

¿Has visto esta charla sobre las mejores prácticas para comunicarse con los servicios REST de Android? http://www.youtube.com/watch?v=xHXn3Kg2IQE?8m50s (8:50 y 11:20). Muy recomendado para aclarar las mejores prácticas y por qué no se deben recuperar los datos REST sin utilizar una base de datos.

En resumen, considere guardar en una base de datos sqlite o en un archivo. Si los datos son muy grandes, quizás podría considerar comprimirlos antes de almacenarlos.

+1

Gracias por el enlace a la charla: lo veré. Mi problema no es realmente guardar datos, y en realidad no estoy analizando tantos datos REST a la vez (20kb en mi vista más grande). Almacenar en una base de datos SQLite (lo que hago) solo será posible después de que todo haya sido analizado, que es donde ocurrió OutOfMemoryError. – esilver

1

Tal vez este código ayuda a evitar el uso de StringBuilder y errores de falta de memoria:

private String convertStreamToString(InputStream is) { 
    ByteArrayOutputStream oas = new ByteArrayOutputStream(); 
    copyStream(is, oas); 
    String t = oas.toString(); 
    try { 
     oas.close(); 
     oas = null; 
    } catch (IOException e) { 
     // TODO Auto-generated catch block 
     e.printStackTrace(); 
    } 
    return t; 
} 

private void copyStream(InputStream is, OutputStream os) 
{ 
    final int buffer_size = 1024; 
    try 
    { 
     byte[] bytes=new byte[buffer_size]; 
     for(;;) 
     { 
      int count=is.read(bytes, 0, buffer_size); 
      if(count==-1) 
       break; 
      os.write(bytes, 0, count); 
     } 
    } 
    catch(Exception ex){} 
} 
0

has necesitado el construido en el método para convertir una corriente a una cadena? Es parte de la biblioteca de Apache Commons (org.apache.commons.io.IOUtils).

A continuación, el código sería esta una línea:

Cadena Total = IOUtils.toString (flujoEntrada);

La documentación de que se puede encontrar aquí: http://commons.apache.org/io/api-1.4/org/apache/commons/io/IOUtils.html#toString%28java.io.InputStream%29

La biblioteca Apache Commons IO se puede descargar desde aquí: http://commons.apache.org/io/download_io.cgi

Cuestiones relacionadas