59

The project I'm working on uses several "high resolution" backgrounds (note the quotes). Just to get into situation, one of them is a 640x935 1.19M PNG file. As far as I know, even if Android decompresses images into memory as raw data this should be:

640 x 935 x 4bytes = 2.39M

I'm having memory issues on my project which I cannot really understand and I hope someone can shed some light on this matter. I'll name two devices that I am developing in and some of the results.

To make sure this wasn't a secondary problem I made an activity not load the background when first created and then, when the user presses a button, all it does is:

findViewById(R.id.completed_block_background).setBackgroundResource(R.drawable.blockbackgroundbottom1);

Then, using DDMS with "Update Heap" on the process (and first forcing GC to make sure this won't be a problem), I'm getting the following memory results:

Nexus S: Going from 18M to 26M (8M difference)

Galaxy Nexus: Going from 28M to 39M (11M difference)

So, as you can see, putting that theorically 2.39M uncompressed image into the background actually increases 8M and 11M in memory usage. Can someone explain why is this and if there is any solution?

The only solution I have been able to find is using bitmaps to halve resolution or to lower the channel format (so far this is what I have done, switched them to 565 RGB, but this makes some banding problems which I cannot accept).

I would also accept, in case there's nothing that can be done, an explanation of why this is happening. Thanks in advance.

h4lc0n
  • 2,730
  • 5
  • 29
  • 41
  • What makes you think tha your 1.19MB PNG file will only take up 2.12MB of heap space? I would expect it to take up significantly more than that. – CommonsWare Oct 29 '12 at 09:51
  • 2
    @CommonsWare he has shown the calculations: 640 x 935 x 4bytes = 2.39M, can you spot his mistake? – lenik Oct 29 '12 at 10:00
  • @lenik: I haven't seen a PNG with such a lousy compression ratio. – CommonsWare Oct 29 '12 at 10:05
  • @CommonsWare it's a very granulated image, I suppose that might be why the compression ratio is that low. Anyways I converted the image to a raw BMP image to make sure I wasn't making anything up and, yup, it's 2.39M, so I'm still stuck with why the device needs so much memory for that :/ – h4lc0n Oct 29 '12 at 10:41
  • Is the size of `R.id.completed_block_background` precisely 640x935? – CommonsWare Oct 29 '12 at 10:59
  • @CommonsWare I just created a bitmap using BitmapFactory.decodeResource and getWidth() and getHeight() both return 640 and 935 respectively. – h4lc0n Oct 29 '12 at 11:16
  • That is not what I asked. Is the size of `R.id.completed_block_background` precisely 640x935? – CommonsWare Oct 29 '12 at 11:25
  • @CommonsWare aaah I get what you mean now... no, absolutely not, that's a linearlayout that spans the whole background. Is that why it's making the image so big? Is there any way to make it keep the original image size in any way? – h4lc0n Oct 29 '12 at 11:33

1 Answers1

117

Is that why it's making the image so big?

Well, what's happening is that setBackgroundResource(R.drawable.blockbackgroundbottom1) is going to cause Android to first do the BitmapFactory.decodeResource() thing you that experimented with, but then have the rendering logic scale the image to apply it as a background. So, for example, the 3MB difference between the Galaxy Nexus and the Nexus S probably reflects the size difference, in pixels, between the renditions of the LinearLayout.

There may also be some resampling going on based on screen density, depending upon where you have stored this image in your resource tree.

Is there any way to make it keep the original image size in any way?

Off the cuff, I would first try putting it in res/drawable-nodpi/ (to prevent any automatic density-based resampling), then manually get the Bitmap via the version of BitmapFactory.decodeResource() that takes the BitmapFactory.Options, so you can get it scaled as it is being read in. If that does not seem to help much, you may need to move the PNG out of drawable resources and into a raw resource or assets, as Android might still try holding onto an un-scaled copy of the image. I don't think that it will if you use BitmapFactory.decodeResource() directly yourself, but I cannot rule it out.

CommonsWare
  • 986,068
  • 189
  • 2,389
  • 2,491
  • 13
    Brilliant... just brilliant! You saved me from a lot of headaches. Moving the image to drawable-nodpi only increases memory usage with barely 1M now... Thanks! – h4lc0n Oct 29 '12 at 11:50
  • 4
    quite possibly the most important memory management quick fix I've ever seen on SO if you're developing Android apps with large/numerous images – whyoz Feb 20 '14 at 23:34
  • 1
    could someone post the code of how to set an image as a scaled background using this method? – Mohamed Hafez Oct 02 '14 at 08:49
  • Is it as simple as placing the image in res/drawable-nodpi instead of in res/drawable? – Mohamed Hafez Oct 02 '14 at 08:56
  • 2
    Answered my own question, looks like it is indeed that simple! – Mohamed Hafez Oct 02 '14 at 10:53
  • Excellent "drawable-nodpi" stuff! We should give you 1 buck for every Mb saved, so i owe you around 10 bucks now... – Benjamin Piette Oct 08 '14 at 13:28
  • "as Android might still try holding onto an un-scaled copy of the image"?why? – zionpi Feb 02 '16 at 02:58
  • @zionpi: Android does its own resource caching. – CommonsWare Feb 02 '16 at 11:27
  • drawable-nodpi is the solution here – dtbarne Apr 18 '16 at 17:54
  • By moving the image from drawable to drawable-nodpi, I reduced memory usage from 47MB to 23MB; however it is still too much. Then I tried the Bitmap and the usage went back up to 33MB. Still can't get to the min of 3MB. Why? – teddy Oct 27 '16 at 16:32
  • @HackingBear: I suggest you ask a separate Stack Overflow question, where you provide a [mcve] demonstrating your problem. – CommonsWare Oct 27 '16 at 16:33