Skip to main content

Faster Loading images in GridViews or ListViews in Android using Menory Caching, Complete implemenation with sample code.

This example shows you how to load large images in the gridview without any lag or without outofmemory error.
This sample application uses the LruCache to hold the images.
A cache that holds strong references to a limited number of values. Each time a value is accessed, it is moved to the head of a queue. When a value is added to a full cache, the value at the end of that queue is evicted and may become eligible for garbage collection.

Read more from here.

http://developer.android.com/reference/android/util/LruCache.html

This is the MainActivity that holds the GridView. We are adding here 100 images in the Grid to load. This time I am loading images from the resources.

MainActivity.java
 
import java.util.ArrayList;
 
import android.app.Activity;
import android.os.Bundle;
import android.widget.GridView;
 
public class MainActivity extends Activity {
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
 
        super.onCreate(savedInstanceState);
 
        ArrayList<string> items = new ArrayList<string>();
 
        // adding 100 images
        for (int p = 0; p < 100; p++) {
            items.add("photo");
        }
 
        ListAdapter list = new ListAdapter(this, items);
 
        GridView grid = new GridView(this);
 
        grid.setAdapter(list);
 
        grid.setNumColumns(GridView.AUTO_FIT);
 
        setContentView(grid);
 
    }
 
}
 
Now that our Activity is complete we will see the ListAdapter class that is processing each image and helps loading fast.

ListAdapter.java
 
import java.lang.ref.WeakReference;
import java.util.ArrayList;
 
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.AsyncTask;
import android.util.LruCache;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.GridLayout.LayoutParams;
import android.widget.GridView;
import android.widget.ImageView;
 
public class ListAdapter extends BaseAdapter {
 
    Context context;
    ArrayList items;
    private LruCache mMemoryCache;
 
    public ListAdapter(Context context, ArrayList items) {
        this.context = context;
        this.items = items;
 
        // Get memory class of this device, exceeding this amount will throw an
        // OutOfMemory exception.
        final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
 
        // Use 1/8th of the available memory for this memory cache.
        final int cacheSize = maxMemory / 8;
 
        mMemoryCache = new LruCache(cacheSize) {
 
            protected int sizeOf(String key, Bitmap bitmap) {
                // The cache size will be measured in bytes rather than number
                // of items.
                return bitmap.getByteCount();
            }
 
        };
    }
 
    @Override
    public int getCount() {
        return items.size();
    }
 
    @Override
    public Object getItem(int arg0) {
        return items.get(arg0);
    }
 
    @Override
    public long getItemId(int arg0) {
        return arg0;
    }
 
    @Override
    public View getView(int arg0, View convertView, ViewGroup arg2) {
        ImageView img = null;
 
        if (convertView == null) {
            img = new ImageView(context);
            img.setScaleType(ImageView.ScaleType.CENTER_CROP);
            img.setLayoutParams(new GridView.LayoutParams(
                    LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
        } else {
            img = (ImageView) convertView;
        }
 
        int resId = context.getResources().getIdentifier(items.get(arg0),
                "drawable", context.getPackageName());
 
        loadBitmap(resId, img);
 
        return img;
    }
 
    public void loadBitmap(int resId, ImageView imageView) {
        if (cancelPotentialWork(resId, imageView)) {
            final BitmapWorkerTask task = new BitmapWorkerTask(imageView);
            imageView.setBackgroundResource(R.drawable.empty_photo);
            task.execute(resId);
        }
    }
 
    static class AsyncDrawable extends BitmapDrawable {
        private final WeakReference bitmapWorkerTaskReference;
 
        public AsyncDrawable(Resources res, Bitmap bitmap,
                BitmapWorkerTask bitmapWorkerTask) {
            super(res, bitmap);
            bitmapWorkerTaskReference = new WeakReference(
                    bitmapWorkerTask);
        }
 
        public BitmapWorkerTask getBitmapWorkerTask() {
            return bitmapWorkerTaskReference.get();
        }
    }
 
    public static boolean cancelPotentialWork(int data, ImageView imageView) {
        final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
 
        if (bitmapWorkerTask != null) {
            final int bitmapData = bitmapWorkerTask.data;
            if (bitmapData != data) {
                // Cancel previous task
                bitmapWorkerTask.cancel(true);
            } else {
                // The same work is already in progress
                return false;
            }
        }
        // No task associated with the ImageView, or an existing task was
        // cancelled
        return true;
    }
 
    private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) {
        if (imageView != null) {
            final Drawable drawable = imageView.getDrawable();
            if (drawable instanceof AsyncDrawable) {
                final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable;
                return asyncDrawable.getBitmapWorkerTask();
            }
        }
        return null;
    }
 
    public void addBitmapToMemoryCache(String key, Bitmap bitmap) {
        if (getBitmapFromMemCache(key) == null) {
            mMemoryCache.put(key, bitmap);
        }
    }
 
    public Bitmap getBitmapFromMemCache(String key) {
        return (Bitmap) mMemoryCache.get(key);
    }
 
    class BitmapWorkerTask extends AsyncTask {
        public int data = 0;
        private final WeakReference imageViewReference;
 
        public BitmapWorkerTask(ImageView imageView) {
            // Use a WeakReference to ensure the ImageView can be garbage
            // collected
            imageViewReference = new WeakReference(imageView);
        }
 
        // Decode image in background.
        @Override
        protected Bitmap doInBackground(Integer... params) {
            data = params[0];
            final Bitmap bitmap = decodeSampledBitmapFromResource(
                    context.getResources(), data, 100, 100);
            addBitmapToMemoryCache(String.valueOf(params[0]), bitmap);
            return bitmap;
        }
 
        // 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);
                }
            }
        }
    }
 
    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);
    }
 
    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) {
 
            // Calculate ratios of height and width to requested height and
            // width
            final int heightRatio = Math.round((float) height
                    / (float) reqHeight);
            final int widthRatio = Math.round((float) width / (float) reqWidth);
 
            // Choose the smallest ratio as inSampleSize value, this will
            // guarantee
            // a final image with both dimensions larger than or equal to the
            // requested height and width.
            inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
        }
 
        return inSampleSize;
    }
 
}

