Extraño problema de memoria al cargar una imagen en un objeto Bitmap

Tengo una vista de lista con un par de botones de imagen en cada fila. Cuando hace clic en la fila de la lista, inicia una nueva actividad. He tenido que crear mis propias tabs debido a un problema con el diseño de la cámara. La actividad que se lanza para el resultado es un mapa. Si hago clic en mi botón para abrir la vista previa de la imagen (cargar una imagen fuera de la tarjeta SD), la aplicación regresa de la actividad a la actividad de la lista de listas al controlador de resultados para reiniciar mi nueva actividad, que no es más que un widget de imagen.

La vista previa de la imagen en la vista de lista se está haciendo con el cursor y ListAdapter . Esto lo hace bastante simple, pero no estoy seguro de cómo puedo poner una imagen redimensionada (es decir, un tamaño de bit más pequeño que no sea el píxel para el botón de imagen sobre la marcha). Así que cambié el tamaño de la imagen que salió de la cámara del teléfono.

El problema es que recibo un error de falta de memoria cuando intenta retroceder y volver a iniciar la segunda actividad.

  • ¿Hay alguna manera de que pueda construir el adaptador de lista fácilmente fila por fila, donde puedo cambiar el tamaño sobre la marcha ( poco sabio )?

Esto sería preferible ya que también necesito hacer algunos cambios en las propiedades de los widgets / elementos en cada fila ya que no puedo seleccionar una fila con la pantalla táctil debido a un problema de enfoque. ( Puedo usar la bola de rodillo )

  • Sé que puedo hacer un cambio de tamaño fuera de banda y guardar mi imagen, pero eso no es realmente lo que quiero hacer, pero un código de muestra sería agradable.

Tan pronto como desactivé la imagen en la vista de lista, funcionó bien de nuevo.

FYI: Así es como lo estaba haciendo:

 String[] from = new String[] { DBHelper.KEY_BUSINESSNAME,DBHelper.KEY_ADDRESS,DBHelper.KEY_CITY,DBHelper.KEY_GPSLONG,DBHelper.KEY_GPSLAT,DBHelper.KEY_IMAGEFILENAME + ""}; int[] to = new int[] {R.id.businessname,R.id.address,R.id.city,R.id.gpslong,R.id.gpslat,R.id.imagefilename }; notes = new SimpleCursorAdapter(this, R.layout.notes_row, c, from, to); setListAdapter(notes); 

Donde R.id.imagefilename es un ButtonImage .

Aquí está mi LogCat:

 01-25 05:05:49.877: ERROR/dalvikvm-heap(3896): 6291456-byte external allocation too large for this process. 01-25 05:05:49.877: ERROR/(3896): VM wont let us allocate 6291456 bytes 01-25 05:05:49.877: ERROR/AndroidRuntime(3896): Uncaught handler: thread main exiting due to uncaught exception 01-25 05:05:49.917: ERROR/AndroidRuntime(3896): java.lang.OutOfMemoryError: bitmap size exceeds VM budget 01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.graphics.BitmapFactory.nativeDecodeStream(Native Method) 01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:304) 01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.graphics.BitmapFactory.decodeFile(BitmapFactory.java:149) 01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.graphics.BitmapFactory.decodeFile(BitmapFactory.java:174) 01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.graphics.drawable.Drawable.createFromPath(Drawable.java:729) 01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.widget.ImageView.resolveUri(ImageView.java:484) 01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.widget.ImageView.setImageURI(ImageView.java:281) 01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.widget.SimpleCursorAdapter.setViewImage(SimpleCursorAdapter.java:183) 01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.widget.SimpleCursorAdapter.bindView(SimpleCursorAdapter.java:129) 01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.widget.CursorAdapter.getView(CursorAdapter.java:150) 01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.widget.AbsListView.obtainView(AbsListView.java:1057) 01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.widget.ListView.makeAndAddView(ListView.java:1616) 01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.widget.ListView.fillSpecific(ListView.java:1177) 01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.widget.ListView.layoutChildren(ListView.java:1454) 01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.widget.AbsListView.onLayout(AbsListView.java:937) 01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.view.View.layout(View.java:5611) 01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1119) 01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.widget.LinearLayout.layoutHorizontal(LinearLayout.java:1108) 01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.widget.LinearLayout.onLayout(LinearLayout.java:922) 01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.view.View.layout(View.java:5611) 01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.widget.FrameLayout.onLayout(FrameLayout.java:294) 01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.view.View.layout(View.java:5611) 01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1119) 01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.widget.LinearLayout.layoutVertical(LinearLayout.java:999) 01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.widget.LinearLayout.onLayout(LinearLayout.java:920) 01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.view.View.layout(View.java:5611) 01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.widget.FrameLayout.onLayout(FrameLayout.java:294) 01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.view.View.layout(View.java:5611) 01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.view.ViewRoot.performTraversals(ViewRoot.java:771) 01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.view.ViewRoot.handleMessage(ViewRoot.java:1103) 01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.os.Handler.dispatchMessage(Handler.java:88) 01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.os.Looper.loop(Looper.java:123) 01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.app.ActivityThread.main(ActivityThread.java:3742) 01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at java.lang.reflect.Method.invokeNative(Native Method) 01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at java.lang.reflect.Method.invoke(Method.java:515) 01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:739) 01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:497) 01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at dalvik.system.NativeStart.main(Native Method) 01-25 05:10:01.127: ERROR/AndroidRuntime(3943): ERROR: thread attach failed 

También tengo un nuevo error al mostrar una imagen:

 01-25 22:13:18.594: DEBUG/skia(4204): xxxxxxxxxxx jpeg error 20 Improper call to JPEG library in state %d 01-25 22:13:18.604: INFO/System.out(4204): resolveUri failed on bad bitmap uri: 01-25 22:13:18.694: ERROR/dalvikvm-heap(4204): 6291456-byte external allocation too large for this process. 01-25 22:13:18.694: ERROR/(4204): VM won't let us allocate 6291456 bytes 01-25 22:13:18.694: DEBUG/skia(4204): xxxxxxxxxxxxxxxxxxxx allocPixelRef failed 

La clase de entrenamiento de Android , ” Mostrar mapas de bits de manera eficiente “, ofrece información excelente para comprender y tratar la excepción java.lang.OutOfMemoryError: bitmap size exceeds VM budget al cargar java.lang.OutOfMemoryError: bitmap size exceeds VM budget bits.


Leer las dimensiones y el tipo de bitmap

