Click here to Skip to main content
14,299,849 members

Displaying Bitmaps Efficiently on Android Apps

Rate this:
5.00 (7 votes)
Please Sign up or sign in to vote.
5.00 (7 votes)
24 Aug 2016CC (ASA 3U)
To create visually engaging apps, displaying images is a must. Learn to display Bitmaps Efficiently on your Android apps, without giving away performance. The post Displaying Bitmaps Efficiently on Android apps appeared first on Isaac RF.

The Pain of Displaying Images on Android

When working on developing visually engaging apps, displaying images is a must. The problem is that Android OS doesn’t handle image decoding very well, forcing the developer to take care of certain tasks to avoid messing up performance.

At least, Google wrote a complete guide about Displaying Bitmaps Efficiently, which we can follow to understand and solve the main flaws of Android OS when displaying Bitmaps.

Android image decoding pain

Android App Performance Killers

Following Google’s guide, we can list some of the main problems we come across when displaying images on our Android app.

Image downsample

Android decodes and displays images at its full dimensions / size, regardless of the viewport size. Due to this, if you try to load a heavy image, you can easily cause an outOfMemoryError on your device.

To avoid this, as stated by Google, we should use the BitmapFactory to decode the image, setting a value for inSampleSize parameter. Image dimensions are divided by inSampleSize, reducing the amount of memory used.

public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,
        int reqWidth, int reqHeight) {

    // First decode with inJustDecodeBounds=true to check dimensions
    final BitmapFactory.Options options = new BitmapFactory.Options();
    options.inJustDecodeBounds = true;
    BitmapFactory.decodeResource(res, resId, options);

    // Calculate inSampleSize
    options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);

    // Decode bitmap with inSampleSize set
    options.inJustDecodeBounds = false;
    return BitmapFactory.decodeResource(res, resId, options);
}

You can manually set inSampleSize, or calculate it using display dimensions.

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;
}

Asynchronous Decoding

Even when using BitmapFactory, image decoding is done on UI thread. This can freeze the app and cause ANR (“Application Not Responding”) alerts.

This one is easy to solve, you just have to place the decoding process on a worker thread. One way to do this is using an AsyncTask, as explained on Google guide:

class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
    private final WeakReference<ImageView> imageViewReference;
    private int data = 0;

    public BitmapWorkerTask(ImageView imageView) {
        // Use a WeakReference to ensure the ImageView can be garbage collected
        imageViewReference = new WeakReference<ImageView>(imageView);
    }

    // Decode image in background.
    @Override
    protected Bitmap doInBackground(Integer... params) {
        data = params[0];
        return decodeSampledBitmapFromResource(getResources(), data, 100, 100));
    }

    // Once complete, see if ImageView is still around and set bitmap.
    @Override
    protected void onPostExecute(Bitmap bitmap) {
        if (imageViewReference != null && bitmap != null) {
            final ImageView imageView = imageViewReference.get();
            if (imageView != null) {
                imageView.setImageBitmap(bitmap);
            }
        }
    }
}

Image Caching

Every time an image is decoded and placed in a view, Android OS repeats the entire rendering process by default, wasting precious device memory. If you are planning to show the same image in different places, or reload it multiple times due to your app life cycle or behaviour, this can be especially annoying.

In order to avoid consuming too much memory, using a memory and a disk cache is recommended. Next, we are going to see the main differences between those caches, and why it’s useful to use both at the same time. Code is too complex to show it here, so please refer to the Caching Bitmaps section of the Google Guide to learn how you can implement your own memory and disk cache.

  • Memory Cache: Images are stored in the device memory. Memory is fast to access. Much faster, indeed, than the image decoding process, so storing images here is a good idea to make your app faster and more stable. The only cons of Memory Cache is that it is only alive during the app life cycle, which means that once the app is closed or killed (entirely or partially) by Android OS memory manager, all images stored there will be lost. Keep in mind that memory cache must set a max usable memory amount. Otherwise, it could cause the famous outOfMemoryError.
  • Disk Cache: Images are stored in the device physical storage (disk). Disk cache is kept alive between app launches, storing images safely as long as it has enough space to store them. The downside is that disk read and write operations can be slow, and are always slower than accessing memory cache. Due to this, all disk operations must be performed on a worker thread, outside the UI thread. Otherwise, your app can freeze and cause ANR alerts.

Every cache has its pros and cons, so the best practice is using both and reading images from where they are available first, starting by memory cache.

Final Thoughts and EpicBitmapRenderer

As you may have already noticed, and as I stated at the beginning of this article, showing images on your Android app can be a real headache. It is certainly not the easy task it seemed, as it is in other environments.

In order to avoid repeating these tasks in every project, I developed a 100% free, open source Android library called EpicBitmapRenderer. You can fork it on EpicBitmapRenderer GitHub repo, and learn more about it at EpicBitmapRenderer website.

EpicBitmapRenderer, Android Image Decoding Library

EpicBitmapRenderer is easy to use, and automatizes all these annoying tasks in every image decoding operation, so you can focus on your app development.

You just have to add the EpicBitmapRenderer dependency on your gradle (to see alternatives for other build tools, take a look at the Importing Library section of EpicBitmapRenderer documentation).

compile 'com.isaacrf.epicbitmaprenderer:epicbitmaprenderer:1.0'

Decoding an image in EpicBitmapRenderer is really easy: just call the desired decoding method and manage the result. Take a look at this example, where we get an image from an URL and display it on an ImageVIew.

//Sample 3: Decode Bitmap from URL (Async)
EpicBitmapRenderer.decodeBitmapFromUrl(
        "http://isaacrf.com/wp-content/themes/Workality-Lite-child/images/IsaacRF.png", 
        200, 200,
        new OnBitmapRendered() {
            @Override
            public void onBitmapRendered(Bitmap bitmap) {
                //Display rendered Bitmap when ready
                ImageView imgView = findViewById(R.id.imgSampleDecodeUrl);
                imgView.setImageBitmap(bitmap);
            }
        },
        new OnBitmapRenderFailed() {
            @Override
            public void onBitmapRenderFailed(Exception e) {
                //Take actions if Bitmap fails to render
                Toast.makeText(MainActivity.this, 
                          "Failed to load Bitmap from URL", 
                          Toast.LENGTH_SHORT).show();
            }
        });

The post Displaying Bitmaps Efficiently on Android apps appeared first on Isaac RF.

License

This article, along with any associated source code and files, is licensed under The Creative Commons Attribution-Share Alike 3.0 Unported License

Share

About the Author

Isaac RF
Software Developer (Senior) Connectis
Spain Spain
Software Developer, 2D/3D Design and game development enthusiast.

Good working on team or alone, very fast learning speed, curious and assertive, always learning new skills.

Comments and Discussions

 
QuestionGreat library ! Pin
Thierry Maurel8-Sep-16 22:56
memberThierry Maurel8-Sep-16 22:56 
AnswerRe: Great library ! Pin
Isaac RF8-Sep-16 23:25
professionalIsaac RF8-Sep-16 23:25 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Technical Blog
Posted 24 Aug 2016

Stats

8.9K views
4 bookmarked