Android custom view Bitmap memory leak

Acasă Forumuri Dezvoltarea de aplicaţii Android Android custom view Bitmap memory leak

Acest forum conține 8 replies și 3 participanți și a fost actualizat de Avatar of Marius Mailat Marius Mailat - acum 3 years, 7 months.

Vizualizezi răspunsul 9 - 1 până la 9 (din totalul de 9)
Autor Mesaje
Autor Mesaje
2010/11/19 - 12:54 pm #5228
Avatar of Alin Berce
Alin Berce

Am un custom view in care trebuie sa contina 2 imagini, una mai mare folosita ca si background, care ramane neschimbata cat timp activitatea care contine view-ul este activ care reprezinta harta, si o imagine mica care contine pozitioa curenta pe harta respectiva. Deci practic, doar imaginea care reprezinta pozitia se va misca in functie de locatia GPS citita.

Aici e codul:
public class MyMapView extends View {
private int xPos = 0;
private int yPos = 0;
private int space = 0;

private Bitmap resizedBitmap;
private Bitmap position;
private Bitmap mapBitmap;

public void setMapBitmap(Bitmap value) {
this.mapBitmap = value;
}

public MyMapView(Context context) {
super(context);
}

public MyMapView(Context context, AttributeSet attrs) {
super(context, attrs);
}

public MyMapView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}

public void init(final Context context) {
Paint paint = new Paint();
paint.setFilterBitmap(true);

int width = getMeasuredWidth();
int height = getMeasuredHeight();

resizedBitmap = Bitmap.createScaledBitmap(mapBitmap, height, height, true);
position = BitmapFactory.decodeResource(getContext().getResources(), R.drawable.position);
space = (width – resizedBitmap.getWidth()) / 2;
}

public void destroy()
{
resizedBitmap.recycle();
resizedBitmap=null;
position.recycle();
position=null;
mapBitmap.recycle();
mapBitmap=null;
}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.getSize(heightMeasureSpec));
}

@Override
protected void onDraw(Canvas canvas) {

if (mapBitmap != null) {
canvas.drawBitmap(resizedBitmap, space, 0, null);
}

if (xPos != 0 && yPos != 0) {
canvas.translate(xPos + space – position.getWidth() / 2, yPos – position.getHeight() / 2);
canvas.drawBitmap(position, new Matrix(), new Paint());

}
}

public void updatePosition(int xpos, int ypos)
{
xPos = xpos;
yPos = ypos;
invalidate();
}
}

Partea proasta e ca dupa un timp apare OutOfMemoryError: bitmap size exceeds VM budget, deci e clar ca am un leak… si nu inteleg de ce.

2010/11/19 - 2:16 pm #6107
Avatar of Marius Mailat
Marius Mailat

Yes, in sfarsit cineva a dat peste bug-ul acesta. Alin cine ajunge la aceasta problema ajunge la un “alt nivel” cum spun eu. Este o problema in core si iti postez mai jos cateva date. Felicitari, nu e gluma, chiar ma bucur ca ai dat de ea.
Eu asteptam de mult intrebarea, eram curios daca o sa vina vreodata aici pe Androider.
Platforma Android limiteaza memoria unei aplicatii la 16
Mb (heap size). Din aceasta cauza aplicatiile care folosesc intensiv imagini primesc eroarea:
E/dalvikvm-heap(15553): 1036800-byte external allocation too large for
this process.
E/GraphicsJNI(15553): VM won’t let us allocate 1036800 bytes
Eroarea apare in cel putin 18.000 rezultate in Google
http://www.google.de/search?q=%22byte+external+allocation+too+large+for+this+process%22&hl=en&client=firefox-a&rls=org.mozilla:en-US:official&ei=TE10TOzqIMqdOKD6-MkI&start=0&sa=N
Bug-ul oficial in Android este:
http://code.google.com/p/android/issues/detail?id=8488
http://code.google.com/p/android/issues/detail?id=2391
Solutii explicate se gasesc la :
http://www.memofy.com/memofy/show/1008ab7f2836ab7f01071c2dbfe138/outofmemory-exception-when-decoding-with-bitmapfactory
http://markmail.org/message/smg7pog5tz25p7w5
http://stackoverflow.com/questions/477572/android-strange-out-of-memory-issue
http://kfb-android.blogspot.com/2009/04/image-processing-in-android.html
http://www.mail-archive.com/android-developers@googlegroups.com/msg16120.html
http://www.helloandroid.com/tutorials/how-avoid-outofmemory-error-oom-ugly-truth-revealed
Solutia ceea mai buna este sa muti logica de incarcare a imaginilor din xmk in code si sa dealoci manual. Atentie sa nu faci leak la context.
Ei bine pentru mine nu a functionat si am ales sa implementez propriul meu life.cycle, am suprascris toate back-urile (compatibil de la 1.6 in sus pana la 2.2).
Nu te asteptai la raspunsul acesta, asai?
Marius