Comments

  1. Hello Great post thanks
    I'm also interested on the modified version of this sample for using it to download from a URL and caching it in the disk and Memory
    Could you please send me the source of that version?
    Thanks in advance, and have a nice day :)

    ReplyDelete
  2. The code is great at speeding up loading in the grid. Since you're using the same picture perhaps you haven't noticed a problem with it. Or at least I'm having trouble when loading multiple images (1500 to be precise) into the grid. As I scroll down, multiple mirages (previously visualized images) stay loaded, and then update to the actual ones. I don't know how should I modify the code. The only thing I changed was the BitmapFactory.decodeResource, to BitmapFactory.decodeFile to load an array list of files that points to a single folder. Any ideas? Thanks!

    ReplyDelete
  3. The code is great at speeding up loading in the grid. Since you're using the same picture perhaps you haven't noticed a problem with it. Or at least I'm having trouble when loading multiple images (1500 to be precise) into the grid. As I scroll down, multiple mirages (previously visualized images) stay loaded, and then update to the actual ones. I don't know how should I modify the code. The only thing I changed was the BitmapFactory.decodeResource, to BitmapFactory.decodeFile to load an array list of files that points to a single folder.

    ReplyDelete
  4. This comment has been removed by the author.

    ReplyDelete

Post a Comment

Popular posts from this blog

Spannable String in Android - URL Span ,Clickable Span, Rich-Style Formatting of Textview .....

See more Android Tutorials here....... Faster Loading images in GridViews or ListViews Spannable brings lots of possibility to TextView, includes displaying various appearance of a Text and onClick callbak. The SpannableString class allows you to easily format certain pieces which are called spans of a string, by applying CharacterStyle ie,color, font, ormake it a link . Here is an example where, explained how to use spannable string to give font size, color, linking a text via clickable span and through URL Span and to strike through the text. Lets go through the example : import android.os.Bundle; import android.text.SpannableString; import android.text.method.LinkMovementMethod; import android.text.style.ClickableSpan; import android.text.style.ForegroundColorSpan; import android.text.style.RelativeSizeSpan; import android.text.style.StrikethroughSpan; import android.text.style.URLSpan; import android.view.View; import android.widget.TextView; import android.widget.Toast; ...

Use Your Own SQLite in ANDROID Application or Using 'SQLite Asset Helper' to read your own databases stored in Asset folder

See more basics on Android along with interview questions See SQLite tutorial here If you want to read your own databases in ANDROID APPLICATION, this post may help you. You may be familiar by creating SQLite in Android use  SQLiteOpenHelper . Here we are going to deal with using our own databases.  I have a database named 'mydatabase.sqlite', which consist of a set of name and age of 5 students. Suppose I want to view the database, I can use SQLite database browser which you can download from here.                                                     download browser..... Through this browser we can view the databases, can even modify delete or create a new one. 'mydatabase.sqlite' with name and age of five students is browsed and viewed as shown below Now  make a zip format of database and copy this database to asse...

Getting started with IBM worklight Mobile App In Eclipse using Ionic / Angularjs

Install Eclipse and add IBM MobileFirst Platform Studio 7.1.0 .  You can follow the following steps : Goto Help > > Eclipse Market Place >> and search for IBM MobileFirst Platform Studio 7.1.0 . and u can add and install thet plugin and get started with ionic - angular app development. You can start the new project and so can add your Environment needed and start development. After creating the sample project you can add ionic bundle folder inside your application so that you get all the necessary features of ionic development. Details and other way of installation is explained here