2010-11-05 16 views
17

En mi aplicación, cargo un par de imágenes de archivos JPEG y PNG. Cuando coloco todos los archivos en el directorio de activos y la carga de esta manera, todo está bien:excepción OutOfMemory al cargar mapa de bits desde el almacenamiento externo

InputStream stream = getAssets().open(path); 
Bitmap bitmap = BitmapFactory.decodeStream(stream, null, null); 
stream.close(); 
return new BitmapDrawable(bitmap); 

Pero cuando intento cargar exactamente las mismas imágenes desde la tarjeta SD, me sale una excepción OutOfMemory!

InputStream stream = new FileInputStream("/mnt/sdcard/mydata/" + path); 
Bitmap bitmap = BitmapFactory.decodeStream(stream, null, null); 
stream.close(); 
return new BitmapDrawable(bitmap); 

Esto es lo que me pasa en el registro:

11-05 00:53:31.003: ERROR/dalvikvm-heap(13183): 827200-byte external allocation too large for this process. 
11-05 00:53:31.003: ERROR/GraphicsJNI(13183): VM won't let us allocate 827200 bytes 
... 
11-05 00:53:31.053: ERROR/AndroidRuntime(13183): Caused by: java.lang.OutOfMemoryError: bitmap size exceeds VM budget 
11-05 00:53:31.053: ERROR/AndroidRuntime(13183):  at android.graphics.BitmapFactory.nativeDecodeStream(Native Method) 
... 

Por qué puede ocurrir esto?

ACTUALIZACIÓN: Intenté ambos en un dispositivo real: parece que no puedo cargar más de 12 MB de mapas de bits en lo que se denomina "memoria externa" (esto no es una tarjeta sd).

+0

¿En qué condiciones están probando que el código anterior? ¿En emulador o dispositivo real conectando USB? Lo más probable es que su modo USB esté configurado en modo Disco que bloquea la tarjeta SD. – xandy

+0

Ejecuto este código en el emulador. – Fixpoint

+0

¿Cuál es el tamaño de los archivos jpg/png? – Fedor

Respuesta

4

Probablemente no haya nada de malo en el uso de su API, supongo que todo lo que podemos hacer es inferir que usar AssetManager implica menos asignación de montón detrás de las escenas que abrir un archivo aleatorio desde la tarjeta SD.

800KB es una asignación seria en el libro de cualquiera ... esto sin duda será para los píxeles de la imagen descomprimida. Dado que conoce el tamaño de la imagen, ¿qué profundidad tiene? Si es 32bpp, intente reemplazarlo usando inPreferredConfig.

+0

