Partage
  • Partager sur Facebook
  • Partager sur Twitter

[Android] OutOfMemoryError

Gestion d'un tas d'images

14 mai 2012 à 15:01:57

Hello,

Et oui, encore des problèmes avec le dev android :p

Cela fait plus d'une semaine déjà que j'ai des OutOfMemoryError dans la BitmapFactory ...
Contextualisons tout ça :
* je suis avec eclipse et Android 2.1 (pas le choix)
* j'ai donc une application avec plusieurs activities
* l'une d'elle contient une ListView avec dans chaque éléments une image propre et une image commune à tous les éléments de la liste (au max 70)
* j'ai fais une classe héritant de ImageView qui ouvre chaque image en question en diminuant la taille pour moins consommer (option inSampleSize de la BitmapFactory) dont voici le code :
package com.BeApp.VendeeVelo;

import com.yannberthou.android.datamanagers.ResourceManager;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.widget.ImageView;

public class ListePisteImgView extends ImageView {
	boolean loaded = false;
	Bitmap 	pixels = null;
	int		idPiste = 0;

	public ListePisteImgView(Context context, AttributeSet attrs) {
		super(context, attrs);
		this.setAdjustViewBounds(true);
	}
	
	private void setPixelsFromIdPiste() {
		BitmapFactory.Options options = new BitmapFactory.Options();
		
		options.inPurgeable = true; // bitmap can be purged to disk
		options.inSampleSize = 6;	// image size divided by 6
		
	    Bitmap new_pixels = BitmapFactory.decodeResource(	this.getContext().getResources(),
	    							ResourceManager.getImgPiste(this.idPiste, 1), // Return the resourceId of the image
	    							options);
	    if (new_pixels != null) {
	    	System.out.println("resource decoded : " + new_pixels.getWidth() + "x" + new_pixels.getHeight());
	    	if (pixels != null)
	    		pixels.recycle();
	    	pixels = new_pixels;
	    	this.setImageBitmap(pixels);
	    	loaded = true;
	    }
	}
	
	@Override
	protected void onDraw(Canvas canvas) {
		if (pixels == null && this.idPiste > 0)
			setPixelsFromIdPiste();
		else
	    	this.setImageBitmap(pixels);
		super.onDraw(canvas);
	}
	
	@Override
	public void setImageResource(int resId) {
		// resId est en fait juste un id de base de donnee
		// on obtient l'id de la ressouce correpondante avec ResourceManager.getImgPiste
		idPiste = resId;
	}
	
	@Override
	protected void onAttachedToWindow() {
		System.out.println("ListePisteImgView attached !!!!!");
		setPixelsFromIdPiste();
		super.onAttachedToWindow();
	}
	
	@Override
	protected void onDetachedFromWindow() {
		System.out.println("ListePisteImgView detached !!!!!");
		if (pixels != null) {
    		pixels.recycle();
    		pixels = null;
    		this.setImageBitmap(null);
		}
		super.onDetachedFromWindow();
	}

}

* Cette vue est utilisée dans le layout
* Enfin, la ListView est remplie avec un SimpleAdapter

Y'a-t-il vraiment un soucis dans ce que je fais ?
Comment est-ce que je pourrais éviter ces erreurs ?

Merci d'avance pour vos réponses ;)

PS: quand on change d'activiy, les contenus des images sont-ils libérés de la mémoire pour être réalloués quand on remonte à celle qui avait des images ?

EDIT : En remplacant les Bitmap par des Drawable ça ne serait pas mieux par hazard ? pareil ...
  • Partager sur Facebook
  • Partager sur Twitter
14 mai 2012 à 15:33:07

Salut,

En voyant ton code, je suis déjà surpris par une chose : Comment ça se fait que tu “attaches" un Bitmap à ton ImageView dans ta méthode protected void onDraw(Canvas canvas) ? A mon sens, tu appelles la méthode setImageBitmap(bitmap) lorsque tu construis ta vue ; c'est-à-dire, dans la méthode getView(int position, View convertView, ViewGroup parent) de ton adaptateur et c'est tout !

Sinon, es-tu certain que tu passes par ta méthode protected void onDetachedFromWindow() pour libérer la mémoire et au bon moment ; c'est-à-dire, lorsque la ligne de ton ListView n'est plus affiché sur l'écran de l'utilisateur ?

Pour finir, je ne peux que t'encourager à te rendre sur ce tutoriel plus ou moins récemment posté dans la documentation Android par Google pour gérer le plus efficacement possible la mémoire avec des objets Bitmap grâce au changement d'échelle et de qualité d'image, d'une mise en cache sur le device et dans une structure de données et le tout de manière multithread. Tu n'es pas obligé de tenir compte de tout dans le tutoriel (bien que ça serait dommage) mais tu pourrais en tout cas reprendre le changement d'échelle et de qualité d'image en fonction d'une hauteur et largeur donnée.
  • Partager sur Facebook
  • Partager sur Twitter
Si vous voulez me retrouver, rendez-vous sur ZesteDeSavoir.
14 mai 2012 à 17:56:38

Merci AndroWiiid

J'ai suivi ton tuto et du coup j'ai fais mon adapter perso (c'est vrai que c'est bien plus propre :p ).
J'ai aussi passé le chargement des images en asynchrone mais je n'ai pas l'impression que cela influe réellement sur la mémoire (par contre c'est bien plus fluide même si du coup les images on tendance à un peu clignoter mais c'est peut-être lent à cause de l'émulateur).
Oui le onDetachedFromWindow est bien appelé.

J'ai toujours la runtime exception de lancée ...
Au pire j'essaierais de la catcher ou faire quelque chose pour que ça ne plante pas je vais voir ça demain :)
  • Partager sur Facebook
  • Partager sur Twitter
15 mai 2012 à 15:25:30

Citation : pepetiti

J'ai suivi ton tuto et du coup j'ai fais mon adapter perso (c'est vrai que c'est bien plus propre :p ).


C'est une bonne chose, il est là pour ça ! :)

Citation : pepetiti

J'ai aussi passé le chargement des images en asynchrone mais je n'ai pas l'impression que cela influe réellement sur la mémoire (par contre c'est bien plus fluide même si du coup les images on tendance à un peu clignoter mais c'est peut-être lent à cause de l'émulateur).


Rendre le chargement des images asynchrones te permettra de ne pas bloquer ton thread principal (UI Thread). Par contre, ce n'est pas normal que tes images clignottent.

Citation : pepetiti

Oui le onDetachedFromWindow est bien appelé.


C'est bizarre parce que tu sembles libérer convenablement la mémoire dans cette méthode.
A tout hasard, tu passes bien à l'intérieur de la condition présent dans cette méthode ?

Citation : pepetiti

J'ai toujours la runtime exception de lancée ...
Au pire j'essaierais de la catcher ou faire quelque chose pour que ça ne plante pas je vais voir ça demain :)


Oula ! Non, ça tu ne peux pas faire. Les mobiles ont une mémoire réduite. Si cette exception est lancée, ton application crash automatiquement sans que tu ne puisses rien faire. Il faut user d'ingéniosité pour gérer convenablement la mémoire.
  • Partager sur Facebook
  • Partager sur Twitter
Si vous voulez me retrouver, rendez-vous sur ZesteDeSavoir.