Memory-efficient Bitmap caching with Mono for Android
Although phones these days have more memory than your last year laptop, mobile development is still a memory contrived environment.
Although Android is fairly lenient when it comes to the memory eaten by an application, it can still decide that you are using too much memory and early kill you or stop giving you more.
Even if you are lucky, another reason to optimize memory usage in your application is that, since both Android and Mono uses pause-based GC, the less time your GC has to be run, the less your application will be suspended to let the system reclaims memory.
This is particularly important if your application uses animations as they will be stopped during GC cleanup and make your application appears sluggish to the user. The same is true with ListView scrolling when you instantiate big objects for each row.
A case of big objects are Bitmap instances. Android has some optimization baked in for drawable resources but if your application needs to instantiate Bitmap manually with BitmapFactory (for example because you download them from the Internet) you will rapidly consume a lot of memory if you keep those around.
At the same time, you don’t want to have to toss these Bitmap instances completly as they can be expensive to fetch again (network cost). The solution is to serialize them to disk and only keep the “hot” ones in memory.
Unfortunately, although the Android documentation preconises this model, they don’t offer a default implementation in the framework and instead let you copy code from the samples included in the SDK.
Since I had a need for something similar, I cooked up a trimmed version of that cache with an added LRU queue on top to keep the most valuables Bitmap in memory. The code for the disk cache is given below:
The algorithm it’s based on is very simple and if you know how filesystem/databases work you will feel at home. The disk cache keeps a journal composed of lines of operation (create, delete, modify) associated with keyname and optionally timestamps.
When you do an operation on the cache, the content you are pushing is first serialized to disk and then an entry is created in the journal to commit the action. This order ensures that even in the unlikely case your application crashes in the middle, a given Bitmap content will never be corrupted (it will simply seem as if it didn’t exist).
When the application is restarted, the cache will open that journal and replay all the operations to arrive at the same internal state it had beforehand. To account for out-of-date entries, a thread is also spawned when the cache is re-created to cleanup files that have expired.
The code I’m giving is simpler and less feature-full than the original Java code but it suits my need and is much shorter. Still, among possible additional features you could add:
Journal trimming: for when the journal files grows too large, filter out ops line that cancel each other i.e. when encountering a delete operation, you can filter out all the previous create and modify operations on that same key.
Fsync support: for those who are really religious about consistency, you could add proper barrier to prevent the filesystem from not following the code order and writing out the log file before the Bitmap content.
More generic and better memory usage: see Jonathan comment in this gist