18
votes

I've got a custom view in which I need to draw two bitmaps, one is a background, representing the image of a map and one is a pin which will be drawn on top/left position in canvas.

The both images are drawn onDraw and remain the same during the live of the activity that contains the view. After a while I get a

OutOfMemoryError: bitmap size exceeds VM budget

This means that I have a leak and the bitmaps don't get garbage collected. I asked this question before, but now the situation changed a little. I made a init method where I set the bitmaps I want to use but this still isn't a good approach, the error appears later, but still there.

Here is the code

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();
 }
}

I call the setMapBitmap() and init() on onCreate of the activity that contains the view. In there I know what bitmap will be used as map. onPause of the activity I call the destroy() of the view. I still get errors. I've read this this but I don't know how to adapt it on my case

I AM OPEN TO ANY OTHER SOLUTION. Please note that I need to resize the map bitmap (based on the height of the layout that contains it in mai activity) and still be able to draw the position on correct place.

2
Do you run this on emulator or device? Under debugger or without it?Peter Knego
I ran it on my emulator without debugging it. With debugging activated is too slow. Thank you.Alin
Why don't you clear the mapBitmap when you have resizedBitmap ? Another thing is resize the bitmap when you need it i.e. resize the bitmap in onDraw and then clear it out so that it can be GC'ed.Karan

2 Answers

12
votes

You obviously have a memory leak. Read how to avoid memory leaks and how to find memory leaks.

Here is a your code refactored to use WeakReference:

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

    private WeakReference<Bitmap> resizedBitmap;
    private WeakReference<Bitmap> position;
    private WeakReference<Bitmap> mapBitmap;

    public void setMapBitmap(Bitmap value) {
        this.mapBitmap = new WeakReference<Bitmap>(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>(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();
    }
}
4
votes

Something that marked me in your code is that you don't recycle all your bitmaps apparently. Here are a couple of code snippets that could help:

bmp = getBitmapFromRessourceID(R.drawable.menu);
ratio = (float)this.height/(float)bmp.getScaledHeight(canvas);
temp = Bitmap.createScaledBitmap(bmp, (int)(bmp.getWidth()*ratio), (int) (bmp.getHeight()*ratio-10), false);
bmp.recycle();
bmp = temp;

and:

Bitmap temp = BitmapFactory.decodeResource(context.getResources(), resource, opts);
Bitmap bmp = Bitmap.createBitmap(temp, 0, 0, temp.getWidth(), temp.getHeight(), flip, true);
temp.recycle();