La clase BitmapFactory proporciona varios métodos de deencoding ( decodeByteArray() , decodeFile() , decodeResource() , etc.) para crear un Bitmap de Bitmap de varias fonts. Elija el método de deencoding más apropiado según su fuente de datos de imágenes. Estos métodos intentan asignar memoria para el bitmap construido y, por lo tanto, pueden resultar fácilmente en una excepción OutOfMemory . Cada tipo de método de deencoding tiene firmas adicionales que le permiten especificar opciones de deencoding a través de la clase BitmapFactory.Options . Establecer la propiedad inJustDecodeBounds en true mientras se decodifica evita la asignación de memoria, devuelve null para el objeto de bitmap pero establece outWidth , outHeight y outMimeType . Esta técnica le permite leer las dimensiones y el tipo de los datos de imagen antes de la construcción (y la asignación de memoria) del bitmap.

 BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeResource(getResources(), R.id.myimage, options); int imageHeight = options.outHeight; int imageWidth = options.outWidth; String imageType = options.outMimeType; 

Para evitar las excepciones de java.lang.OutOfMemory , verifique las dimensiones de un bitmap antes de decodificarlo, a menos que confíe plenamente en que la fuente le proporcione datos de imágenes de tamaño predecible que quepan cómodamente dentro de la memoria disponible.


Cargue una versión reducida en la memoria

Ahora que las dimensiones de la imagen son conocidas, se pueden usar para decidir si la imagen completa se debe cargar en la memoria o si se debe cargar una versión submuestreada. Aquí hay algunos factores a considerar:

  • Uso estimado de la memoria al cargar la imagen completa en la memoria.
  • La cantidad de memoria que está dispuesto a cargar para cargar esta imagen dado cualquier otro requisito de memoria de su aplicación.
  • Dimensiones del componente ImageView o UI de destino en el que se va a cargar la imagen.
  • Tamaño de pantalla y densidad del dispositivo actual.

Por ejemplo, no vale la pena cargar una imagen de 1024×768 píxeles en la memoria si finalmente se mostrará en una miniatura de 128×96 píxeles en un ImageView .

Para indicarle al decodificador que muestre una submuestra de la imagen, que cargue una versión más pequeña en la memoria, configure en inSampleSize en true en su objeto BitmapFactory.Options . Por ejemplo, una imagen con resolución 2048×1536 que se decodifica con un inSampleSize de inSampleSize de 4 produce un bitmap de aproximadamente 512×384. Cargar esto en la memoria utiliza 0.75MB en lugar de 12MB para la imagen completa (suponiendo una configuración de bitmap de ARGB_8888 ). Aquí hay un método para calcular un valor de tamaño de muestra que es una potencia de dos basada en el ancho y la altura del objective:

 public static int calculateInSampleSize( BitmapFactory.Options options, int reqWidth, int reqHeight) { // Raw height and width of image final int height = options.outHeight; final int width = options.outWidth; int inSampleSize = 1; if (height > reqHeight || width > reqWidth) { final int halfHeight = height / 2; final int halfWidth = width / 2; // Calculate the largest inSampleSize value that is a power of 2 and keeps both // height and width larger than the requested height and width. while ((halfHeight / inSampleSize) > reqHeight && (halfWidth / inSampleSize) > reqWidth) { inSampleSize *= 2; } } return inSampleSize; } 

Nota : Se calcula una potencia de dos valores porque el decodificador usa un valor final al redondear a la potencia más cercana a dos, según la documentación de inSampleSize .

Para usar este método, primero decodifique con inJustDecodeBounds establecido en true , pase las opciones a través y luego descodifique de nuevo usando el nuevo valor de inJustDecodeBounds y en inJustDecodeBounds establecido en false :

 public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId, int reqWidth, int reqHeight) { // First decode with inJustDecodeBounds=true to check dimensions final BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeResource(res, resId, options); // Calculate inSampleSize options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight); // Decode bitmap with inSampleSize set options.inJustDecodeBounds = false; return BitmapFactory.decodeResource(res, resId, options); } 

Este método facilita la carga de un bitmap de tamaño arbitrariamente grande en un ImageView que muestra una miniatura de 100×100 píxeles, como se muestra en el siguiente código de ejemplo:

 mImageView.setImageBitmap( decodeSampledBitmapFromResource(getResources(), R.id.myimage, 100, 100)); 

Puede seguir un proceso similar para decodificar mapas de bits de otras fonts, sustituyendo el método apropiado BitmapFactory.decode* según sea necesario.

Para solucionar el error de OutOfMemory, debe hacer algo como esto:

 BitmapFactory.Options options = new BitmapFactory.Options(); options.inSampleSize = 8; Bitmap preview_bitmap = BitmapFactory.decodeStream(is, null, options); 

Esta opción inSampleSize reduce el consumo de memoria.

Aquí hay un método completo. Primero lee el tamaño de la imagen sin decodificar el contenido en sí. Luego encuentra el mejor valor en el inSampleSize la inSampleSize , debe ser una potencia de 2, y finalmente la imagen se decodifica.

 // Decodes image and scales it to reduce memory consumption private Bitmap decodeFile(File f) { try { // Decode image size BitmapFactory.Options o = new BitmapFactory.Options(); o.inJustDecodeBounds = true; BitmapFactory.decodeStream(new FileInputStream(f), null, o); // The new size we want to scale to final int REQUIRED_SIZE=70; // Find the correct scale value. It should be the power of 2. int scale = 1; while(o.outWidth / scale / 2 >= REQUIRED_SIZE && o.outHeight / scale / 2 >= REQUIRED_SIZE) { scale *= 2; } // Decode with inSampleSize BitmapFactory.Options o2 = new BitmapFactory.Options(); o2.inSampleSize = scale; return BitmapFactory.decodeStream(new FileInputStream(f), null, o2); } catch (FileNotFoundException e) {} return null; } 

He hecho una pequeña mejora en el código de Fedor. Básicamente hace lo mismo, pero sin (en mi opinión) feo while loop y siempre resulta en una potencia de dos. Felicitaciones a Fedor por hacer la solución original, me quedé atrapado hasta que encontré la suya, y luego pude hacer esta 🙂

  private Bitmap decodeFile(File f){ Bitmap b = null; //Decode image size BitmapFactory.Options o = new BitmapFactory.Options(); o.inJustDecodeBounds = true; FileInputStream fis = new FileInputStream(f); BitmapFactory.decodeStream(fis, null, o); fis.close(); int scale = 1; if (o.outHeight > IMAGE_MAX_SIZE || o.outWidth > IMAGE_MAX_SIZE) { scale = (int)Math.pow(2, (int) Math.ceil(Math.log(IMAGE_MAX_SIZE / (double) Math.max(o.outHeight, o.outWidth)) / Math.log(0.5))); } //Decode with inSampleSize BitmapFactory.Options o2 = new BitmapFactory.Options(); o2.inSampleSize = scale; fis = new FileInputStream(f); b = BitmapFactory.decodeStream(fis, null, o2); fis.close(); return b; } 

Vengo de la experiencia de iOS y me sentí frustrado al descubrir un problema con algo tan básico como cargar y mostrar una imagen. Después de todo, todos los que tienen este problema están tratando de mostrar imágenes de un tamaño razonable. De todos modos, aquí están los dos cambios que arreglaron mi problema (y hicieron que mi aplicación fuera muy receptiva).

1) Cada vez que realice BitmapFactory.decodeXYZ() , asegúrese de pasar un BitmapFactory.Options con inPurgeable establecido en true (y preferiblemente con inInputShareable también configurado en true ).

2) NUNCA use Bitmap.createBitmap(width, height, Config.ARGB_8888) . ¡Quiero decir NUNCA! Nunca tuve esa cosa que no boosta el error de memoria después de algunos pases. No hay cantidad de recycle() , System.gc() , lo que sea que haya ayudado. Siempre planteó una excepción. La otra forma en que realmente funciona es tener una imagen ficticia en tus dibujos (u otro bitmap que decodificaste usando el paso 1 anterior), cambiarla a lo que quieras y luego manipular el bitmap resultante (como pasarlo a un canvas para más diversión). Entonces, lo que deberías usar en su lugar es: Bitmap.createScaledBitmap(srcBitmap, width, height, false) . Si por alguna razón usted DEBE usar el método de creación de fuerza bruta, al menos pase Config.ARGB_4444 .

