Memoria interna llena de imágenes, probablemente causada por Bitmap.compress (formato, int, flujo)

Mi aplicación es una aplicación de chat Wifi con la que puedes comunicarte entre dos unidades Android con mensajes de texto y fotos de cámara y enviarlas. Las imágenes se almacenan en la tarjeta SD.

Solía ​​tener un OutOfMemoryError lanzado después de un par de imágenes enviadas, pero he resuelto ese problema enviando el

 options.inPurgeable = true; 

y

 options.inInputShareable = true; 

Al método BitmapFactory.decodeByteArray. Esto hace que los píxeles "deallocable" para que las nuevas imágenes pueden utilizar la memoria. Por lo tanto, el error ya no permanece.

Pero, la memoria interna todavía está llena de imágenes y aparece la advertencia "Bajo espacio: el espacio de almacenamiento del teléfono está bajando". La aplicación ya no se bloquea, pero no hay más memoria en el teléfono después de la aplicación termina. Tengo que borrar manualmente los datos de la aplicación en Configuración> Aplicaciones> Administrar aplicaciones.

Intenté reciclar los mapas de bits e incluso intenté vaciar explícitamente la memoria caché de la aplicación, pero no parece hacer lo que esperaba.

Esta función recibe la imagen a través de una toma TCP, la escribe en la tarjeta SD y inicia mi actividad personalizada PictureView:

 public void receivePicture(String fileName) { try { int fileSize = inStream.readInt(); Log.d("","fileSize:"+fileSize); byte[] tempArray = new byte[200]; byte[] pictureByteArray = new byte[fileSize]; path = Prefs.getPath(this) + "/" + fileName; File pictureFile = new File(path); try { if( !pictureFile.exists() ) { pictureFile.getParentFile().mkdirs(); pictureFile.createNewFile(); } } catch (IOException e) { Log.d("", "Recievepic - Kunde inte skapa fil.", e); } int lastRead = 0, totalRead = 0; while(lastRead != -1) { if(totalRead >= fileSize - 200) { lastRead = inStream.read(tempArray, 0, fileSize - totalRead); System.arraycopy(tempArray, 0, pictureByteArray, totalRead, lastRead); totalRead += lastRead; break; } lastRead = inStream.read(tempArray); System.arraycopy(tempArray, 0, pictureByteArray, totalRead, lastRead); totalRead += lastRead; } BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(pictureFile)); bos.write(pictureByteArray, 0, totalRead); bos.flush(); bos.close(); bos = null; tempArray = null; pictureByteArray = null; setSentence("<"+fileName+">", READER); Log.d("","path:"+path); try { startActivity(new Intent(this, PictureView.class).putExtra("path", path)); } catch(Exception e) { e.printStackTrace(); } } catch(IOException e) { Log.d("","IOException:"+e); } catch(Exception e) { Log.d("","Exception:"+e); } } 

Aquí está PictureView . Crea un byte [] desde el archivo de la tarjeta SD, decodifica la matriz en un mapa de bits, comprime el mapa de bits y lo escribe de nuevo en la tarjeta SD. Por último, en el Progress.onDismiss , la imagen se establece como la imagen de una imagen en pantalla completaVer:

 public class PictureView extends Activity { private String fileName; private ProgressDialog progress; public ImageView view; @Override public void onCreate(Bundle bundle) { super.onCreate(bundle); Log.d("","onCreate() PictureView"); requestWindowFeature(Window.FEATURE_NO_TITLE); getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); view = new ImageView(this); setContentView(view); progress = ProgressDialog.show(this, "", "Laddar bild..."); progress.setOnDismissListener(new OnDismissListener() { public void onDismiss(DialogInterface dialog) { File file_ = getFileStreamPath(fileName); Log.d("","SETIMAGE"); Uri uri = Uri.parse(file_.toString()); view.setImageURI(uri); } }); new Thread() { public void run() { String path = getIntent().getStringExtra("path"); Log.d("","path:"+path); File pictureFile = new File(path); if(!pictureFile.exists()) finish(); fileName = path.substring(path.lastIndexOf('/') + 1); Log.d("","fileName:"+fileName); byte[] pictureArray = new byte[(int)pictureFile.length()]; try { DataInputStream dis = new DataInputStream( new BufferedInputStream( new FileInputStream(pictureFile)) ); for(int i=0; i < pictureArray.length; i++) pictureArray[i] = dis.readByte(); } catch(Exception e) { Log.d("",""+e); e.printStackTrace(); } /** * Passing these options to decodeByteArray makes the pixels deallocatable * if the memory runs out. */ BitmapFactory.Options options = new BitmapFactory.Options(); options.inPurgeable = true; options.inInputShareable = true; Bitmap pictureBM = BitmapFactory.decodeByteArray(pictureArray, 0, pictureArray.length, options); OutputStream out = null; try { out = openFileOutput(fileName, MODE_PRIVATE); /** * COMPRESS !!!!! **/ pictureBM.compress(CompressFormat.PNG, 100, out); pictureBM = null; progress.dismiss(); } catch (IOException e) { Log.e("test", "Failed to write bitmap", e); } finally { if (out != null) try { out.close(); out = null; } catch (IOException e) { } } } }.start(); } @Override protected void onStop() { super.onStop(); Log.d("","ONSTOP()"); Drawable oldDrawable = view.getDrawable(); if( oldDrawable != null) { ((BitmapDrawable)oldDrawable).getBitmap().recycle(); oldDrawable = null; Log.d("","recycle"); } Editor editor = this.getSharedPreferences("clear_cache", Context.MODE_PRIVATE).edit(); editor.clear(); editor.commit(); } } 