2010/11/19 - 3:02 pm #6110
Avatar of Eduard Scarlat
Eduard Scarlat

@Marius
Ai suprascris toate back-urile? :)
Eu zic ca existau solutii mai usoare… dar nu stiu exact in ce context ai avut problema, deci poate ai dreptate.

@alinu
Avem nevoie de mai multe detalii…
1. In ce context folosesti MyMapView? Poti sa pui codul aici?
2. Ce marime (rezolutie) au cele 3 poze (resizedBitmap, position, mapBitmap)?
3. Ai zis ca dupa un timp apare OutOfMemoryError… poti sa ne spui ce faci in aplicatie pana apare eroarea? Ce alte view-uri mai ai? Nu de alta, dar poti sa ai leak-ul in alta parte, iar memoria sa fie aproape full inainte de a folosi MyMapView.
Si te-as sfatui sa nu creezi cele 2 obiecte (new Matrix(), new Paint()) de fiecare data in onDraw()… din motive de performanta.

Majoritatea OutOfMemoryError sunt din cauza dezvoltatorilor, nu din cauza SDK-ului.
Evident, exista si exceptii. Poate voi va incadrati aici.
De exemplu, CursorAdapter-ul din versiunile vechi de Android avea memory leak-uri. Si daca il folositi, greu va dati seama ca este din cauza lui. Iar solutia ar fi sa va scrieti propriul CursorAdapter. Sau sa-l folositi pe cel din Android 2.2.
Puteti sa folositi Eclipse MAT pentru a identifica memory leak-urile.

2010/11/19 - 3:40 pm #6111
Avatar of Marius Mailat
Marius Mailat

@eduard

La mine era simplu, 2 activitati, o logica inauntru doar imagini. Subliniez din nou, 0 logica inauntru. Activitatile se chemau intre ele fara sa faca leak la ceva. Dupa 20-30 de back si forward aplicatia dadea force closed.

I-am prezentat la Roman Nurik bugul si e destul de cunoscut. L-am aminitit si la Google Developer Day in speranta ca il rezolva cineva.

Cu backu-l :)) am facut o clasa GeneralActivity si toate activitatile mele extind acuma clasa asta. Aplicatia are 56 de activitati, aprox 70 de http requesturi, imaginile au cam 2-3 mb cred, toate controale sunt de la 0 reconstruite sa “mimeze” look-ul de iPhone. Si nu intreba de ce :))

2010/11/19 - 4:08 pm #6112
Avatar of Eduard Scarlat
Eduard Scarlat

@Marius
Nu intreb de ce :) Stiu raspunsul.
Oricum, ma mir cum de n-am dat de bug pana acum, desi am avut de-a face cu un numar similar de activitati intr-o aplicatie (bine, pozele erau mai mici…).

2010/11/19 - 4:15 pm #6113
Avatar of Marius Mailat
Marius Mailat

Exact, din cauza imginilor. Practic cred ca in momentul in care mare parte din controale sunt bazate pe imagini custom + background custom + gradiente etc. Undeva framework-ul nu dealoca, daca te uiti pe bugul oficial discutia e aprinsa.

Desi oficial nu e fixata problema, e extrem de enervanta.

La aplicatiile mele “normale” nu am avut problema aceasta, foloseam mult interfata nativa.

2010/11/22 - 4:33 pm #6114
Avatar of Alin Berce
Alin Berce

Salut. Ma bucur sa vad feedback din partea voastra.

Acel custom view al meu, are 3 imagine, o harta care o iau de pe http/cache si care are 400×400 px. Aceasta harta o redimensionez in functie de layoutul care contine view-ul meu, in asa fel incat sa ramanta tot patrata imagine, harta redimensionata va fi resizedBitmap. position e o imagine cu pozitia actuala pe harta care are 24×24 px si e in resursele proiectului.

