The intensive use of many images in an app can create difficulties to the responsiveness and to memory, shown by warnings like ANR (Application Not Responding) or errors like OutOfMemoryError.
Consider an app that displays images in a list using ListView, GridView or RecyclerView, for better responsiveness of the app all the images should be loaded into an array in memory, but this could lead to an error because there is a limit to the memory that an app can use, often in the range 16MB – 48MB.
The alternative is to save the images to files or a database sqlite of the device and load into memory only those currently displayed to the user but the reading time by a physical memory can be perceived by the user, and this would make slow the app or could lead to an ANR, even the same image can be charged repeatedly scrolling the list up and down.
The class LruCache saves the images in a cache then these images can be loaded only once, it is a good compromise between responsiveness and memory usage.
LruCache is part of the Support Pack that can be imported using Gradle:
dependencies { ... compile 'com.android.support:appcompat-v7:23.1.1' ... }
Create the class BitmapArray, a wrapper for a LruCache object that in this example uses generics as a Long and a Bitmap; Long id the key to access to Bitmap and you can replace it with other objects as Integer or String.
import android.graphics.Bitmap; import android.util.LruCache; public class BitmapArray { // Set how much memory is reserved for the cache of the images private final static double PERC = 0.3; private LruCache<Long, Bitmap> mMemoryCache; public BitmapArray() { // Get max available VM memory, exceeding this amount will throw an // OutOfMemory exception. Stored in kilobytes as LruCache takes an // int in its constructor. final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024); final int cacheSize = (int) (maxMemory * PERC); mMemoryCache = new LruCache<Long, Bitmap>(cacheSize) { @Override protected int sizeOf(Long key, Bitmap bitmap) { // The cache size will be measured in kilobytes rather than // number of items. return bitmap.getByteCount() / 1024; } }; } public Bitmap get(long lng) { return mMemoryCache.get(lng); } public void put(Long lng, Bitmap bitmap) { if (get(lng) == null) { mMemoryCache.put(lng, bitmap); } } }
PERC is a value between 0 and 1 and sets the percentage of memory reserved to the cache, in this example it is 30%.
The get and put methods are the getter and setter to manage the images in the cache, every time they are called the image is placed at the head of the queue of the cache, in case you tried to insert an image with cache already full the images at the tail of the queue of the cache are released.
In the adapter class that manage the list of the images you’ll probably have to write code like this:
public class MyAdapter extends ArrayAdapter<MyItem> { @Override public View getView(int position, View convertView, ViewGroup parent) { ... // bitmapArray is a reference to a BitmapArray object // lng is the key, probably derived from position argument of this method Bitmap bitmap = bitmapArray.get(lng); if(bitmap == null){ // the image is not in the cache, then load the image from the storage // maybe here you call an AsyncTask to load the image } else { // the image is in the cache, then I set the view holder.image.setImageBitmap(bitmap); } ... } // an holder class for the views of the item private static class ViewHolder { ... private ImageView image; public ViewHolder(View item) { ... image = (ImageView) item.findViewById(R.id.item_image); ... } } }
References:
LruCache
Caching Bitmaps
Manage Memory on Android 3.0 and Higher
Leave a Reply