Esto casi garantiza que le ahorrará horas si no días. Todo lo que se dice sobre escalar la imagen, etc., realmente no funciona (a menos que considere la posibilidad de obtener una solución de tamaño incorrecto o degradada).

Es un error conocido , no se debe a archivos grandes. Desde Android Cache los Drawables, se está quedando sin memoria después de usar algunas imágenes. Pero encontré una forma alternativa de hacerlo, omitiendo el sistema de caché predeterminado de Android.

Solución : Mueva las imágenes a la carpeta “assets” y use la siguiente función para obtener BitmapDrawable:

 public static Drawable getAssetImage(Context context, String filename) throws IOException { AssetManager assets = context.getResources().getAssets(); InputStream buffer = new BufferedInputStream((assets.open("drawable/" + filename + ".png"))); Bitmap bitmap = BitmapFactory.decodeStream(buffer); return new BitmapDrawable(context.getResources(), bitmap); } 

Tuve este mismo problema y lo resolví evitando las funciones BitmapFactory.decodeStream o decodeFile y en su lugar usé BitmapFactory.decodeFileDescriptor

decodeFileDescriptor parece que llama a diferentes métodos nativos que el decodeStream / decodeFile.

De todos modos, lo que funcionó fue esto (nótese que agregué algunas opciones como algunas anteriores, pero eso no fue lo que marcó la diferencia. Lo que es crítico es la llamada a BitmapFactory.decodeFileDescriptor en lugar de decodeStream o decodeFile ):

 private void showImage(String path) { Log.i("showImage","loading:"+path); BitmapFactory.Options bfOptions=new BitmapFactory.Options(); bfOptions.inDither=false; //Disable Dithering mode bfOptions.inPurgeable=true; //Tell to gc that whether it needs free memory, the Bitmap can be cleared bfOptions.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 bfOptions.inTempStorage=new byte[32 * 1024]; File file=new File(path); FileInputStream fs=null; try { fs = new FileInputStream(file); } catch (FileNotFoundException e) { //TODO do something intelligent e.printStackTrace(); } try { if(fs!=null) bm=BitmapFactory.decodeFileDescriptor(fs.getFD(), null, bfOptions); } catch (IOException e) { //TODO do something intelligent e.printStackTrace(); } finally{ if(fs!=null) { try { fs.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } //bm=BitmapFactory.decodeFile(path, bfOptions); This one causes error: java.lang.OutOfMemoryError: bitmap size exceeds VM budget im.setImageBitmap(bm); //bm.recycle(); bm=null; } 

Creo que hay un problema con la función nativa utilizada en decodeStream / decodeFile. He confirmado que se llama a un método nativo diferente cuando se usa decodeFileDescriptor. Además, lo que he leído es “que las imágenes (mapas de bits) no están asignadas de una forma Java estándar sino a través de llamadas nativas, las asignaciones se realizan fuera del montón virtual, ¡pero se cuentan en su contra!

Creo que la mejor manera de evitar el OutOfMemoryError es enfrentarlo y entenderlo.

Hice una aplicación para provocar intencionalmente OutOfMemoryError y supervisar el uso de la memoria.

Después de hacer muchos experimentos con esta aplicación, tengo las siguientes conclusiones:

Voy a hablar sobre las versiones de SDK antes de Honey Comb primero.

  1. El bitmap se almacena en el montón nativo, pero obtendrá basura recogida automáticamente, llamando a reciclar () es innecesario.

  2. Si {Tamaño de almacenamiento dynamic de VM} + {memoria de almacenamiento dynamic asignada}> = {Límite de tamaño de almacenamiento dynamic de VM para el dispositivo}, y está intentando crear un bitmap, se lanzará OOM.

    AVISO: VM HEAP SIZE se cuenta en lugar de VM ALLOCATED MEMORY.

  3. El tamaño del almacenamiento dynamic de VM nunca se reducirá una vez crecido, incluso si la memoria de VM asignada se reduce.

  4. Por lo tanto, debe mantener la memoria de VM máxima lo más baja posible para evitar que el tamaño del almacenamiento dynamic de VM crezca demasiado para guardar la memoria disponible para mapas de bits.

  5. Llamar manualmente a System.gc () no tiene sentido, el sistema lo llamará primero antes de intentar hacer crecer el tamaño del montón.

  6. El tamaño de Heap nativo nunca se reducirá, pero no cuenta para OOM, por lo que no debe preocuparse por ello.

Entonces, hablemos de los inicios de SDK de Honey Comb.

  1. El bitmap se almacena en el montón de VM, la memoria nativa no se cuenta para OOM.

  2. La condición para OOM es mucho más simple: {VM heap size}> = {VM heap límite de tamaño para el dispositivo}.

  3. Para que tenga más memoria disponible para crear mapas de bits con el mismo límite de tamaño de almacenamiento dynamic, es menos probable que se genere OOM.

Aquí están algunas de mis observaciones acerca de Garbage Collection y Memory Leak.

Puedes verlo tú mismo en la aplicación. Si una Actividad ejecutó una AsyncTask que aún se estaba ejecutando después de que se destruyó la Actividad, la Actividad no obtendrá basura recolectada hasta que la AsyncTask finalice.

Esto es porque AsyncTask es una instancia de una clase interna anónima, contiene una referencia de la Actividad.

Llamar a AsyncTask.cancel (verdadero) no detendrá la ejecución si la tarea está bloqueada en una operación IO en el hilo de fondo.

Las rellamadas también son clases internas anónimas, de modo que si una instancia estática en su proyecto las retiene y no las libera, la memoria se filtraría.

Si programó una tarea repetitiva o retrasada, por ejemplo, un temporizador, y no llama a cancelar () y purgar () en OnPause (), la memoria se filtró.

Recientemente he visto muchas preguntas sobre las excepciones y el almacenamiento en caché de OOM. La guía para desarrolladores tiene un artículo realmente bueno sobre esto, pero algunos tienden a fallar al implementarlo de una manera adecuada.

Debido a esto, escribí una aplicación de ejemplo que demuestra el almacenamiento en caché en un entorno Android. Esta implementación aún no ha recibido un OOM.

Mire al final de esta respuesta un enlace al código fuente.

Requisitos:

  • Android API 2.1 o superior (simplemente no pude conseguir la memoria disponible para una aplicación en la API 1.6, que es la única pieza de código que no funciona en la API 1.6)
  • Paquete de soporte de Android

Captura de pantalla

caracteristicas:

  • Retiene la caché si hay un cambio de orientación , usando un singleton
  • Use una octava parte de la memoria de la aplicación asignada a la memoria caché (modifique si lo desea)
  • Los mapas de bits grandes se escalan (puede definir los píxeles máximos que desea permitir)
  • Controla que haya una conexión a Internet disponible antes de descargar los mapas de bits
  • Asegúrate de estar creando solo una tarea por fila
  • Si arrojas el ListView lejos, simplemente no descargará los mapas de bits entre

Esto no incluye:

  • Almacenamiento en caché de disco. Esto debería ser fácil de implementar de todos modos, simplemente apunta a una tarea diferente que toma los mapas de bits del disco

Código de muestra:

Las imágenes que se están descargando son imágenes (75×75) de Flickr. Sin embargo, coloque las urls de imagen que desee que se procesen, y la aplicación la reducirá si excede el máximo. En esta aplicación, las urls están simplemente en una matriz String .

El LruCache tiene una buena manera de lidiar con bitmaps. Sin embargo, en esta aplicación puse una instancia de un LruCache dentro de otra clase de caché que creé para hacer que la aplicación sea más factible.

Las cosas críticas de loadBitmap() método loadBitmap() es el más importante):

 public Cache(int size, int maxWidth, int maxHeight) { // Into the constructor you add the maximum pixels // that you want to allow in order to not scale images. mMaxWidth = maxWidth; mMaxHeight = maxHeight; mBitmapCache = new LruCache(size) { protected int sizeOf(String key, Bitmap b) { // Assuming that one pixel contains four bytes. return b.getHeight() * b.getWidth() * 4; } }; mCurrentTasks = new ArrayList(); } /** * Gets a bitmap from cache. * If it is not in cache, this method will: * * 1: check if the bitmap url is currently being processed in the * BitmapLoaderTask and cancel if it is already in a task (a control to see * if it's inside the currentTasks list). * * 2: check if an internet connection is available and continue if so. * * 3: download the bitmap, scale the bitmap if necessary and put it into * the memory cache. * * 4: Remove the bitmap url from the currentTasks list. * * 5: Notify the ListAdapter. * * @param mainActivity - Reference to activity object, in order to * call notifyDataSetChanged() on the ListAdapter. * @param imageKey - The bitmap url (will be the key). * @param imageView - The ImageView that should get an * available bitmap or a placeholder image. * @param isScrolling - If set to true, we skip executing more tasks since * the user probably has flinged away the view. */ public void loadBitmap(MainActivity mainActivity, String imageKey, ImageView imageView, boolean isScrolling) { final Bitmap bitmap = getBitmapFromCache(imageKey); if (bitmap != null) { imageView.setImageBitmap(bitmap); } else { imageView.setImageResource(R.drawable.ic_launcher); if (!isScrolling && !mCurrentTasks.contains(imageKey) && mainActivity.internetIsAvailable()) { BitmapLoaderTask task = new BitmapLoaderTask(imageKey, mainActivity.getAdapter()); task.execute(); } } } 

No debería necesitar editar nada en el archivo Cache.java a menos que desee implementar el almacenamiento en caché de disco.

Cosas críticas de MainActivity.java:

 public void onScrollStateChanged(AbsListView view, int scrollState) { if (view.getId() == android.R.id.list) { // Set scrolling to true only if the user has flinged the // ListView away, hence we skip downloading a series // of unnecessary bitmaps that the user probably // just want to skip anyways. If we scroll slowly it // will still download bitmaps - that means // that the application won't wait for the user // to lift its finger off the screen in order to // download. if (scrollState == SCROLL_STATE_FLING) { mIsScrolling = true; } else { mIsScrolling = false; mListAdapter.notifyDataSetChanged(); } } } // Inside ListAdapter... @Override public View getView(final int position, View convertView, ViewGroup parent) { View row = convertView; final ViewHolder holder; if (row == null) { LayoutInflater inflater = getLayoutInflater(); row = inflater.inflate(R.layout.main_listview_row, parent, false); holder = new ViewHolder(row); row.setTag(holder); } else { holder = (ViewHolder) row.getTag(); } final Row rowObject = getItem(position); // Look at the loadBitmap() method description... holder.mTextView.setText(rowObject.mText); mCache.loadBitmap(MainActivity.this, rowObject.mBitmapUrl, holder.mImageView, mIsScrolling); return row; } 

getView() gets called very often. It’s normally not a good idea to download images there if we haven’t implemented a check that ensure us that we won’t start an infinite amount of threads per row. Cache.java checks whether the rowObject.mBitmapUrl already is in a task and if it is, it won’t start another. Therefore, we are most likely not exceeding the work queue restriction from the AsyncTask pool.

Descargar:

You can download the source code from https://www.dropbox.com/s/pvr9zyl811tfeem/ListViewImageCache.zip .


Last words:

I have tested this for a few weeks now, I haven’t gotten a single OOM exception yet. I have tested this on the emulator, on my Nexus One and on my Nexus S. I have tested image urls that contain images that were in HD quality. The only bottleneck is that it takes more time to download.

There is only one possible scenario where I can imagine that the OOM will appear, and that is if we download many, really big images, and before they get scaled and put into cache, will simultaneously take up more memory and cause an OOM. But that isn’t even an ideal situation anyway and it most likely won’t be possible to solve in a more feasible way.

Report errors in the comments! 🙂

I did the following to take the image and resize it on the fly. Espero que esto ayude

 Bitmap bm; bm = Bitmap.createScaledBitmap(BitmapFactory.decodeFile(filepath), 100, 100, true); mPicture = new ImageView(context); mPicture.setImageBitmap(bm); 

It seems that this is a very long running problem, with a lot of differing explanations. I took the advice of the two most common presented answers here, but neither one of these solved my problems of the VM claiming it couldn’t afford the bytes to perform the decoding part of the process. After some digging I learned that the real problem here is the decoding process taking away from the NATIVE heap.

See here: BitmapFactory OOM driving me nuts

That lead me to another discussion thread where I found a couple more solutions to this problem. One is to call System.gc(); manually after your image is displayed. But that actually makes your app use MORE memory, in an effort to reduce the native heap. The better solution as of the release of 2.0 (Donut) is to use the BitmapFactory option “inPurgeable”. So I simply added o2.inPurgeable=true; just after o2.inSampleSize=scale; .

More on that topic here: Is the limit of memory heap only 6M?

Now, having said all of this, I am a complete dunce with Java and Android too. So if you think this is a terrible way to solve this problem, you are probably right. 😉 But this has worked wonders for me, and I have found it impossible to run the VM out of heap cache now. The only drawback I can find is that you are trashing your cached drawn image. Which means if you go RIGHT back to that image, you are redrawing it each and every time. In the case of how my application works, that is not really a problem. Su experiencia puede ser diferente.

unfortunately if None of the Above works, then Add this to your Manifest file. Inside application tag

   

Use this bitmap.recycle(); This helps without any image quality issue.

I have resolved the same issue in the following manner.

 Bitmap b = null; Drawable d; ImageView i = new ImageView(mContext); try { b = Bitmap.createBitmap(320,424,Bitmap.Config.RGB_565); b.eraseColor(0xFFFFFFFF); Rect r = new Rect(0, 0,320 , 424); Canvas c = new Canvas(b); Paint p = new Paint(); p.setColor(0xFFC0C0C0); c.drawRect(r, p); d = mContext.getResources().getDrawable(mImageIds[position]); d.setBounds(r); d.draw(c); /* BitmapFactory.Options o2 = new BitmapFactory.Options(); o2.inTempStorage = new byte[128*1024]; b = BitmapFactory.decodeStream(mContext.getResources().openRawResource(mImageIds[position]), null, o2); o2.inSampleSize=16; o2.inPurgeable = true; */ } catch (Exception e) { } i.setImageBitmap(b); 

I have a much more effective solution which does not need scaling of any sort. Simply decode your bitmap only once and then cache it in a map against its name. Then simply retrieve the bitmap against the name and set it in the ImageView. There is nothing more that needs to be done.

This will work because the actual binary data of the decoded bitmap is not stored within the dalvik VM heap. It is stored externally. So every time you decode a bitmap, it allocates memory outside of VM heap which is never reclaimed by GC

To help you better appreciate this, imagine you have kept ur image in the drawable folder. You just get the image by doing a getResources().getDrwable(R.drawable.). This will NOT decode your image everytime but re-use an already decoded instance everytime you call it. So in essence it is cached.

Now since your image is in a file somewhere (or may even be coming from an external server), it is YOUR responsibility to cache the decoded bitmap instance to be reused any where it is needed.

Espero que esto ayude.

This worked for me!

 public Bitmap readAssetsBitmap(String filename) throws IOException { try { BitmapFactory.Options options = new BitmapFactory.Options(); options.inPurgeable = true; Bitmap bitmap = BitmapFactory.decodeStream(assets.open(filename), null, options); if(bitmap == null) { throw new IOException("File cannot be opened: It's value is null"); } else { return bitmap; } } catch (IOException e) { throw new IOException("File cannot be opened: " + e.getMessage()); } } 

There are two issues here….

  • Bitmap memory isn’t in the VM heap but rather in the native heap – see BitmapFactory OOM driving me nuts
  • Garbage collection for the native heap is lazier than the VM heap – so you need to be quite aggressive about doing bitmap.recycle and bitmap =null every time you go through an Activity’s onPause or onDestroy

Great answers here, but I wanted a fully usable class to address this problem.. so I did one.

Here is my BitmapHelper class that is OutOfMemoryError proof 🙂

 import java.io.File; import java.io.FileInputStream; import android.graphics.Bitmap; import android.graphics.Bitmap.Config; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Matrix; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; public class BitmapHelper { //decodes image and scales it to reduce memory consumption public static Bitmap decodeFile(File bitmapFile, int requiredWidth, int requiredHeight, boolean quickAndDirty) { try { //Decode image size BitmapFactory.Options bitmapSizeOptions = new BitmapFactory.Options(); bitmapSizeOptions.inJustDecodeBounds = true; BitmapFactory.decodeStream(new FileInputStream(bitmapFile), null, bitmapSizeOptions); // load image using inSampleSize adapted to required image size BitmapFactory.Options bitmapDecodeOptions = new BitmapFactory.Options(); bitmapDecodeOptions.inTempStorage = new byte[16 * 1024]; bitmapDecodeOptions.inSampleSize = computeInSampleSize(bitmapSizeOptions, requiredWidth, requiredHeight, false); bitmapDecodeOptions.inPurgeable = true; bitmapDecodeOptions.inDither = !quickAndDirty; bitmapDecodeOptions.inPreferredConfig = quickAndDirty ? Bitmap.Config.RGB_565 : Bitmap.Config.ARGB_8888; Bitmap decodedBitmap = BitmapFactory.decodeStream(new FileInputStream(bitmapFile), null, bitmapDecodeOptions); // scale bitmap to mathc required size (and keep aspect ratio) float srcWidth = (float) bitmapDecodeOptions.outWidth; float srcHeight = (float) bitmapDecodeOptions.outHeight; float dstWidth = (float) requiredWidth; float dstHeight = (float) requiredHeight; float srcAspectRatio = srcWidth / srcHeight; float dstAspectRatio = dstWidth / dstHeight; // recycleDecodedBitmap is used to know if we must recycle intermediary 'decodedBitmap' // (DO NOT recycle it right away: wait for end of bitmap manipulation process to avoid // java.lang.RuntimeException: Canvas: trying to use a recycled bitmap android.graphics.Bitmap@416ee7d8 // I do not excatly understand why, but this way it's OK boolean recycleDecodedBitmap = false; Bitmap scaledBitmap = decodedBitmap; if (srcAspectRatio < dstAspectRatio) { scaledBitmap = getScaledBitmap(decodedBitmap, (int) dstWidth, (int) (srcHeight * (dstWidth / srcWidth))); // will recycle recycleDecodedBitmap recycleDecodedBitmap = true; } else if (srcAspectRatio > dstAspectRatio) { scaledBitmap = getScaledBitmap(decodedBitmap, (int) (srcWidth * (dstHeight / srcHeight)), (int) dstHeight); recycleDecodedBitmap = true; } // crop image to match required image size int scaledBitmapWidth = scaledBitmap.getWidth(); int scaledBitmapHeight = scaledBitmap.getHeight(); Bitmap croppedBitmap = scaledBitmap; if (scaledBitmapWidth > requiredWidth) { int xOffset = (scaledBitmapWidth - requiredWidth) / 2; croppedBitmap = Bitmap.createBitmap(scaledBitmap, xOffset, 0, requiredWidth, requiredHeight); scaledBitmap.recycle(); } else if (scaledBitmapHeight > requiredHeight) { int yOffset = (scaledBitmapHeight - requiredHeight) / 2; croppedBitmap = Bitmap.createBitmap(scaledBitmap, 0, yOffset, requiredWidth, requiredHeight); scaledBitmap.recycle(); } if (recycleDecodedBitmap) { decodedBitmap.recycle(); } decodedBitmap = null; scaledBitmap = null; return croppedBitmap; } catch (Exception ex) { ex.printStackTrace(); } return null; } /** * compute powerOf2 or exact scale to be used as {@link BitmapFactory.Options#inSampleSize} value (for subSampling) * * @param requiredWidth * @param requiredHeight * @param powerOf2 * weither we want a power of 2 sclae or not * @return */ public static int computeInSampleSize(BitmapFactory.Options options, int dstWidth, int dstHeight, boolean powerOf2) { int inSampleSize = 1; // Raw height and width of image final int srcHeight = options.outHeight; final int srcWidth = options.outWidth; if (powerOf2) { //Find the correct scale value. It should be the power of 2. int tmpWidth = srcWidth, tmpHeight = srcHeight; while (true) { if (tmpWidth / 2 < dstWidth || tmpHeight / 2 < dstHeight) break; tmpWidth /= 2; tmpHeight /= 2; inSampleSize *= 2; } } else { // Calculate ratios of height and width to requested height and width final int heightRatio = Math.round((float) srcHeight / (float) dstHeight); final int widthRatio = Math.round((float) srcWidth / (float) dstWidth); // Choose the smallest ratio as inSampleSize value, this will guarantee // a final image with both dimensions larger than or equal to the // requested height and width. inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio; } return inSampleSize; } public static Bitmap drawableToBitmap(Drawable drawable) { if (drawable instanceof BitmapDrawable) { return ((BitmapDrawable) drawable).getBitmap(); } Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), Config.ARGB_8888); Canvas canvas = new Canvas(bitmap); drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight()); drawable.draw(canvas); return bitmap; } public static Bitmap getScaledBitmap(Bitmap bitmap, int newWidth, int newHeight) { int width = bitmap.getWidth(); int height = bitmap.getHeight(); float scaleWidth = ((float) newWidth) / width; float scaleHeight = ((float) newHeight) / height; // CREATE A MATRIX FOR THE MANIPULATION Matrix matrix = new Matrix(); // RESIZE THE BIT MAP matrix.postScale(scaleWidth, scaleHeight); // RECREATE THE NEW BITMAP Bitmap resizedBitmap = Bitmap.createBitmap(bitmap, 0, 0, width, height, matrix, false); return resizedBitmap; } } 

None of the answers above worked for me, but I did come up with a horribly ugly workaround that solved the problem. I added a very small, 1×1 pixel image to my project as a resource, and loaded it into my ImageView before calling into garbage collection. I think it might be that the ImageView was not releasing the Bitmap, so GC never picked it up. It’s ugly, but it seems to be working for now.

 if (bitmap != null) { bitmap.recycle(); bitmap = null; } if (imageView != null) { imageView.setImageResource(R.drawable.tiny); // This is my 1x1 png. } System.gc(); imageView.setImageBitmap(...); // Do whatever you need to do to load the image you want. 

Esto funciona para mí

 Bitmap myBitmap; BitmapFactory.Options options = new BitmapFactory.Options(); options.InPurgeable = true; options.OutHeight = 50; options.OutWidth = 50; options.InSampleSize = 4; File imgFile = new File(filepath); myBitmap = BitmapFactory.DecodeFile(imgFile.AbsolutePath, options); 

and this is on C# monodroid. you can easily change the path of the image. what important here is the options to be set.

This seems like the appropriate place to share my utility class for loading and processing images with the community, you are welcome to use it and modify it freely.

 package com.emil; import java.io.IOException; import java.io.InputStream; import android.graphics.Bitmap; import android.graphics.BitmapFactory; /** * A class to load and process images of various sizes from input streams and file paths. * * @author Emil http://stackoverflow.com/users/220710/emil * */ public class ImageProcessing { public static Bitmap getBitmap(InputStream stream, int sampleSize, Bitmap.Config bitmapConfig) throws IOException{ BitmapFactory.Options options=ImageProcessing.getOptionsForSampling(sampleSize, bitmapConfig); Bitmap bm = BitmapFactory.decodeStream(stream,null,options); if(ImageProcessing.checkDecode(options)){ return bm; }else{ throw new IOException("Image decoding failed, using stream."); } } public static Bitmap getBitmap(String imgPath, int sampleSize, Bitmap.Config bitmapConfig) throws IOException{ BitmapFactory.Options options=ImageProcessing.getOptionsForSampling(sampleSize, bitmapConfig); Bitmap bm = BitmapFactory.decodeFile(imgPath,options); if(ImageProcessing.checkDecode(options)){ return bm; }else{ throw new IOException("Image decoding failed, using file path."); } } public static Dimensions getDimensions(InputStream stream) throws IOException{ BitmapFactory.Options options=ImageProcessing.getOptionsForDimensions(); BitmapFactory.decodeStream(stream,null,options); if(ImageProcessing.checkDecode(options)){ return new ImageProcessing.Dimensions(options.outWidth,options.outHeight); }else{ throw new IOException("Image decoding failed, using stream."); } } public static Dimensions getDimensions(String imgPath) throws IOException{ BitmapFactory.Options options=ImageProcessing.getOptionsForDimensions(); BitmapFactory.decodeFile(imgPath,options); if(ImageProcessing.checkDecode(options)){ return new ImageProcessing.Dimensions(options.outWidth,options.outHeight); }else{ throw new IOException("Image decoding failed, using file path."); } } private static boolean checkDecode(BitmapFactory.Options options){ // Did decode work? if( options.outWidth<0 || options.outHeight<0 ){ return false; }else{ return true; } } /** * Creates a Bitmap that is of the minimum dimensions necessary * @param bm * @param min * @return */ public static Bitmap createMinimalBitmap(Bitmap bm, ImageProcessing.Minimize min){ int newWidth, newHeight; switch(min.type){ case WIDTH: if(bm.getWidth()>min.minWidth){ newWidth=min.minWidth; newHeight=ImageProcessing.getScaledHeight(newWidth, bm); }else{ // No resize newWidth=bm.getWidth(); newHeight=bm.getHeight(); } break; case HEIGHT: if(bm.getHeight()>min.minHeight){ newHeight=min.minHeight; newWidth=ImageProcessing.getScaledWidth(newHeight, bm); }else{ // No resize newWidth=bm.getWidth(); newHeight=bm.getHeight(); } break; case BOTH: // minimize to the maximum dimension case MAX: if(bm.getHeight()>bm.getWidth()){ // Height needs to minimized min.minDim=min.minDim!=null ? min.minDim : min.minHeight; if(bm.getHeight()>min.minDim){ newHeight=min.minDim; newWidth=ImageProcessing.getScaledWidth(newHeight, bm); }else{ // No resize newWidth=bm.getWidth(); newHeight=bm.getHeight(); } }else{ // Width needs to be minimized min.minDim=min.minDim!=null ? min.minDim : min.minWidth; if(bm.getWidth()>min.minDim){ newWidth=min.minDim; newHeight=ImageProcessing.getScaledHeight(newWidth, bm); }else{ // No resize newWidth=bm.getWidth(); newHeight=bm.getHeight(); } } break; default: // No resize newWidth=bm.getWidth(); newHeight=bm.getHeight(); } return Bitmap.createScaledBitmap(bm, newWidth, newHeight, true); } public static int getScaledWidth(int height, Bitmap bm){ return (int)(((double)bm.getWidth()/bm.getHeight())*height); } public static int getScaledHeight(int width, Bitmap bm){ return (int)(((double)bm.getHeight()/bm.getWidth())*width); } /** * Get the proper sample size to meet minimization restraints * @param dim * @param min * @param multipleOf2 for fastest processing it is recommended that the sample size be a multiple of 2 * @return */ public static int getSampleSize(ImageProcessing.Dimensions dim, ImageProcessing.Minimize min, boolean multipleOf2){ switch(min.type){ case WIDTH: return ImageProcessing.getMaxSampleSize(dim.width, min.minWidth, multipleOf2); case HEIGHT: return ImageProcessing.getMaxSampleSize(dim.height, min.minHeight, multipleOf2); case BOTH: int widthMaxSampleSize=ImageProcessing.getMaxSampleSize(dim.width, min.minWidth, multipleOf2); int heightMaxSampleSize=ImageProcessing.getMaxSampleSize(dim.height, min.minHeight, multipleOf2); // Return the smaller of the two if(widthMaxSampleSizedim.height){ return ImageProcessing.getMaxSampleSize(dim.width, min.minDim, multipleOf2); }else{ return ImageProcessing.getMaxSampleSize(dim.height, min.minDim, multipleOf2); } } return 1; } public static int getMaxSampleSize(int dim, int min, boolean multipleOf2){ int add=multipleOf2 ? 2 : 1; int size=0; while(min<(dim/(size+add))){ size+=add; } size = size==0 ? 1 : size; return size; } public static class Dimensions { int width; int height; public Dimensions(int width, int height) { super(); this.width = width; this.height = height; } @Override public String toString() { return width+" x "+height; } } public static class Minimize { public enum Type { WIDTH,HEIGHT,BOTH,MAX } Integer minWidth; Integer minHeight; Integer minDim; Type type; public Minimize(int min, Type type) { super(); this.type = type; switch(type){ case WIDTH: this.minWidth=min; break; case HEIGHT: this.minHeight=min; break; case BOTH: this.minWidth=min; this.minHeight=min; break; case MAX: this.minDim=min; break; } } public Minimize(int minWidth, int minHeight) { super(); this.type=Type.BOTH; this.minWidth = minWidth; this.minHeight = minHeight; } } /** * Estimates size of Bitmap in bytes depending on dimensions and Bitmap.Config * @param width * @param height * @param config * @return */ public static long estimateBitmapBytes(int width, int height, Bitmap.Config config){ long pixels=width*height; switch(config){ case ALPHA_8: // 1 byte per pixel return pixels; case ARGB_4444: // 2 bytes per pixel, but depreciated return pixels*2; case ARGB_8888: // 4 bytes per pixel return pixels*4; case RGB_565: // 2 bytes per pixel return pixels*2; default: return pixels; } } private static BitmapFactory.Options getOptionsForDimensions(){ BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds=true; return options; } private static BitmapFactory.Options getOptionsForSampling(int sampleSize, Bitmap.Config bitmapConfig){ BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = false; options.inDither = false; options.inSampleSize = sampleSize; options.inScaled = false; options.inPreferredConfig = bitmapConfig; return options; } } 

In one of my application i need to take picture either from Camera/Gallery . If user click image from Camera(may be 2MP, 5MP or 8MP), image size varies from kB s to MB s. If image size is less(or up to 1-2MB) above code working fine but if i have image of size above 4MB or 5MB then OOM comes in frame 🙁

then i have worked to solve this issue & finally i’ve made the below improvement to Fedor’s(All Credit to Fedor for making such a nice solution) code 🙂

 private Bitmap decodeFile(String fPath) { // Decode image size BitmapFactory.Options opts = new BitmapFactory.Options(); /* * If set to true, the decoder will return null (no bitmap), but the * out... fields will still be set, allowing the caller to query the * bitmap without having to allocate the memory for its pixels. */ opts.inJustDecodeBounds = true; opts.inDither = false; // Disable Dithering mode opts.inPurgeable = true; // Tell to gc that whether it needs free // memory, the Bitmap can be cleared opts.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 BitmapFactory.decodeFile(fPath, opts); // The new size we want to scale to final int REQUIRED_SIZE = 70; // Find the correct scale value. int scale = 1; if (opts.outHeight > REQUIRED_SIZE || opts.outWidth > REQUIRED_SIZE) { // Calculate ratios of height and width to requested height and width final int heightRatio = Math.round((float) opts.outHeight / (float) REQUIRED_SIZE); final int widthRatio = Math.round((float) opts.outWidth / (float) REQUIRED_SIZE); // Choose the smallest ratio as inSampleSize value, this will guarantee // a final image with both dimensions larger than or equal to the // requested height and width. scale = heightRatio < widthRatio ? heightRatio : widthRatio;// } // Decode bitmap with inSampleSize set opts.inJustDecodeBounds = false; opts.inSampleSize = scale; Bitmap bm = BitmapFactory.decodeFile(fPath, opts).copy( Bitmap.Config.RGB_565, false); return bm; } 

I hope this will help the buddies facing the same problem!

for more please refer this

I just ran into this issue a couple minutes ago. I solved it by doing a better job at managing my listview adapter. I thought it was an issue with the hundreds of 50x50px images I was using, turns out I was trying to inflate my custom view each time the row was being shown. Simply by testing to see if the row had been inflated I eliminated this error, and I am using hundreds of bitmaps. This is actually for a Spinner, but the base adapter works all the same for a ListView. This simple fix also greatly improved the performance of the adapter.

 @Override public View getView(final int position, View convertView, final ViewGroup parent) { if(convertView == null){ LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); convertView = inflater.inflate(R.layout.spinner_row, null); } ... 

This issue only happens in Android emulators. I also faced this issue in an emulator but when I checked in a device then it worked fine.

So please check in a device. It may be run in device.

I’ve spent the entire day testing these solutions and the only thing that worked for me is the above approaches for getting the image and manually calling the GC, which I know is not supposed to be necessary, but it is the only thing that worked when I put my app under heavy load testing switching between activities. My app has a list of thumbnail images in a listview in (lets say activity A) and when you click on one of those images it takes you to another activity (lets say activity B) that shows a main image for that item. When I would switch back and forth between the two activities, I would eventually get the OOM error and the app would force close.

When I would get half way down the listview it would crash.

Now when I implement the following in activity B, I can go through the entire listview with no issue and keep going and going and going…and its plenty fast.

 @Override public void onDestroy() { Cleanup(); super.onDestroy(); } private void Cleanup() { bitmap.recycle(); System.gc(); Runtime.getRuntime().gc(); } 

My 2 cents: i solved my OOM errors with bitmaps by:

a) scaling my images by a factor of 2

b) using Picasso library in my custom Adapter for a ListView, with a one-call in getView like this: Picasso.with(context).load(R.id.myImage).into(R.id.myImageView);

use these code for every image in select from SdCard or drewable to convert bitmap object.

 Resources res = getResources(); WindowManager window = (WindowManager) getSystemService(Context.WINDOW_SERVICE); Display display = window.getDefaultDisplay(); @SuppressWarnings("deprecation") int width = display.getWidth(); @SuppressWarnings("deprecation") int height = display.getHeight(); try { if (bitmap != null) { bitmap.recycle(); bitmap = null; System.gc(); } bitmap = Bitmap.createScaledBitmap(BitmapFactory .decodeFile(ImageData_Path.get(img_pos).getPath()), width, height, true); } catch (OutOfMemoryError e) { if (bitmap != null) { bitmap.recycle(); bitmap = null; System.gc(); } BitmapFactory.Options options = new BitmapFactory.Options(); options.inPreferredConfig = Config.RGB_565; options.inSampleSize = 1; options.inPurgeable = true; bitmapBitmap.createScaledBitmap(BitmapFactory.decodeFile(ImageData_Path.get(img_pos) .getPath().toString(), options), width, height,true); } return bitmap; 

use your image path instend of ImageData_Path.get(img_pos).getPath() .

This code will help to load large bitmap from drawable

 public class BitmapUtilsTask extends AsyncTask { Context context; public BitmapUtilsTask(Context context) { this.context = context; } /** * Loads a bitmap from the specified url. * * @param url The location of the bitmap asset * @return The bitmap, or null if it could not be loaded * @throws IOException * @throws MalformedURLException */ public Bitmap getBitmap() throws MalformedURLException, IOException { // Get the source image's dimensions int desiredWidth = 1000; BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeResource(context.getResources(), R.drawable.green_background , options); int srcWidth = options.outWidth; int srcHeight = options.outHeight; // Only scale if the source is big enough. This code is just trying // to fit a image into a certain width. if (desiredWidth > srcWidth) desiredWidth = srcWidth; // Calculate the correct inSampleSize/scale value. This helps reduce // memory use. It should be a power of 2 int inSampleSize = 1; while (srcWidth / 2 > desiredWidth) { srcWidth /= 2; srcHeight /= 2; inSampleSize *= 2; } // Decode with inSampleSize options.inJustDecodeBounds = false; options.inDither = false; options.inSampleSize = inSampleSize; options.inScaled = false; options.inPreferredConfig = Bitmap.Config.ARGB_8888; options.inPurgeable = true; Bitmap sampledSrcBitmap; sampledSrcBitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.green_background , options); return sampledSrcBitmap; } /** * The system calls this to perform work in a worker thread and delivers * it the parameters given to AsyncTask.execute() */ @Override protected Bitmap doInBackground(Object... item) { try { return getBitmap(); } catch (MalformedURLException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } return null; } 

}

Such OutofMemoryException cannot be totally resolved by calling the System.gc() and so on .

By referring to the Activity Life Cycle

The Activity States are determined by the OS itself subject to the memory usage for each process and the priority of each process.

You may consider the size and the resolution for each of the bitmap pictures used. I recommend to reduce the size ,resample to lower resolution , refer to the design of galleries (one small picture PNG , and one original picture.)

Generally android device heap size is only 16MB (varies from device/OS see post Heap Sizes ), if you are loading the images and it crosses the size of 16MB , it will throw out of memory exception, instead of using the Bitmap for , loading images from SD card or from resources or even from network try to using getImageUri , loading bitmap require more memory , or you can set bitmap to null if your work done with that bitmap.

All the solutions here require setting a IMAGE_MAX_SIZE. This limits devices with more powerful hardware and if the image size is too low it looks ugly on the HD screen.

I came out with a solution that works with my Samsung Galaxy S3 and several other devices including less powerful ones, with better image quality when a more powerful device is used.

The gist of it is to calculate the maximum memory allocated for the app on a particular device, then set the scale to be lowest possible without exceeding this memory. Aquí está el código:

 public static Bitmap decodeFile(File f) { Bitmap b = null; try { // Decode image size BitmapFactory.Options o = new BitmapFactory.Options(); o.inJustDecodeBounds = true; FileInputStream fis = new FileInputStream(f); try { BitmapFactory.decodeStream(fis, null, o); } finally { fis.close(); } // In Samsung Galaxy S3, typically max memory is 64mb // Camera max resolution is 3264 x 2448, times 4 to get Bitmap memory of 30.5mb for one bitmap // If we use scale of 2, resolution will be halved, 1632 x 1224 and x 4 to get Bitmap memory of 7.62mb // We try use 25% memory which equals to 16mb maximum for one bitmap long maxMemory = Runtime.getRuntime().maxMemory(); int maxMemoryForImage = (int) (maxMemory / 100 * 25); // Refer to // http://developer.android.com/training/displaying-bitmaps/cache-bitmap.html // A full screen GridView filled with images on a device with // 800x480 resolution would use around 1.5MB (800*480*4 bytes) // When bitmap option's inSampleSize doubled, pixel height and // weight both reduce in half int scale = 1; while ((o.outWidth / scale) * (o.outHeight / scale) * 4 > maxMemoryForImage) scale *= 2; // Decode with inSampleSize BitmapFactory.Options o2 = new BitmapFactory.Options(); o2.inSampleSize = scale; fis = new FileInputStream(f); try { b = BitmapFactory.decodeStream(fis, null, o2); } finally { fis.close(); } } catch (IOException e) { } return b; } 

I set the maximum memory used by this bitmap to be 25% of maximum allocated memory, you may need to adjust this to your needs and make sure this bitmap is cleaned up and don’t stay in memory when you’ve finished using it. Typically I use this code to perform image rotation (source and destination bitmap) so my app needs to load 2 bitmaps in memory at the same time, and 25% gives me a good buffer without running out of memory when performing image rotation.

Hope this helps someone out there..