outofmemoryerror - out of memory android studio
Excepción de OutOfMemory al cargar mapa de bits desde un almacenamiento externo (13)
En mi aplicación cargo un par de imágenes de archivos JPEG y PNG. Cuando coloco todos esos archivos en el directorio de activos y los cargo de esta manera, todo está bien:
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 de la tarjeta sd, ¡obtengo una excepción de 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 obtengo 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 pasar esto?
ACTUALIZACIÓN: Probé 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).
En lugar de cargarla directamente desde la tarjeta SD, ¿por qué no mover la imagen a la memoria caché en el almacenamiento interno del teléfono usando getCacheDir () o usar un directorio temporal para almacenar las imágenes?
Ver this , this en el uso de la memoria externa. Además, este artículo puede ser de importancia para usted.
Este es un problema bastante común al que todos nos 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 no descodificaría realmente 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 imagen de 5MP puede controlar fácilmente la poca memoria del teléfono. Esta creo que es la solución más elegante.
Gracias a todos los hilos, he encontrado una solución que funciona para mí en un dispositivo real. Los trucos se tratan de usar
BitmapFactory.Options opts=new BitmapFactory.Options();
opts.inSampleSize=(int)(target_size/bitmap_size); //if original bitmap is bigger
Pero para mí esto no fue suficiente. Mi imagen original (tomada de la aplicación de la cámara) era 3264x2448. La proporción correcta para mí era 3, ya que quería una imagen VGA simple de 1024x768.
Pero establecer inSampleSize en 3 no fue suficiente: todavía falta de memoria. Así que al final opté por un enfoque iterativo: comienzo a partir del tamaño correcto calculado y lo aumente hasta que dejo de tener una excepción OOM. Para mi 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;
Hay dos problemas aquí....
- La memoria de mapa de bits no se encuentra en el montón de VM, sino en el montón nativo; consulte BitmapFactory OOM que me vuelve loco
- La recolección de basura para el montón nativo es más perezosa que el montón de la máquina virtual, por lo que debe ser bastante agresivo al hacer bitmap.recycle y bitmap = null cada vez que realice una actividad en OnPause o onDestroy
Intenta esto de otra manera ...
Bitmap bmpOrignal = BitmapFactory.decodeFile("/sdcard/mydata/" + path");
No debe depender del GC para reciclar su memoria de mapa de bits. Debe reciclar claramente el mapa de bits cuando no sea necesario.
Ver el método de mapa de bits:
void recycle () Libera la memoria asociada con los píxeles de este mapa de bits y marca el mapa de bits como "muerto", lo que significa que lanzará una excepción si se llama a getPixels () o setPixels (), y no dibujará nada.
Permite a inSampleSize cambiar el tamaño de la imagen de lectura final. getLength() de AssetFileDescriptor permite obtener el tamaño del archivo.
Puede variar inSampleSize según getLength () para evitar que OutOfMemory sea 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;
}
Probé todos los enfoques mencionados here y en otros recursos, pero llegué a la conclusión de que establecer la referencia de ImageView a null 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;
}
& ¡estás listo!
Nota: Aunque puede resolver el problema anterior, le sugiero que verifique la carga optimizada de la imagen de Tom van Zummeren .
Y también verifique SoftReference : se SoftReference todas las SoftReferences que apuntan a objetos de fácil acceso se borrarán antes de que la máquina virtual lance un OutOfMemoryError.
Probablemente no haya nada de malo en el uso de la API, supongo que todo lo que podemos hacer es inferir que el uso de AssetManager implica menos asignación de pilas detrás de escena 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 imagen descomprimidos. Dado que conoces el tamaño de la imagen, ¿qué profundidad tiene? Si es 32bpp, intente inPreferredConfig
utilizando inPreferredConfig
.
Uno de los errores más comunes que encontré al desarrollar aplicaciones de Android es el error “java.lang.OutOfMemoryError: el tamaño del mapa de bits supera el presupuesto de VM”. Encontré este error frecuentemente en actividades que usan muchos mapas de bits después de cambiar la orientación: la Actividad se destruye, se crea de nuevo y los diseños se "inflan" desde el XML que consume la memoria de la VM disponible para los mapas de bits.
Los mapas de bits en el diseño de la actividad anterior no son desasignados correctamente por el recolector de basura porque han cruzado referencias a su actividad. Después de muchos experimentos encontré una solución bastante buena para este problema.
Primero, establezca el atributo "id" en la vista principal 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 al método unbindDrawables () pasando un refence a la Vista principal y luego haga 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 método unbindDrawables () explora el árbol de vista recursivamente y:
- Elimina las devoluciones de llamada en todos los dibujables de fondo
- Elimina niños en cada grupo de vista
Utilice el siguiente código y nunca obtendrá el siguiente error: java.lang.OutOfMemoryError: el tamaño del mapa de bits supera el presupuesto de la máquina virtual
BitmapFactory.Options bounds = new BitmapFactory.Options();
bounds.inSampleSize = 4;
myBitmap = BitmapFactory.decodeFile(imgFile.getAbsolutePath(), bounds);
picturesView.setImageBitmap(myBitmap);
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;
}
- Al hacer mucho con mapas de bits, no depure la aplicación, simplemente ejecútela. El depurador dejará fugas de memoria.
- Los mapas de bits son muy caros. Si es posible,
inSampleSize
al cargar creandoBitmapFactory.Options
y configurandoinSampleSize
a> 1.
EDITAR: También, asegúrese de revisar su aplicación para detectar pérdidas de memoria. Fugas en un mapa de bits (tener static
bits static
es una excelente manera de hacerlo) agotará rápidamente la memoria disponible.