12

I'm new to all the memory management subject, so there are a lot of things I don't understand.
I'm trying to cache an image in my app, but I'm having troubles with its memory consumption:

All of the Bitmap Chaching code is pretty much copy-pasted from here: http://developer.android.com/training/displaying-bitmaps/index.html

I debugged the code and checked the heap size in the DDMS view in eclipse, and there is about 15mb jump after these code lines:

        options.inJustDecodeBounds = false;
        return BitmapFactory.decodeResource(res, resId, options);

in the "decodeSampledBitmapFromResource" method.

The image is 1024x800, 75kb jpg file. According to what I've already seen on the internet, the amount of memory this image is supposed to take is about 1024*800*4(Bytes per pixel)=3.125mb

All of the threads regarding this subject don't say why it's taking much more memory than it should. Is there a way to cache one image with a reasonable amount of memory?

EDIT

I tried using the decodeFile method suggested on @ArshadParwez's answer below. Using this method, after the BitmapFactory.decodeStream method the memory is increased by only 3.5mb - problem solved, sort of, but I want to cache bitmaps directly from the resource.

I noticed that during the decodeResource method there are 2 memory "jumps" - one of about 3.5mb - which is reasonable, and another strange one of 14mb. What are those 14mb used for and why does this happen?

Ori Wasserman
  • 962
  • 1
  • 10
  • 24
  • How about caching it to the file system instead of active memory? – Paul Nikonowicz Aug 15 '13 at 15:07
  • @PaulNikonowicz even if it solves the problem, its presumable that the secondary memory is of flash or similar type, writing frequently to it will reduce its life-time, what is not the case for primary memory. – Diego C Nascimento Aug 15 '13 at 15:16
  • 4 bytes is for ARGB4444. Are you sure it's not argb8888? – gunar Aug 15 '13 at 15:16
  • @DiegoCNascimento secondary memory does not necessarily mean the flash card with an Android device. – Paul Nikonowicz Aug 15 '13 at 15:29
  • Even if it is 8 bytes, it should be 6.25mb. And I don't want to avoid caching, this is not a solution. There has to be a way to do it right. – Ori Wasserman Aug 15 '13 at 15:32
  • @PaulNikonowicz first I said presumable. Second, I dint said flash card, I said flash memory, the internal storage could be, and much types will be, flash or similar technology type, with a writing endurance that can be less than a million, to some millions. – Diego C Nascimento Aug 15 '13 at 17:28
  • @DiegoCNascimento ya? so? my tv has a shelf life too, that doesn't mean i won't watch movies on it. – Paul Nikonowicz Aug 15 '13 at 17:56
  • @PaulNikonowicz if there's a way to preserve it and watch movies I think you would use :-) as if there's a better way to implement the caching that is the way to go. – Diego C Nascimento Aug 15 '13 at 18:00
  • @DiegoCNascimento I've had this flash memory conversation before with someone and could not come to a conclusion. But what can we do with big data (relative for mobile) without trashing that memory? I mean, Android ships w/ a DB. – Paul Nikonowicz Aug 15 '13 at 18:03
  • @PaulNikonowicz that's not a relative talk, it's based on facts. If you worked with embedded devices, theres algorithm to preserve the write to the memory. Even Windows Embedded have protection to no write to the flash partition. Of course, we are talking about a good implementation, that is, when it could be done, if the device have low primary memory for the work, you need to use secondary, but that's does not seems the problem for a 4MB image. And, for DB you need to persist the information, cache no. – Diego C Nascimento Aug 15 '13 at 18:10
  • @DiegoCNascimento I'm not saying you are incorrect, my argument is that, as Android devs, we should not have to worry about preserving hardware other than battery life. – Paul Nikonowicz Aug 15 '13 at 18:23
  • @PaulNikonowicz I'm sorry, I can't agree with you. Not only Android developers but also others platforms developers, and it has much more than battery life. – Diego C Nascimento Aug 15 '13 at 18:31
  • @DiegoCNascimento Well, if you ever need to develop an app that works offline, your hands are tied. Otherwise, maybe the cloud is a reasonable option. – Paul Nikonowicz Aug 15 '13 at 19:00
  • @PaulNikonowicz You just need to preserve it, trying to preserve the information in primary memory when you can, but theres a time you need to write it to the secondary and there's no problem in this. Like a photo app will write the photo to the secondary memory, this is the way, but lets say a photo editor, should load to primary memory and save to secondary memory when its is done. If the programmer save every time a contrast adjust is made to the photo, this is the problem, I think you just get it the wrong way. – Diego C Nascimento Aug 15 '13 at 19:06
  • @PaulNikonowicz caching the image to the storage won't help since sooner or later he will need to show the image(plus it takes storage that he already used inside the app, so why have duplicate files?) , and for this he needs to decode it to a real bitmap, uncompressed. the reason for the high memory is the density, and you are welcomed to read about it on the post i've written. – android developer Aug 17 '13 at 16:02
  • @gunar 4 bytes per pixel is for ARGB_8888 . for ARGB_4444 which is not recommended, it's 2 bytes per pixel, and it includes alpha. – android developer Aug 25 '13 at 19:07
  • you're right! thanks for flagging that up! – gunar Aug 25 '13 at 19:11

