安卓自定义视图中的位图内存泄漏问题

18

我有一个自定义视图,需要绘制两个位图:一个是背景图片,代表地图的图像;另一个是指针,将被绘制在画布的左上角位置。

这两张图片都在 onDraw 方法中绘制,并且在包含该视图的活动期间保持不变。一段时间后,我会收到“OutOfMemoryError: bitmap size exceeds VM budget”错误。

这意味着我存在内存泄漏,并且位图没有被垃圾回收。我之前问过这个问题,但现在情况有些变化。我创建了一个 init 方法,在其中设置要使用的位图,但这仍然不是一个好的方法,错误会在稍后出现。

以下是代码:

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();
 }
}
在包含视图的活动的onCreate方法中,我调用setMapBitmap()和init()方法。在此处,我知道哪个位图将被用作地图。在活动的onPause方法中,我调用了视图的destroy()方法。但是我仍然遇到错误。 我阅读了这个文章,但不知道如何将其应用到我的情况中。 我对任何其他解决方案都持开放态度。请注意,我需要调整地图位图的大小(基于包含它的布局的高度),并且仍然能够在正确的位置绘制位置。

你是在模拟器上还是实体设备上运行?在调试器下还是没有调试器? - Peter Knego
1
我在模拟器上运行了它,没有进行调试。启用调试会使速度变得太慢。谢谢。 - Alin
为什么你在调整大小的时候不清除mapBitmap呢?另外一件事是当你需要它的时候才调整位图大小,即在onDraw中调整位图大小,然后清除它,以便进行垃圾回收。 - Karan
2个回答

12

显然你的程序存在内存泄漏问题,请阅读如何避免内存泄漏以及如何查找内存泄漏问题

以下是使用弱引用重构后的代码:

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

谢谢Peter。我已经安装了Eclipse Memory Analyzer并将检查它是否正常工作。如果是的话,我会标记答案。 - Alin
这是一个解决方案,直到我发现了一个问题:一段时间后地图图像消失了... 我猜测它被垃圾回收了。 - Alin
1
当然。您需要检查weakReference.get()是否返回null,如果是,则重新加载位图。您可以将WeakReference扩展为BitmapWeakReference,并覆盖.get()方法,以便它检查super.get()是否为空并重新加载图片。 - Peter Knego
5
只有在低内存时不需要对位图进行引用时才应使用弱引用,这并不是一个真正的解决方案。 - TjerkW
@TjerkW 我认为没有其他解决方案。如果你的内存变低,你必须让垃圾回收器清理图像。 - neteinstein
厉害的解决方案 Peter!使用弱引用的那个部分非常好用,谢谢! - Elias

4

你的代码中有一个问题,就是显然没有回收所有的位图。这里有几个代码片段可以帮助解决:

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;

并且:

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接