Today I’m going to write about some of the issues I faced while developing an Image Gallery with caching techniques, efficient bitmap loading and what tactics that I’ve taken to overcome the issues.
I have developed a custom image gallery with the use of android GridView. Key requirement was to load large amount of images with limited time. In addition the user should be able to smoothly scroll up-down the gallery control.
In order to accomplish this, I have used caching techniques like LruCache, scale down the images before loading (low memory consumption) and loading the images asynchronously (with the use of AsyncTask).
I experienced the above mentioned EMFILE(Too many open files) exception while I was testing this. At this time there were 50-100 images were loaded into the Image Gallery and I was scrolling up and down for around 10-20 times.
EMFILE(Too many open files)
This was happening due a limitation in Android(We can say this is an inherited behavior of Linux). The reason behind this is Android limits the max open files per process to 1024. So this can be experienced in a scenario like Image Gallery with large amount of files, unless we carefully handle the file processing techniques.
But finally got a clue.
It was the process of scaling down the images. It had few FileInputStream objects which were not closed after consuming.
protected static Bitmap decodeFile(File imageFile, int requiredHeight, int requiredWidth) throws Exception {
// Decode image size
BitmapFactory.Options oSize = new BitmapFactory.Options();
oSize.inJustDecodeBounds = true;
oSize.inDither = false;
oSize.inPurgeable = true;
oSize.inInputShareable = true;
//FileInputStream is used, but it's not closing after consuming...
BitmapFactory.decodeStream(new FileInputStream(imageFile), null, oSize);
// Find the correct scale value. It should be the power of 2.
int width_tmp = oSize.outWidth, height_tmp = oSize.outHeight;
int scale = 1;
while (true) {
if (width_tmp <= requiredWidth || height_tmp <= requiredHeight)
break;
width_tmp /= scale;
height_tmp /= scale;
scale *= 2;
}
// Decode with inSampleSize
BitmapFactory.Options oPreview = new BitmapFactory.Options();
oPreview.inSampleSize = scale;
oPreview.inDither = false;
oPreview.inPurgeable = true;
oPreview.inJustDecodeBounds = false;
oPreview.inInputShareable = true;
//FileInputStream is used, but it's not closing after consuming...
return BitmapFactory.decodeStream(new FileInputStream(imageFile), null, oPreview);
}
Following is the modified version of the code block, and I was able to get rid of EMFILE(Too many open files) exception.
protected static Bitmap decodeFile(File imageFile, int requiredHeight, int requiredWidth) throws Exception {
FileInputStream fileInputStreamIn = null;
FileInputStream fileInputStreamOut = null;
try{
// Decode image size
BitmapFactory.Options oSize = new BitmapFactory.Options();
oSize.inJustDecodeBounds = true;
oSize.inDither = false;
oSize.inPurgeable = true;
oSize.inInputShareable = true;
fileInputStreamIn = new FileInputStream(imageFile);
BitmapFactory.decodeStream(fileInputStreamIn, null, oSize);
// Find the correct scale value. It should be the power of 2.
int width_tmp = oSize.outWidth, height_tmp = oSize.outHeight;
int scale = 1;
while (true) {
if (width_tmp <= requiredWidth || height_tmp <= requiredHeight)
break;
width_tmp /= scale;
height_tmp /= scale;
scale *= 2;
}
// Decode with inSampleSize
BitmapFactory.Options oPreview = new BitmapFactory.Options();
oPreview.inSampleSize = scale;
oPreview.inDither = false;
oPreview.inPurgeable = true;
oPreview.inJustDecodeBounds = false;
oPreview.inInputShareable = true;
fileInputStreamOut = new FileInputStream(imageFile);
return BitmapFactory.decodeStream(fileInputStreamOut, null, oPreview);
}
finally{
//Got rid of EMFILE Exception(Too many file open)
if(fileInputStreamIn != null)
fileInputStreamIn.close();
if(fileInputStreamOut != null)
fileInputStreamOut.close();
}
}
OOM(OutOfMemory)
The application got crashed, soon after I left the screen which had the Image Gallery. This was due to OOM Exception. This issue was also bit tricky, since there was no straight forward way of identifying the reason behind this. The Image Gallery (Custom GridView) Adapter was fully enhanced to reuse the views (using of ViewHolder pattern which Android has suggested), so I thought this wasn’t causing the error. Then my eyes turned into the implementation of LruCache. There was a slight mistake in LruCache, because when the time of initializing it I have used a fixed size for it.
So, DON’T DO THIS,
mMemoryCache = new LruCache<String, Bitmap>(30)
because what Android has suggested is, set the size of LruCache dynamically depending of the available memory.
final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
final int cacheSize = maxMemory / 8;
mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
@Override
protected int sizeOf(String key, Bitmap bitmap) {
return (bitmap.getRowBytes() * bitmap.getHeight()) / 1024;
}
};
With the help of above modification, I was able to get rid of the OOM Exception.
Happy Coding…. 🙂