Android case study: placeholder image and memory consumption
I’m trying to optimize and lower memory footprint of an android app, and increase performance by doing that. If you want to see what’s been using up your memory you can use Eclipse MAT — Memory Analyzer Tool. You can plug it in to your Eclipse or use it as a standalone app, it’s easy to use but also have a lot of advanced options. I’ll talk about the tool itself some other time, today I’ll present a case study in which I’ve used it.
Analyzing memory consumption I saw that ImageDownloader, from internal mini framework, holds up a reference to a Bitmap object taking 2 840 000 bytes — 2.7MB on the heap. Instance of this class is an image downloader after all, so it’s not strange to hold large bitmaps but not the class itself. This class have several static bitmaps used as placeholder until actual image is downloaded. It’s clear that placeholder should not be this size so I had to investigate why this was the case.
Lets say you have a placeholder.png in yours /res/drawable folder, and its size is 500x350 px. When you load this image to a Bitmap object in memory it could take 2 800 000 bytes, ~2.67MB. How is that possible when its only 24 509 bytes ~25KB on a filesystem? What’s with all the diffrence?
[caption id=”attachment_730" align=”aligncenter” width=”500"]
First of all, image can not take as little in memory as on disk, since its compressed on the disc with png or jpeg compression. This can not be the case in memory and Bitmap has color and transparency data for each and every pixel. Once we know that, it’s easy to see how much this image should take in memory: Total pixel count is 500 x 350, we need 4 bytes per pixel (one for alpha and three colors) so that is 500 x 350 x 4 = 700 000 bytes, ~683KB.
So, image is taking 2 800 000 bytes in memory and it should take 700 000 bytes. we see at once that the difference is exactly 4x. On some other phone it could take 1 400 000 bytes or even an expected 700 000 bytes. This has to do with pixel density of your screen, is your phone ldpi, mdpi, hdpi ili xhdpi configuration. If we know that the phone in question is xhdpi the problem is becoming evident.
The issue here is that the image is in /res/drawable folder. When xhdpi phone needs an image it will look in the /res/drawable-xhdpi folder first, and if it can not find it, eventually it will load from /res/drawable folder but scale it by factor of 2! (ratio ldpi : mdpi : hdpi : xhdpi is 0.75 : 1 : 1.5 : 2 ). Does this explains why our placeholder is holding 4x memory it should? Yes, and perfectly. Image is now actually 1000 x 700px, and thats 1000 x 700 x 4 = 2 800 000 bytes.
By simply moving a image file to /res/drawable-xhdpi bitmap memory consumption will lower to expected 700 000 bytes.
So we fixed this one, but there’s no need to stop here since obviously we can lower still memory consumption for a placeholder image. You can split in half memory consumption per bitmap with one simple trick by asking OS to describe a bitmap not with 4 bytes but with 2. This is not free off course, you’ll have somewhat lesser quality but for a placeholder we can certainly live with that. How is this done: BitmapFactory decode… methods take a Options parameter by which you can configure the decoding process. One of the options is inPrefferedConfig that can take a value of Bitmap.Config.ARGB_8888 (one byte for alpha channel and one per color — red, green, blue) but also can take a value of Bitmap.Config.RGB_565 (5 bits, 6 bits and 5 bits respectively for red, green and blue)
Now every pixel will take up 2 bytes so the whole image will take up 500 x 350 x 2 = 350 000 bytes, one half of previous size.
We could go on, this resolution is probably too high for a placeholder anyway. Or we could convert it to 9patch. This modification will take us to 100KB for this bitmap and that’s a 28x difference :)