Take photos Smartly-Part 2

This blog is in continuation with my first one where I explained the basics of how to take full-sized photos in android.

Here are a few performance tips to make your images load smoothly across your application.

  1. Be sure to scale you bitmaps using the createScaledBitmap function

This becomes necessary when the size of the image in memory is more than the size of the image you want to display.

Lets say you are building a gallery app where users can scroll through your photos. Here you are usually displaying only thumbnails of the images which is much smaller than the dimensions of image in memory.

This problem is so common that platform provides a function createScaledBitmap which will scale your bitmaps on demand.

2. If you didn’t notice, having a previously loaded bitmap for createScaledBitmap is kind of a dealbreaker for most app developers. Instead, they prefer to find a way to do the resizing of their bitmaps at load time, which is something that the inSampleSize flag of your bitmap’s Option object can do for you. Having this property set to a non-1 value will result in an image that’s a fraction of the size of the original without having to load in the full sized image. For example, setting inSampleSize to 2 returns an image that’s half the size. And setting it to 4 returns an image that’s 1/4 the size.

public static int calculateInSampleSize(
BitmapFactory.Options options, int reqWidth, int reqHeight) {
// Raw height and width of image
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;

if (height > reqHeight || width > reqWidth) {

final int halfHeight = height / 2;
final int halfWidth = width / 2;

// Calculate the largest inSampleSize value that is a power of 2 and keeps both
// height and width larger than the requested height and width.
while ((halfHeight / inSampleSize) >= reqHeight
&& (halfWidth / inSampleSize) >= reqWidth) {
inSampleSize *= 2;
}
}

return inSampleSize;
}

3. Another caveat about loading images in android is as user scrolls through hundreds of images, multiple GC events are called to free the bitmaps which have lost user focus. This can cause long pauses and users take notice. Here the concept of maintaining object pools in memory can help a lot! Object pools are a common technique for high-churn data types. When you’re done with an object, rather than freeing it back to the memory heap, you keep a reference to it in the list of available objects. The next time you need a new object of that same type, you can repurpose the existing object from the pool rather than grabbing a brand new one from the memory heap.

Extending this idea to bitmaps can be tricky as Android image-loading APIs by default will load your image data and create new bitmap on the heap for them to reside in. But you can actually work around this limitation. Rather than creating a brand new bitmap, you can signal the decoder to use an existing piece of memory to load that bitmap into. This is easily done using the inBitmap property of your bitmap options object. When you assign the inBitmap property to an existing bitmap, any of your decode or load calls will reuse that existing bitmap for the incoming pixel data rather than allocating a new object from the heap. Now, when the user starts scrolling through 10,000 profile photos, you don’t need to create 10,000 separate bitmap objects. Instead you only need to allocate the maximum number of bitmaps that could be visible and just reuse those new bitmaps as they scroll onto the screen.

private static void addInBitmapOptions(BitmapFactory.Options options,
ImageCache cache) {
// inBitmap only works with mutable bitmaps, so force the decoder to
// return mutable bitmaps.
options.inMutable = true;

if (cache != null) {
// Try to find a bitmap to use for inBitmap.
Bitmap inBitmap = cache.getBitmapFromReusableSet(options);

if (inBitmap != null) {
// If a suitable bitmap has been found, set it as the value of
// inBitmap.
options.inBitmap = inBitmap;
}
}
}

// This method iterates through the reusable bitmaps, looking for one
// to use for inBitmap:
protected Bitmap getBitmapFromReusableSet(BitmapFactory.Options options) {
Bitmap bitmap = null;

if (mReusableBitmaps != null && !mReusableBitmaps.isEmpty()) {
synchronized (mReusableBitmaps) {
final Iterator<SoftReference<Bitmap>> iterator
= mReusableBitmaps.iterator();
Bitmap item;

while (iterator.hasNext()) {
item = iterator.next().get();

if (null != item && item.isMutable()) {
// Check to see it the item can be used for inBitmap.
if (canUseForInBitmap(item, options)) {
bitmap = item;

// Remove from reusable set so it can't be used again.
iterator.remove();
break;
}
} else {
// Remove from the set if the reference has been cleared.
iterator.remove();
}
}
}
}
return bitmap;
}

Though, this is a great time to point out there are actually open source libraries already handling this for you. Checkout Glide and picasso. Anyways its always great to know about how things are working behind the scenes.