Por lo tanto, parece que AssetManager en * emulator only * puede evitar el límite total de tamaño de bitmap cargado. :( – Fixpoint

5
  • Al hacer mucho con bitmaps, no depure la aplicación, simplemente ejecútela. El depurador dejará pérdidas de memoria.
  • Los mapas de bits son muy caros. De ser posible, aligerarlos en la carga creando BitmapFactory.Options y configurando inSampleSize en> 1.

EDITAR: También, asegúrese de revisar su aplicación para detectar fugas de memoria. Filtrar un mapa de bits (tener static mapas de bits es una forma excelente de hacerlo) agotará rápidamente su memoria disponible.

+0

No estoy depurando esta aplicación, simplemente ejecutándola. El resto del código es 100% mismo, entonces ¿cómo podría ser que los mapas de bits cargados desde un lugar fugas mientras que otros no? En cuanto a la escala, me gustaría utilizar imágenes de alta calidad (debe haber suficiente memoria para cargarlas, ya que se cargan sin errores de los activos). – Fixpoint

1

En lugar de cargarlo directamente desde la tarjeta SD, ¿por qué no mover la imagen al caché en el almacenamiento interno del teléfono usando getCacheDir() o usar un directorio temporal para almacenar las imágenes?

Consulte this, this en uso de memoria externa. Además, this article puede ser relevante para usted.

8

Probé todos los enfoques mencionados here & a otros recursos, pero llegué a la conclusión de que el establecimiento de referencia de ImageView a nula va a resolver el problema:

public Bitmap getimage(String path ,ImageView iv) 
    { 
    //iv is passed to set it null to remove it from external memory 
    iv=null; 
    InputStream stream = new FileInputStream("/mnt/sdcard/mydata/" + path); 
    Bitmap bitmap = BitmapFactory.decodeStream(stream, null, null); 
    stream.close(); 
    stream=null; 
    return bitmap; 
    } 

& ya está!

Nota: Aunque puede resolver el problema anterior, le sugiero que compruebe la carga de la imagen optimizada Tom van Zummeren.

Y también marque SoftReference: Se garantiza que todas las SoftReferences que apuntan a objetos de alcance bajo se borren antes de que la VM arroje un OutOfMemoryError.

0

Prueba esto de otra manera ...

Bitmap bmpOrignal = BitmapFactory.decodeFile("/sdcard/mydata/" + path"); 
+0

Probado, mismo resultado que con InputStream: OutOfMemory. – Fixpoint

0

No debe depender del GC para reciclar la memoria de mapa de bits. Debe reciclar claramente el mapa de bits cuando no sea necesario.

Véase el método de mapa de bits:

vacío de reciclaje() liberar la memoria asociada con píxeles de este mapa de bits, y marca el mapa de bits como "muerto", lo que significa que una excepción si getPixels() o setPixels () se llama y no extraerá nada.

+0

No quiero reciclar todos esos mapas de bits, los estoy usando. – Fixpoint

+0

Si un mapa de bits se oculta al usuario, será mejor que los recicle. Si se vuelven a mostrar , los carga nuevamente.La memoria del teléfono es limitada. – imcaptor

+0

Al intentar cargar un mapa de bits que ha sido reciclado emitirá una excepción diciendo que no puede cargar un mapa de bits reciclado ... – 66CLSjY

3

Este es un problema bastante común que todos nosotros enfrentamos al cargar imágenes desde la tarjeta SD.

La solución que encontré fue usar inJustDecodeBounds primero al cargar la imagen usando decodeFileDescriptor. Eso en realidad no decodificaría la imagen, pero daría el tamaño de la imagen. Ahora puedo escalarlo apropiadamente (usando las opciones) para cambiar el tamaño de la imagen para el área de visualización. Es necesario porque la memoria baja en el teléfono puede ser fácilmente asumida por su imagen de 5MP. Esta creo que es la solución más elegante.

+0

Estaba contemplándolo, pero eso también significa que tendría para hacer 2 solicitudes para una imagen – Bostone

+0

Sí, pero no he enfrentado ninguna desaceleración debido a dos llamadas posiblemente porque el inJustDecodeBounds no hace mucho trabajo – the100rabh

2

Hay dos cuestiones aquí ....

  • memoria de mapa de bits no está en el montón VM sino más bien en el montón nativa - ver BitmapFactory OOM driving me nuts
  • La recolección de basura para el montón nativa es más perezoso que la VM montón - por lo que necesita ser bastante agresiva de hacer bitmap.recycle y mapa de bits = null cada vez que pasan por onPause de una actividad o OnDestroy
0

Uno de los errores más comunes que he encontrado Aplicaciones Android en desarrollo es la “ java.lang.OutOfMemoryErro r: Error de tamaño de mapa de bits excede el presupuesto de VM ". Encontré este error frecuentemente en actividades que usan muchos mapas de bits después de cambiar de orientación: la actividad se destruye, se crea nuevamente y los diseños se "inflan" desde el XML consumiendo la memoria VM disponible para bitmaps.

Los mapas de bits en el diseño de actividad anterior no son desasignados correctamente por el recolector de basura porque tienen referencias cruzadas a su actividad. Después de muchos experimentos, encontré una solución bastante buena para este problema.

En primer lugar, establece el atributo “id” en la vista padre de su diseño XML:

<?xml version="1.0" encoding="utf-8"?> 
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" 
    android:layout_width="fill_parent" 
    android:layout_height="fill_parent" 
    android:id="@+id/RootView" 
    > 
    ... 

Luego, en el método OnDestroy() de su actividad, llame a la unbindDrawables() método de pasar una refence a la vista padre y luego hacer un System.gc()

@Override 
    protected void onDestroy() { 
    super.onDestroy(); 

    unbindDrawables(findViewById(R.id.RootView)); 
    System.gc(); 
    } 

    private void unbindDrawables(View view) { 
     if (view.getBackground() != null) { 
     view.getBackground().setCallback(null); 
     } 
     if (view instanceof ViewGroup) { 
      for (int i = 0; i < ((ViewGroup) view).getChildCount(); i++) { 
      unbindDrawables(((ViewGroup) view).getChildAt(i)); 
      } 
     ((ViewGroup) view).removeAllViews(); 
     } 
    } 

Este unbindDrawables() método explora el árbol de vista de forma recursiva y:

  1. Elimina las devoluciones de llamada de todos los antecedentes dibujables
  2. Elimina del niño en cada ViewGroup
0

Usar el código abajo y nunca se tiene el siguiente error: java.lang.OutOfMemoryError: tamaño de mapa de bits supera el presupuesto VM

   BitmapFactory.Options bounds = new BitmapFactory.Options(); 

       bounds.inSampleSize = 4; 

       myBitmap = BitmapFactory.decodeFile(imgFile.getAbsolutePath(), bounds); 

       picturesView.setImageBitmap(myBitmap); 
4
The best solution i found and edited according to my need 

public static Bitmap getImageBitmap(String path) throws IOException{ 
     // Allocate files and objects outside of timingoops    
     File file = new File(thumbpath);   
     RandomAccessFile in = new RandomAccessFile(file, "rws"); 
     final FileChannel channel = in.getChannel(); 
     final int fileSize = (int)channel.size(); 
     final byte[] testBytes = new byte[fileSize]; 
     final ByteBuffer buff = ByteBuffer.allocate(fileSize); 
     final byte[] buffArray = buff.array(); 
     @SuppressWarnings("unused") 
     final int buffBase = buff.arrayOffset(); 

     // Read from channel into buffer, and batch read from buffer to byte array; 
     long time1 = System.currentTimeMillis(); 
     channel.position(0); 
     channel.read(buff); 
     buff.flip(); 
     buff.get(testBytes); 
     long time1 = System.currentTimeMillis(); 
     Bitmap bmp = Bitmap_process(buffArray); 
     long time2 = System.currentTimeMillis();   
     System.out.println("Time taken to load: " + (time2 - time1) + "ms"); 

     return bmp; 
    } 

    public static Bitmap Bitmap_process(byte[] buffArray){ 
     BitmapFactory.Options options = new BitmapFactory.Options(); 

     options.inDither=false;      //Disable Dithering mode 
     options.inPurgeable=true;     //Tell to gc that whether it needs free memory, the Bitmap can be cleared 
     options.inInputShareable=true;    //Which kind of reference will be used to recover the Bitmap data after being clear, when it will be used in the future 
     options.inTempStorage=new byte[32 * 1024]; //Allocate some temporal memory for decoding 

     options.inSampleSize=1; 

     Bitmap imageBitmap = BitmapFactory.decodeByteArray(buffArray, 0, buffArray.length, options); 
     return imageBitmap; 
    } 
1

Gracias a todos los hilos, he encontrado una solución que funciona para mí en un dispositivo real. Los trucos son todo acerca del uso

BitmapFactory.Options opts=new BitmapFactory.Options(); 
opts.inSampleSize=(int)(target_size/bitmap_size); //if original bitmap is bigger 

Pero para mí esto no era suficiente. Mi imagen original (tomada de la aplicación de la cámara) era 3264x2448. La proporción correcta para mí fue 3, ya que quería una imagen VGA simple de 1024x768.

Pero el ajuste inSampleSize a 3 no fue suficiente: sigue habiendo una excepción de memoria. Así que al final opté por un enfoque iterativo: comienzo desde el tamaño correcto calculado, y lo aumento hasta que deje de tener una excepción OOM. para mí fue en muestra de 4.

// Decode with inSampleSize 
BitmapFactory.Options o2 = new BitmapFactory.Options(); 
// o2.inSampleSize = scale; 
float trueScale = o.outWidth/1024; 
o2.inPurgeable = true; 
o2.inDither = false; 
Bitmap b = null; 
do { 
    o2.inSampleSize = (int) trueScale; 
    Log.d(TAG, "Scale is " + trueScale); 
try { 
    b = BitmapFactory.decodeStream(new FileInputStream(f), null, o2); 
    } catch (OutOfMemoryError e) { 
     Log.e(TAG,"Error decoding image at sampling "+trueScale+", resampling.."+e); 
     System.gc(); 
    try { 
     Thread.sleep(50); 
    } catch (InterruptedException e1) { 
     e1.printStackTrace(); 
    } 
} 
    trueScale += 1; 
} while (b==null && trueScale < 10); 
return b; 
0

Permite inSampleSize cambiar el tamaño de la imagen lectura final. getLength() de AssetFileDescriptor permite obtener el tamaño del archivo.

Puede variar de acuerdo a inSampleSize getLength() para evitar OutOfMemory así:

private final int MAX_SIZE = 500000; 

public Bitmap readBitmap(Uri selectedImage) 
{ 
    Bitmap bm = null; 
    AssetFileDescriptor fileDescriptor = null; 
    try 
    { 
     fileDescriptor = this.getContentResolver().openAssetFileDescriptor(selectedImage,"r"); 
     long size = fileDescriptor.getLength(); 
     BitmapFactory.Options options = new BitmapFactory.Options(); 
     options.inSampleSize = (int) (size/MAX_SIZE); 
     bm = BitmapFactory.decodeFileDescriptor(fileDescriptor.getFileDescriptor(), null, options); 
    } 
    catch (Exception e) 
    { 
     e.printStackTrace(); 
    } 
    finally 
    { 
     try { 
      if(fileDescriptor != null) fileDescriptor.close(); 
     } catch (IOException e) {} 
    } 
    return bm; 
} 
Cuestiones relacionadas