Android Bitmap Loading for Efficient Memory Usage
When handling bitmaps Android developers can come across the error java.lang.OutOfMemoryError: bitmap size exceeds VM budget. This usually occurs when the bitmaps are large (several megabytes uncompressed), also when running the code on older devices, or trying to load lots of bitmaps. This occurs because Android was design for mobile devices, which may have limited hardware resources, including memory, and bitmaps can require large amounts of memory.
Load a Bitmap from Resources Correctly
Despite Android not dealing with multi-megabyte bitmap loading automatically, it can still be done. Instead of loading the whole bitmap a reduced resolution version is loaded (via a technique called subsampling). Android bitmap loading is achieved via the BitmapFactory
class. The Options
settings passed to the class can reduce the size of the bitmap, saving on memory usage.
(For this Android tutorial try the code in a simple app. This article assumes that the latest Android Studio is installed, a basic app can be created and run, here called Bitmap Loading, and the code in this article can be correctly copied into Android Studio. The example code can be changed to meet your own requirements. When entering code in Studio add import statements when prompted by pressing Alt-Enter. The code for the Bitmap Loading demo project used in this article is available for importing into Android Studio.)
Bitmaps can be BIG but Android Screens can be Small
A digital image from a camera on an Android voice can vary in size, between 5 and 18 megapixels (MP) is possible (and larger). This is the uncompressed size. The file storing the image, usually a JPEG file, is smaller than the displayed image because of compression. High end cameras can produce 20 MP or higher images. A 5 MP image can have a resolution of 2560×1920, that is greater than high definition (HD) television at 1920×1080, and bigger than the resolution of the Goggle Nexus 10 tablet device at 2560×1600. What about an 18 MP image? A massive 5184×3456. The Nexus 5 phone has a HD screen (1920×1080), so why load more than 2 MP of data if the screen can only display a maximum of 2 MP.
Bitmap Subsampling to the Rescue
Using a technique called subsampling the huge photo image being viewed on the Android device only consumes as much memory as required to display it, 2 MP for a HD resolution screen. This saves precious app memory and reduces the possibility of out of memory errors. Here’s an example of loading large bitmaps on different Android devices. A 1000×1000 placeholder image was loaded into an ImageView
and the app run on two devices. One with 480x800 screen, and another with a 320×480 screen.
The one million pixel image is only using 230,400 pixels on the larger screen, and 102,400 pixels on the smaller screen. Therefore loading all one million pixels is not required. Loading only the required size for display makes sense.
Android Bitmap Loading Code from the Developer Site
There is an article on the Android Developers site called Loading Large Bitmaps Efficiently that discusses bitmap loading. When processing multiple large bitmaps it needs to be done away from the User Interface (UI) thread, for example using AsyncTask
, and use caching when appropriate. The article links to a sample app, DisplayingBitmaps.zip. This same sample is available via Android Studio using the Import Sample option (via the File then New menu option. (In early releases of the Android SDK this example app was previously called Bitmap Fun from the BitmapFun.zip file.)
The basic steps in loading a large bitmap are:
- Determine the required size (from a known size or determining the size of the target View).
- Use the BitmapFactory class to get the bitmap’s size (set inJustDecodeBounds in BitmapFactory.Options to true).
- Using 1. and 2. calculate the subsampling value (a multiple of 2) and pass it to the BitmapFactory.Options setting inSampleSize.
- Use the BitmapFactory to load a reduced size bitmap.
The following example app code performs the steps listed above.
Code to Load an Android Bitmap from a Large Resource Image
A large bitmap is needed. You may be reading this because you have a very large bitmap causing out of memory errors. If not this example code is using a large image of a Turner painting. The image size is 5684×4223, a 24 MP image! You can grab the image from Wikimedia Commons, it is available in several sizes.
The large Turner image was added to the project in a folder called drawable-nodpi under the res folder. A large placeholder image (large enough to match the screen width) was also added to the folder. It can be generated from Placeholder Graphic at openclipart.org. The layout is just an ImageView with a Button
below it:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.example.bitmaploading.MainActivity">
<ImageView
android:id="@+id/imageView1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:src="@drawable/placeholder_large"
android:adjustViewBounds="true" />
<Button
android:id="@+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/imageView1"
android:layout_centerHorizontal="true"
android:layout_marginTop="16dp"
android:text="Load" />
</RelativeLayout>
Here’s the code for the bitmap loading app:
package com.example.bitmaploading;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.ImageView;
public class MainActivity extends AppCompatActivity {
ImageView mImageView; //reference to the ImageView
int xDim, yDim; //stores ImageView dimensions
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//attach an instance of HandleClick to the Button
findViewById(R.id.button1).setOnClickListener(new HandleClick());
//reference the ImageView
mImageView=(ImageView)findViewById(R.id.imageView1);
}
//Button click loads the image
private class HandleClick implements View.OnClickListener {
public void onClick(View arg0) {
//Decode the image from resources
mImageView.setImageBitmap(decodeSampledBitmapFromResource(getResources(),
R.drawable.turner_large, xDim, yDim));
}
}
@Override
//Get the size of the Image view after the
//Activity has completely loaded
public void onWindowFocusChanged(boolean hasFocus){
super.onWindowFocusChanged(hasFocus);
xDim=mImageView.getWidth();
yDim=mImageView.getHeight();
}
//Load a bitmap from a resource with a target size
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);
}
//Given the bitmap size and View size calculate a subsampling size (powers of 2)
static int calculateInSampleSize( BitmapFactory.Options options, int reqWidth, int reqHeight) {
int inSampleSize = 1; //Default subsampling size
// See if image raw height and width is bigger than that of required view
if (options.outHeight > reqHeight || options.outWidth > reqWidth) {
//bigger
final int halfHeight = options.outHeight / 2;
final int halfWidth = options.outWidth / 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;
}
}
And here is the app code in action, loading a 24 MP image into a much smaller ImageView:
The code used in this tutorial is available in bitmap-loading.zip, ready for importing into Android Studio (select New from the File menu and then Import project). It is also available from the Android Studio example projects page, which has some additional details on importing projects into Android Studio.
Using a Open Source Library to Manage Large Bitmap Loading
This code is good for a single bitmap, however, caching and background loading is important for professional apps. One way of achieving this is to use an existing implementation, such as that provided in the Display Bitmaps sample, or existing libraries that support large bitmap loading with caching, including:
- The Glide library
- Android Universal Image Loader
- android-query (AQuery)
- Android Volley is useful for loading images over networks
See Also
- The source code for the demo in this article is in bitmap-loading.zip.
- How to Get a View Size in Android.
- See the other Android Studio example projects to learn Android app programming.
- For a full list of the articles on Tek Eye see the full site Index
Archived Comments
Girish on November 5, 2014 at 11:11 am said:
This code also throws OUTOFMEMORYEXCEPTION.
Tek Eye on November 5, 2014 at 12.45 pm said:
Double check that the code has been copied correctly.
Erro on August 3, 2015 at 12:08 am said:
Exeption problem error too, this code don't work any more.
Tek Eye on August 4, 2015 at 10.32 am said:
As per previous comment.
Author:Daniel S. Fowler Published: Updated: