Content is very important in a mobile app as it helps to keep a user engaged for longer durations. Might it be an app for business, social networking or an app for showcasing a brand, showing lots of text without any images makes for a dull content. So, content providers add images to keep the content alive and easy to understand. While this is exciting for everyone, a developer has to make sure content is presented in the most optimal way, without impacting the performance of the app. Moreover, the app should perform well across thousands of devices and handful of mobile OS versions. In this post, we discuss about some of the techniques we used to process and load large Bitmaps while keeping the UI responsive by avoiding excessive memory usage.
Due to the vast number of devices that run Android, it is challenging to develop an app that is rich in content. Let’s start there.
Android devices can have very little memory available for a single application. Loading 12 bitmaps of size 4MB can easily consume a heap size of 48MB, if you are not careful, bitmaps can easily consume your available memory budget leading to an application crash due to java.lang.OutofMemoryError: bitmap size exceeds VM budget.
Reduce the size of a Bitmap:
While displaying thumbnail images in a ListView or in a Photo Gallery loading images with high resolution doesn’t provide any visible benefit, but still takes up precious memory and incurs additional performance overhead. The camera on the Galaxy Nexus takes photos up to 2592x1936 pixels (5 megapixels). If the bitmap configuration used is ARGB_8888 (the default from the Android 2.3 onward) then loading this image into memory takes about 19MB of memory (2592*1936*4 bytes), immediately exhausting the per-app limit on some devices. It is not worth loading a 1024x768 pixel image into memory if it will eventually be displayed in a 128x96 pixel thumbnail in an ImageView.
We can produce a subsampled image of original image using BitmapFactory.Options class. An inSampleSize of 4 reduce the size of bitmap by a factor of 4. an image with resolution 2048x1536 that is decoded with an inSampleSize of 4 produces a bitmap of approximately 512x384. Loading this into memory uses 0.5MB rather than 8MB for the full image (assuming a bitmap configuration of ARGB_8888).
You may also be able to get away with using RGB_565 representation for the pixels, which take up half the memory of ARGB_8888 pixels.Each pixel is stored in 2 bytes rather than in 4 bytes.
We can also create a scaled down version from an existing bitmap using createScaledBitmap method of Bitmap class.
Recycle or Reuse Bitmap
Pre Android 2.3.3
We can release memory allocated to a bitmap by calling recycle() method. If we create a bitmap from another bitmap and if original bitmap is no longer needed we can reclaim its memory.
Whenever we allocate a new Bitmap object for a photo it blocks the whole application for around seventy milliseconds. If we want to give our users a smooth experience using our application, we need to bring that number down to under sixteen milliseconds.
GC_FOR_ALLOC freed 3255K, 20% free 21813K/26980K, paused 62ms, total 62ms
It took 62 milliseconds to free up 3255kb of bitmap memory and to allocate 21813kb of memory for a bitmap.
Post Android 3.0 and higher we can reuse an existing bitmaps memory resulting in improved performance, and removing both memory allocation and de-allocation.
We can use inBitmap on the BitmapFactory.Options object, and have the OS reuse an already-allocated memory block for a bitmap, rather than allocating a fresh block. The developer can then maintain an “object pool” of bitmaps, reusing ones that are no longer needed elsewhere in the app. Such object pools are a common technique, particularly among game developers, to reduce the overhead of garbage collection. However, there are certain restrictions with how inBitmap can be used. In particular, before Android 4.4 (API level 19), only equal sized bitmaps are supported.
On iOS bitmap memory management is relatively straightforward. It is better to create a thumbnail of an existing image, draw it into a buffer so that the image can be saved into a file. For ex, create a bitmap image context, use UIKit framework or Core Graphics functions to draw to it, and then obtain an image object from the context.
Reduce the size of a Bitmap:
Call UIGraphicsBeginImageContextWithOptions ( CGSize size, BOOL opaque, CGFloat scale );
to create a bitmap context and push it onto the graphics stack.
size — pass a CGSize value to specify the dimensions of the bitmap context (in points).
opaque — if your image contains transparency (an alpha channel), pass NO. Otherwise, pass YES to maximize performance.
scale — pass 0.0 for a bitmap that is scaled appropriately for the main screen of the device, or pass the scale factor of your choice.
For example, the following code snippet creates a bitmap that is 200 x 200 pixels.
UIGraphicsBeginImageContextWithOptions(CGSizeMake(100.0,100.0), NO, 2.0);
Use UIKit or Core Graphics routines to draw the content of the image into the newly created graphics context. Call the UIGraphicsGetImageFromCurrentImageContext function to generate and return a UIImage object based on what you drew. If desired, you can continue drawing and call this method again to generate additional images. Call UIGraphicsEndImageContext to pop the context from the graphics stack.
UIGraphicsGetImageFromCurrentImageContext gets an image downloaded over the Internet and draws it into an image-based context, scaled down to the size of an app icon. It then obtains an UI Image object created from the bitmap data and assigns it to an instance variable. Note that the size of the bitmap and the size of the drawn content (the size of imageRect) should match. If the content is larger than the bitmap, a portion of the content will be clipped and not appear in the resulting image.
To know more about how we implemented these in our mobile apps or learn about out capabilities, please visit http://www.imaginnovate.com Sign-up for our newsletter @ http://www.imaginnovate.com/subscribe