Cuando el usuario pulsa la tecla de retroceso, no se supone que la imagen ya esté disponible en la aplicación. Sólo almacenado en la tarjeta SD.

En onStop() reciclo el viejo Bitmap e incluso intento vaciar los datos de la aplicación. Aún aparece la advertencia "Bajo en espacio". ¿Cómo puedo estar seguro de que las imágenes no asignarán la memoria más cuando no sean necesarias?

EDIT: Parece que el problema es el método de compresión. Si todo después de la compresión es comentado, el problema permanece. Si elimino la compresión, el problema desaparece. Compress parece asignar memoria que nunca se libera, y es de 2 a 3 MB por imagen.

Ok, lo resolví. El problema era, estaba pasando un OutputStream para compress , que es un flujo a un archivo privado en la memoria interna de la aplicación. Eso es lo que fijé como la imagen más tarde. Este archivo nunca se asigna.

No entendí que tenía dos archivos: uno en la tarjeta SD y otro en la memoria interna, ambos con el mismo nombre.

Ahora, sólo estoy configurando el archivo de tarjeta SD como la imagen de ImageView. Nunca leo el archivo en la memoria interna como un byte [], por lo que nunca la decodificación de la matriz a un mapa de bits, por lo tanto nunca comprimir el mapa de bits en la memoria interna.

Este es el nuevo PictureView:

 public class PictureView extends Activity { public ImageView view; private String path; @Override public void onCreate(Bundle bundle) { super.onCreate(bundle); Log.d("","onCreate() PictureView"); path = getIntent().getStringExtra("path"); requestWindowFeature(Window.FEATURE_NO_TITLE); getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); view = new ImageView(this); setContentView(view); Uri uri = Uri.parse( new File(path).toString() ); view.setImageURI(uri); } @Override public boolean onKeyDown(int keyCode, KeyEvent event) { if (keyCode == KeyEvent.KEYCODE_BACK) { Log.d("","Back key pressed"); Drawable oldDrawable = view.getDrawable(); if( oldDrawable != null) { ((BitmapDrawable)oldDrawable).getBitmap().recycle(); oldDrawable = null; Log.d("","recycle"); } view = null; } return super.onKeyDown(keyCode, event); } } 

¿Es mala práctica colocar un archivo externo como la imagen de un ImageView? ¿Debo cargarlo primero en la memoria interna?

Si desea específicamente que la imagen se anule desde la memoria para asegurarse de que cuando un usuario presiona hacia atrás podría anular el botón de retroceso y hacer que su imagen limpiar las llamadas allí. Lo hago en algunas de mis aplicaciones y parece que funciona. Tal vez algo como esto:

 @Override protected void onBackPressed() { super.onBackPressed(); view.drawable = null; jumpBackToPreviousActivity(); } 

Estoy bastante seguro de que hay algunos métodos de vista que borrar otros cachés y cosas por el estilo. Usted puede reciclar el mapa de bits, pero que no garantiza que será objeto de dumping en ese momento, pero sólo en algún momento cuando el gc llega a ella ….. pero estoy seguro de que probablemente ya saben que ya 🙂

EDIT: También puede hacer lo mismo en el método onPause. Que uno está garantizado para ser llamado. Los otros dos nunca pueden ser llamados de acuerdo con los documentos Android. http://developer.android.com/reference/android/app/Activity.html

  • ¿Cómo entender que muestra LeakCanary?
  • Investigación de LeakCanary: com.motorola.pixelpipe.PixelPipeTarget.mContext
  • Atascado en "Vaciar memoria, la aplicación se congelará. Brrr. "Mensaje
  • Fuga de memoria MediaControllerCompat
  • Leyendo LeakCanary Log
  • Detección de fugas de memoria nativas en el código JNI de Android
  • FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.