2

I realize that there are many similar questions, but most involve scaling down the bitmap, or explicit calls to bitmap.recycle() and System.gc() which doesn't guarantee anything (and in my case failed to prevent the error).

Edit: I have also tried using isPurgable = true when creating my bitmap.

Edit: Also, I have only tested this with Froyo (2.2.2) on Motorola Droid.

Here is the scenario: I am loading one bitmap (width: 1500, height: 2400). This takes up roughly 14 MB. The rest of the app is minuscule with regard to memory consumption (easily less than 2 MB).

I am using the bitmap with a transformation matrix to pan and zoom around on a surface view. On first load, this works perfectly. However, when I exit the app and relaunch it, I get the dreaded OutOfMemoryError. On third launch it works, on fourth it crashes ... and so on.

I don't need to save state, and so I tried calling finish() in onPause() (as well as the recycle() and gc() methods mentioned above). Finish() seems to stop all threads, but does not clear the memory.

I should mention that I am also using a technique which I found in a comment from this question. Also Please check this

So, my image is loaded from the web, as an immutable bitmap. Its bytes are then saved to sdcard (very slow) just to be reloaded back to a mutable bitmap. If jumping through all these hoops is laughable, please educate me...

For my case, clearing all memory allocated for the app would be acceptable (if it doesn't generate crash messages). Is there anyway to just totally clear the memory allocated to my app so that each restart is as clean as the first launch?

Is there any solution involving tiling? Surely I am missing something.. since the image file itself (a png) is only a few kilobytes, and I have viewed larger images in the stock gallery app without this problem.

Edit: I have determined the cause of the problem based on insight gleaned from @Jason Lebrun's answer. It turns out that the canvas I had used to draw on this bitmap held a reference too it, so that canvas needed to be set to null for it to be properly garbage collected. Hope this helps someone with a similar issue.

Community
  • 1
  • 1
snapfractalpop
  • 2,086
  • 2
  • 21
  • 28

6 Answers6

4

Are you experiencing this problem on Gingberbread, or a different version? Gingerbread has a lot of problems with apps that use a lot of Bitmap memory, so knowing the OS version can help with determining the nature of the problem.

In your case, it's hard to say exactly what might be the cause. However, with a 14MB bitmap, even a 2nd instance is likely to use up your available heap and cause a crash. On Gingerbread, it's pretty easy to end up with Bitmap memory sticking around for longer than it should, due to the concurrent GC + the fact that Bitmap memory is allocated in a native array.

When you exit the app, it's probably not being unloaded from memory. That would explain your crash pattern: the first time you run the app, the large bitmap is loaded. The 2nd time you launch it, it's actually just restarting an Activity for a process already in memory, and for some reason, the memory for the Bitmap is still hanging around taking up room. So the app crashes, and a new process is started, with a fresh heap. If recycling is not helping, you might still have a reference to the previous Bitmap sticking around. If you're always using the same Bitmap, reusing a static reference to it might help, although I'm not sure.

Are you sure that the Bitmap is not leaked via a leaked context, long running background code, or something similar?

Other answers here have good advice, which is to try tiling the Bitmap after you fetch it, and only loading tiles as necessary. If you don't need to support older versions, you can use BitmapRegionDecoder (http://developer.android.com/reference/android/graphics/BitmapRegionDecoder.html) to do this, but only on devices that support API level 10 or higher.

Jason LeBrun
  • 13,037
  • 3
  • 46
  • 42
  • I have been experimenting with Froyo (2.2.2) on Motorola Droid. I will edit the question to mention that. – snapfractalpop Mar 30 '12 at 22:07
  • +1 for BitmapRegionDecoder (which is good to know of, though in this case I'm developing for API 8). I will look into the leaked context issue..although I wish I could just annihilate the app and its memory at every onPause. – snapfractalpop Mar 30 '12 at 22:32
  • As it turns out, I did have a leak! I was destroying the bitmap, which was accessed by a separate thread in a surfaceView, but I also needed to set to null the canvas used to draw on this bitmap. Your insight helped me a lot! Now, I don't need to bother with back-porting RegionDecoder code. So, happy happy joy joy.. for now... – snapfractalpop Apr 02 '12 at 20:48
  • Update: While recycling the bitmap and setting the canvas to null helped, I found that if I exited the app and re-launched several times, I could "catch the garbage collector off guard" or "beat it to the punch", resulting in the OOM error. Using a static reference and NOT calling recycle so far is working slightly better in my case. I will still be looking to back-port RegionDecoder code, and will update my answer if I ever figure that out. – snapfractalpop Apr 05 '12 at 06:13
2

To expand on @jtietema's answer, have you tried loading/rendering only the part of your bitmap that would be visible after applying your transformation matrix? You could do this by using a bounds-only bitmap for the whole image and transforming that, and using the resulting rectangle as an input to your acquiring-bitmap-from-sd-card.

Jon O
  • 6,532
  • 1
  • 46
  • 57
  • Could you elaborate on this? Would the bounds be larger than the screen so that panning would be smooth (i.e. pre-loading tiles beyond the edge so panning is smooth)? – snapfractalpop Mar 30 '12 at 22:28
  • After a bit of searching around for the easiest way to do it, it seems like BitmapRegionDecoder is what you're looking for. See here: http://stackoverflow.com/questions/4815192/is-it-possible-to-chop-a-bitmap-to-small-pieces-without-loading-the-entire-thing/4903455#4903455 – Jon O Apr 01 '12 at 18:04
  • In the question I just linked, Romain Guy suggests going to the android git repo and pulling out the code for use in your own app if you're using pre 2.3.3. It looks like there's some native stuff that they're using, though... Have a look at the Java for yourself: http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/2.3.3_r1/android/graphics/BitmapRegionDecoder.java/ – Jon O Apr 01 '12 at 18:07
  • Thanks! Was hoping to avoid the NDK, but it has served me well in the past. I will re-comment after trying this out. – snapfractalpop Apr 02 '12 at 05:00
2

You can use something like the following to decrease the sample size. I use this method in one of my apps to display images from the assets directory. But you can play around with the sample size, I've used values of 1 for images that aren't to big (94kb) and 4 for larger images (1.9mb)

protected void loadImageIntoBitmap( String imageAssetFile, Bitmap bitmap, int sampleSize, ImageView imgvw ) throws IOException {
    InputStream is = getContext().getAssets().open( imageAssetFile );
    BitmapFactory.Options options = new BitmapFactory.Options();
    options.inSampleSize = sampleSize;
    bitmap = BitmapFactory.decodeStream( is,null,options );
    imgvw.setImageBitmap( bitmap );

    is.close();
}
mmaitlen
  • 824
  • 9
  • 17
1

What are you doing with the bitmap? The resolution is way higher than that of your android device, so if you are viewing the whole bitmap than scaling it down would do.

If you are zooming in you could create just a subset of the bitmap and just load the part that is visible.

Jeroen
  • 3,399
  • 1
  • 22
  • 25
  • I am loading the entire bitmap and instantiating a canvas for it. The surface view canvas is much smaller (as you mentioned), but the transformation matrix handles the scaling and translation. On first launch, it works (and you can pan around the zoomed in image in full detail). – snapfractalpop Mar 30 '12 at 22:02
1

well whenever you are saving the image to SD card you can use a lower quality of the image like:

myBitmap.compress(Bitmap.CompressFormat.PNG, 85, fileOutputStream);

and/or also use the bitmapFactory options to get a smaller image to save memory:

BitmapFactory.Options factoryOptions= new BitmapFactory.Options();
factoryOptions.inSampleSize = samplesize;

note that calling recicle() and gc() doesn't mean that the resources will be freed immediatly. As the docs say:

from myBitmap.recycle():

Free the native object associated with this bitmap, and clear the reference to the pixel data. This will not free the pixel data synchronously; it simply allows it to be garbage collected if there are no other references. The bitmap is marked as "dead", meaning it will throw an exception if getPixels() or setPixels() is called, and will draw nothing. This operation cannot be reversed, so it should only be called if you are sure there are no further uses for the bitmap. This is an advanced call, and normally need not be called, since the normal GC process will free up this memory when there are no more references to this bitmap.

and from System.gc():

Indicates to the VM that it would be a good time to run the garbage collector. Note that this is a hint only. There is no guarantee that the garbage collector will actually be run.

therefore your app might be still using the resources and then whenever you re open the app the system is still using the resources plus the new ones that you are generating, and that might be the reason of why you are getting the out of memory error. So in short handling a large image in memory is not a good idea.

Guillaume Darmont
  • 5,002
  • 1
  • 23
  • 35
Raykud
  • 2,488
  • 3
  • 21
  • 41
  • 1
    note that the myBitmap.compress example above is misleading. You need to use a lossy format for actual compression: `quality Hint to the compressor, 0-100. 0 meaning compress for small size, 100 meaning compress for max quality. Some formats, like PNG which is lossless, will ignore the quality setting` – sghael Mar 17 '13 at 00:28
0

This method takes in the image path and gives you a drawable without crashing

For best possible quality of image,always call this method with argument imageSizeDivide's value =1

public Drawable recurseCompressAndGetImage(String image_path,
        int imageSizeDivide) {
    try {

        Log.w("", "imageSizeDivide = " + imageSizeDivide);
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inSampleSize = imageSizeDivide;// controls the quality of image

        // Bitmap           
        Bitmap srcBmp = BitmapFactory
                .decodeFile(image_path.trim(), options);

       //next if-else block converts the image into a squire image.Remove this block if u wish
        if (srcBmp.getWidth() >= srcBmp.getHeight()) {

            dstBmp = Bitmap.createBitmap(srcBmp, srcBmp.getWidth() / 2
                    - srcBmp.getHeight() / 2, 0, srcBmp.getHeight(),
                    srcBmp.getHeight());

        } else {

            dstBmp = Bitmap.createBitmap(srcBmp, 0, srcBmp.getHeight() / 2
                    - srcBmp.getWidth() / 2, srcBmp.getWidth(),
                    srcBmp.getWidth());
        }

        dstBmp = Bitmap.createScaledBitmap(dstBmp, 400, 400, true);

        return new BitmapDrawable(mContext.getResources(), dstBmp);

    } catch (OutOfMemoryError e) {

        //reduce quality and try again
        return recurseCompressAndGetImage(image_path, imageSizeDivide * 2);

    }

}
Flying Monkey
  • 669
  • 1
  • 5
  • 13