In activitatea care gazduieste view-ul meu am un LocationListener. La fiecare citire de pozitie prin GPS (care o fac la 3 secunde) invalidez view-ul prin metoda updatePosition(), redesenz harta si pozitia.

Am mai primit o solutie

public class MyMapView extends View {
private int xPos = 0;
private int yPos = 0;
private int space = 0;

private WeakReference resizedBitmap;
private WeakReference
position;
private WeakReference
mapBitmap;

public void setMapBitmap(Bitmap value) {
this.mapBitmap = new WeakReference(value);
}

public MyMapView(Context context) {
super(context);
}

public MyMapView(Context context, AttributeSet attrs) {
super(context, attrs);
}

public MyMapView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}

public void init(Bitmap mapBitmap) {
Paint paint = new Paint();
paint.setFilterBitmap(true);

int width = getMeasuredWidth();
int height = getMeasuredHeight();

resizedBitmap = new WeakReference(Bitmap.createScaledBitmap(
mapBitmap, width, height, true));
position = new WeakReference(BitmapFactory.decodeResource(
getContext().getResources(), R.drawable.position));
space = (width – resizedBitmap.get().getWidth()) / 2;
}

// public void destroy() {
// resizedBitmap.recycle();
// resizedBitmap = null;
// position.recycle();
// position = null;
// mapBitmap.recycle();
// mapBitmap = null;
// }

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec),
MeasureSpec.getSize(heightMeasureSpec));
}

@Override
protected void onDraw(Canvas canvas) {

if (mapBitmap != null) {
canvas.drawBitmap(resizedBitmap.get(), space, 0, null);
}

if (xPos != 0 && yPos != 0) {
canvas.translate(xPos + space – position.get().getWidth() / 2,
yPos – position.get().getHeight() / 2);
canvas.drawBitmap(position.get(), new Matrix(), null);

}
}

public void updatePosition(int xpos, int ypos) {
xPos = xpos;
yPos = ypos;
invalidate();
}
}

Cum vi se pare ? Inca nu am reusit sa testez acum ma chinui cu Eclipse Memory Analyzer.

2010/11/24 - 3:54 pm #6116
Avatar of Marius Mailat
Marius Mailat

Aici sunt cele 2 metode de “curatare” care le folosesc eu. Codul e modelat dupa un exemplu de la Romain si combinat cu exemplu de book shelves (via Google).

protected void cleanDrawable(int id)
{

ImageView im = (ImageView)findViewById(id);
if (im != null)
{
try{
BitmapDrawable bd = (BitmapDrawable)im.getDrawable();
if (bd != null)
{
bd.setCallback(null);
Bitmap bm = bd.getBitmap();
if (bm != null)
{
bm.recycle();
bm = null;
}
}

}catch (Exception e)
{

}

try{
BitmapDrawable bd = (BitmapDrawable)im.getBackground( );
if (bd != null)
{
bd.setCallback(null);
Bitmap bm = bd.getBitmap();
if (bm != null)
{
bm.recycle();
bm = null;
}
}
}catch (Exception e)
{

}
}

}

protected void cleanDrawableBackground(int id)
{
View im = (View)findViewById(id);
if (im != null)
{
BitmapDrawable bd;
Drawable d;
try
{
bd = (BitmapDrawable)im.getBackground();
if (bd != null)
{
bd.setCallback(null);
Bitmap bm = bd.getBitmap();
if (bm != null)
{
bm.recycle();
bm = null;
bd = null;
}
}
} catch (Exception e)
{
try{
d = (Drawable)im.getBackground();
if (d != null)
{
d.setCallback(null);
}
} catch (Exception e2)
{}
}
}
}

2010/11/24 - 4:01 pm #6117
Avatar of Marius Mailat
Marius Mailat

Legat de lifecycle pentru compatibilitate cu 1.5 pana la 2.2 folosesc

@Override
public void onBackPressed() {
//back on ApiMiscHomeActivity, no threading need it
Intent intent = new Intent();
intent.setClass( getApplicationContext(), MainActivity.class );
startActivity(intent);
overridePendingTransition(R.anim.fade, R.anim.hold);
finish();
}

@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK && event.getRepeatCount() == 0) {
onBackPressed();
}
return super.onKeyDown(keyCode, event);
}

Vizualizezi răspunsul 9 - 1 până la 9 (din totalul de 9)

Trebuie să te loghezi pentru a răspunde in acest topic.