6

I put 4x4 imageView to an activity(BoardActivity), and user can change the images by clicking them. With HTC Desire (Android 2.2.2), I got OOM(Out Of Memory) in about 30 minutes of intensive useage -EDIT: 16th start of this activity-, but no other devices produces this (android 2.1, and android 2.2.1). Is it possible, that I made some mistake with the bitmap/imageview useage and that causes this error? First, I load all resource ID into a map:

private Map<String, Integer> imageResourceMap;
imageResourceMap = new HashMap<String, Integer>();
        imageResourceMap.put("a", R.drawable.a);
        imageResourceMap.put("b", R.drawable.b);
        imageResourceMap.put("c", R.drawable.c);
//... I store 55 drawable's resourceId in this map

Then I resize and save every image into bitmap, represented in Map:

private static Map<String, Bitmap> imageBitmap;

    private void loadBitmaps(int imagesSize) {

    for (String s : imageResourceMap.keySet()) {
        Bitmap tmp = getBitmapFromRes(imageResourceMap.get(s), imagesSize);
        imageBitmap.put(s, tmp);
    }
}

private Bitmap getBitmapFromRes(int resId, int imageSize) {
    Bitmap b = null;
    try {
        // Decode image size
        BitmapFactory.Options o = new BitmapFactory.Options();
        o.inJustDecodeBounds = true;

        InputStream fis = getResources().openRawResource(resId);
        BitmapFactory.decodeStream(fis, null, o);
        fis.close();

        int scale = 1;
        if (o.outHeight > imageSize || o.outWidth > imageSize) {
            scale = (int) Math.pow(2, (int) Math.round(Math.log(imageSize / (double) Math.max(o.outHeight, o.outWidth)) / Math.log(0.5)));
        }

        // Decode with inSampleSize
        BitmapFactory.Options o2 = new BitmapFactory.Options();
        o2.inSampleSize = scale;
        fis = getResources().openRawResource(resId);
        b = BitmapFactory.decodeStream(fis, null, o2);
        fis.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
    return b;
}

I keep the imageViews in an array, and init all images with this function:

private static ImageView[][] imageViews;

private ImageView getImage(String name) {
        MyImageView item = new MyImageView(this, i, j, c + "");
        item.setImageBitmap(imageBitmap.get(name));
        item.setAdjustViewBounds(true);
        return item;
    }

When I need to change an image, I simple change its resource:

imageViews[i][j].setImageBitmap(imageBitmap.get("a"));

And right before I finish the activity, I recycle the bitmap map:

private void recycleImageBitmaps() {
    for (Bitmap b : imageBitmap.values()) {
        if (b != null) {
            b.recycle();
        }
    }

}

In AndroidManifest.xml I declared this activity "singleTask":

<activity android:name=".game.board.BoardActivity" android:launchMode="singleTask">
    </activity>

In this application(game), we reopen this activity a several times... What did I wrong? Can this cause the Out Of Memory error?

CORRECTION Corrected the getBitmapFromRes like this:

private Bitmap getBitmapFromRes(int resId, int imageSize) {
    Bitmap tmp = null;
    Bitmap b = null;
    try {
        // Decode image size
        BitmapFactory.Options o = new BitmapFactory.Options();
        o.inJustDecodeBounds = true;

        InputStream fis = getResources().openRawResource(resId);
        tmp = BitmapFactory.decodeStream(fis, null, o);
        fis.close();

        int scale = 1;
        if (o.outHeight > imageSize || o.outWidth > imageSize) {
            scale = (int) Math.pow(2, (int) Math.round(Math.log(imageSize / (double) Math.max(o.outHeight, o.outWidth)) / Math.log(0.5)));
        }

        // Decode with inSampleSize
        BitmapFactory.Options o2 = new BitmapFactory.Options();
        o2.inSampleSize = scale;
        fis = getResources().openRawResource(resId);
        b = BitmapFactory.decodeStream(fis, null, o2);
        fis.close();
    } catch (IOException e) {
        e.printStackTrace();
    }finally{
        if(tmp != null){
            tmp.recycle();
            tmp = null;
        }
    }
    return b;
}

HTC still crashed at the 11th start of this activity.

EDIT: This activity(BoardActivity) launch from an Activity(MenuActivity), which have 4 imageButton, and is in a Tabhost activity. The imageButtons declarations look like this:

<ImageButton
    android:id="@+id/game_menu_CreateButton"
    android:layout_width="120dip" 
    android:layout_height="120dip"
    android:layout_alignRight="@+id/textView1"
    android:layout_alignTop="@+id/textView1"
    android:background="@drawable/create" 
    android:layout_marginRight="1sp"
    android:layout_marginTop="10sp"
     />

When I start the BoardActivity from MenuActivity, I don't call finish() at MenuActivity, and when I call finish() at the BoardActivity, I don't start a new intent, so it just return to the already opened MenuActivity. And the 16 round of this, I got the OOM.

Kara
  • 6,115
  • 16
  • 50
  • 57
Dénes
  • 113
  • 1
  • 1
  • 8
  • What is the os version of your HTC Desire? And did you need to cache your bitmap? – Wenhui Dec 02 '12 at 01:01
  • gwhy is this line not assigned to a bitmap? `BitmapFactory.decodeStream(fis, null, o);` ah, I get it, just decode bounds is set. Can't delete comment on my tablet web browser! – weston Dec 02 '12 at 01:14
  • If I didn't cache the bitmap, then I need to resize the drawable and recycle the old bitmap at every image change. I tried to avoid that with caching. – Dénes Dec 02 '12 at 09:39
  • HTC Desire runs on Android 2.2.2 There's 2 other device without the OOM problem, with 2.2.1 and 2.1. – Dénes Dec 02 '12 at 09:55

1 Answers1

9

To reduce memory, you can try out these things :

  • After converting drawables into bitmaps, you can set the imageResourceMap to null. This will unload the 3 drawables.
  • Avoid storing a reference to the imageViews. You might be storing imageViews even after they are removed from the UI
  • Recycle the bitmaps more often. Instead of just onDestroy, as soon as you know that one bitmap is not used, you can recycle it.

Edit : based on the conversation in the comments : The bitmap returned by BitmapFactory.decodeStream(fis, null, o) is not assigned to any variable and hence is not recycled. Android 2.2 and 2.3 will have leaks in this line.

Kiran Kumar
  • 1,192
  • 8
  • 10
  • Actually I store 55 drawable's resourceId in the imageResourceMap, and I try it right now to set it null after I have the bitmaps. But the imageViews array is in use until activity-finish, and until finish, I could need every bitmap, so I cannot recycle them. – Dénes Dec 02 '12 at 10:38
  • o.inPurgeable = true; and o2.inPurgeable = true; might help. – Kiran Kumar Dec 02 '12 at 12:23
  • (edited the question) tried, now I got OOM at 16th start of the activity. Is there any other type beside Bitmap, which need to be recycle, and not enough to set them to null? – Dénes Dec 02 '12 at 13:23
  • are your sure you are calling recycleImageBitmaps() in onDestroy of your activity ? – Kiran Kumar Dec 02 '12 at 13:26
  • yes, I'am sure. i put it in to onDestroy, and before calling finish() I call it too. I put breakpoint into recycleImageBitmaps(), and it stop's it right where I need, and do the 50 round in the for loop. (making b.recycle()) – Dénes Dec 02 '12 at 14:07
  • Sorry, I am out of ideas on how to reduce memory in your case. The only thing I would do at this point is to the eclipse MAT tool to analyse the dominator tree and find out the memory leaks. All the best ! – Kiran Kumar Dec 02 '12 at 15:49
  • Thanks a lot, when you pointed out that BitmapFactory.decodeStream(fis, null o) is not assigned to any variable, that was a certain memory leak. Maybe now it comes from elsewhere. I would vote up your answer if I could ^^ – Dénes Dec 02 '12 at 16:06
  • I have edited my answer to contain the leak. You can vote up now. – Kiran Kumar Dec 02 '12 at 19:24