Android bitmap imageview memory leak

15,701

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.

Share:
15,701
Dénes
Author by

Dénes

Updated on June 08, 2022

Comments

  • Dénes
    Dénes almost 2 years

    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.