3 Answers3

8

Images are also scaled according to the density so they can use a lot of memory.

For example, if the image file is in the drawable folder (which is mdpi density) and you run it on an xhdpi device, both the width and the height would double. Maybe this link could help you, or this one.

So in your example the bytes the image file would take are :

(1024*2)*(800*2)*4 = 13,107,200 bytes.

It would be even worse if you ran it on an xxhdpi device (like the HTC one and Galaxy S4) .

What can you do? Either put the image file in the correct density folder (drawable-xhdpi or drawable-xxhdpi) or put it in drawable-nodpi (or in the assets folder) and downscale the image according to your needs.

BTW you don't have to set options.inJustDecodeBounds = false since it's the default behavior. In fact you can set null for the bitmap options.

About down scaling you can use either google's way or my way each has its own advantages and disadvantages.

About caching there are many ways to do it. The most common one is LRU cache. There is also an alternative I've created recently (link here or here) that allows you to cache a lot more images and avoid having OOM but it gives you a lot of responsibility.

Community
  • 1
  • 1
android developer
  • 114,585
  • 152
  • 739
  • 1,270
  • 1
    ok. i forgot another tip regarding bitmaps: you can set their format, so if you don't need transparency and the quality doesn't matter much, you could use RGB_565 (uses 2 bytes per pixel) instead of the default ARGB_8888 (which uses 4 bytes per pixel) . link here: http://developer.android.com/reference/android/graphics/Bitmap.Config.html . there are also some videos regarding memory and bitmaps, even on google IO websites. i recommend to watch them, even if they don't talk about your problem. – android developer Aug 17 '13 at 09:56
7

You can use this method to pass the image and get a bitmap out of it :

public Bitmap decodeFile(File f) {
    Bitmap b = null;
    try {
        // Decode image size
        BitmapFactory.Options o = new BitmapFactory.Options();
        o.inJustDecodeBounds = true;

        FileInputStream fis = new FileInputStream(f);
        BitmapFactory.decodeStream(fis, null, o);
        fis.close();
        int IMAGE_MAX_SIZE = 1000;
        int scale = 1;
        if (o.outHeight > IMAGE_MAX_SIZE || o.outWidth > IMAGE_MAX_SIZE) {
            scale = (int) Math.pow(
                    2,
                    (int) Math.round(Math.log(IMAGE_MAX_SIZE
                            / (double) Math.max(o.outHeight, o.outWidth))
                            / Math.log(0.5)));
        }

        // Decode with inSampleSize
        BitmapFactory.Options o2 = new BitmapFactory.Options();
        o2.inSampleSize = scale;
        fis = new FileInputStream(f);
        b = BitmapFactory.decodeStream(fis, null, o2);
        fis.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
    return b;
}
arshu
  • 11,805
  • 3
  • 23
  • 21
  • I too was getting a lot of crashes like OutOfMemoryError while working with Images. So then I made this method using some mathematics and after that I could use even a 5mb size of image without any crash. – arshu Aug 15 '13 at 15:24
  • Before I try this, I want to understand the reason - are you saying that BitmapFactory.decodeStream decodes better than BitmapFactory.decodeResource? – Ori Wasserman Aug 15 '13 at 15:31
  • It's not that, I have used decodeStream because I have taken an image file as input and its using a FileInputStream and if it had been a resource file then I would have used decodeResource – arshu Aug 15 '13 at 15:36
  • so how is this better than decodeSampledBitmapFromResource as shown at http://developer.android.com/training/displaying-bitmaps/load-bitmap.html#load-bitmap ? – Ori Wasserman Aug 15 '13 at 15:39
  • The method in that link and which I using is different as I'm fixing the maximum size of the image to be 1000 pixels and moreover if you use my code you would not see the output image having much degradation, whereas the code at that link reduces the output image quality a lot. – arshu Aug 15 '13 at 15:57
  • Your method does work better, But I'm not happy with it. There must be a way to cache bitmaps directly from the resources. I just found something rather strange. I will update my question in a few minutes. – Ori Wasserman Aug 16 '13 at 13:27
  • Scaling Bitmap is not Enough , you need also to manage Caching every time we drop images. – neferpitou Jan 15 '15 at 12:36
1

@Ori Wasserman: As per your request I used a method to get images from the resource folder and that too I used a 7 MB image. I put the 7 MB image in the "res->drawable" folder and with the following code it didn't crash and the image was shown in the imageview:

 Bitmap image = BitmapFactory.decodeResource(getResources(), R.drawable.image_7mb);
 loBitmap = Bitmap.createScaledBitmap(image, width_of_screen , height_of_screen, true);
 imageview.setImageBitmap(loBitmap);
arshu
  • 11,805
  • 3
  • 23
  • 21
  • actually it will use more RAM than what you said, only for a short time. the reason is that you create both the original bitmap and a small one. you won't notice it since the large image will be disposed of on the next GC. the point where you have both is the most critical one. – android developer Aug 16 '13 at 19:51
  • You are creating 2 bitmaps to achieve 1 single objectives which is very frustrating to memory. Try using BitmapFactory.Option in this issue. – neferpitou Jan 15 '15 at 12:38