Evgeniy Mishustin
Evgeniy Mishustin

Reputation: 3794

Load Images from net to Grid without third Party libraries

I have a list of url of images at remote server. I need to load them into Grid View asynchronously without using any third party libraries. I have built the following task:

class BitmapWorkerTask extends AsyncTask<String, Void, Bitmap> {
        private final WeakReference<ImageView> imageViewReference;


    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(String... params) {
        if (isCancelled()) {
            return null;
        }
        try {
            URL url = new URL(params[0]);
            return BitmapFactory.decodeStream(url.openConnection().getInputStream());
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }

    }

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

}`

And I'm invoking it onBindViewHolder as following:

 @Override
public void onBindViewHolder(GridHolder holder, int position) {
    final AnimalModel animal = list.get(position);
    holder.image.setTag(holder);
    holder.text.setText(animal.getName());
    new BitmapWorkerTask(holder.image).execute(animal.getImagePath());
}

I'm facing several problems that I can't find the way to deal with: 1) On scroll images lost their positions almost every time 2) On configurationChanged takes time to load images, I assume that several tasks not stopped and OS waits for them, how can I cancel all the tasks? Any help, please.

Upvotes: 1

Views: 2295

Answers (1)

Mohammad Haidar
Mohammad Haidar

Reputation: 1135

Add these 4 classes to your project

1

import android.content.Context;

import java.io.File;

public class FileCache {

private File cacheDir;

public FileCache(Context context){

    //Find the dir at SDCARD to save cached images

    if (android.os.Environment.getExternalStorageState().equals(
            android.os.Environment.MEDIA_MOUNTED))
    {
        //if SDCARD is mounted (SDCARD is present on device and mounted)
        cacheDir = new File(
                android.os.Environment.getExternalStorageDirectory(),"LazyList");
    }
    else
    {
        // if checking on simulator the create cache dir in your application context
        cacheDir=context.getCacheDir();
    }

    if(!cacheDir.exists()){
        // create cache dir in your application context
        cacheDir.mkdirs();
    }
}

public File getFile(String url){
    //Identify images by hashcode or encode by URLEncoder.encode.
    String filename=String.valueOf(url.hashCode());

    File f = new File(cacheDir, filename);
    return f;

}

public void clear(){
    // list all files inside cache directory
    File[] files=cacheDir.listFiles();
    if(files==null)
        return;
    //delete all cache directory files
    for(File f:files)
        f.delete();
}

}  

2

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Handler;
import android.widget.ImageView;

import com.example.smartlaw.R;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Collections;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ImageLoader {

    // Initialize MemoryCache
    MemoryCache memoryCache = new MemoryCache();

    FileCache fileCache;

    //Create Map (collection) to store image and image url in key value pair
    private Map<ImageView, String> imageViews = Collections.synchronizedMap(
            new WeakHashMap<ImageView, String>());
    ExecutorService executorService;

    //handler to display images in UI thread
    Handler handler = new Handler();

    public ImageLoader(Context context){

        fileCache = new FileCache(context);

        // Creates a thread pool that reuses a fixed number of
        // threads operating off a shared unbounded queue.
        executorService=Executors.newFixedThreadPool(5);

    }

    // default image show in list (Before online image download)
    final int stub_id= R.drawable.book_background;

    public void DisplayImage(String url, ImageView imageView)
    {
        //Store image and url in Map
        imageViews.put(imageView, url);

        //Check image is stored in MemoryCache Map or not (see MemoryCache.java)
        Bitmap bitmap = memoryCache.get(url);

        if(bitmap!=null){
            // if image is stored in MemoryCache Map then
            // Show image in listview row
            imageView.setImageBitmap(bitmap);
        }
        else
        {
            //queue Photo to download from url
            queuePhoto(url, imageView);

            //Before downloading image show default image
            imageView.setImageResource(stub_id);
        }
    }

    private void queuePhoto(String url, ImageView imageView)
    {
        // Store image and url in PhotoToLoad object
        PhotoToLoad p = new PhotoToLoad(url, imageView);

        // pass PhotoToLoad object to PhotosLoader runnable class
        // and submit PhotosLoader runnable to executers to run runnable
        // Submits a PhotosLoader runnable task for execution

        executorService.submit(new PhotosLoader(p));
    }

    //Task for the queue
    private class PhotoToLoad
    {
        public String url;
        public ImageView imageView;
        public PhotoToLoad(String u, ImageView i){
            url=u;
            imageView=i;
        }
    }

    class PhotosLoader implements Runnable {
        PhotoToLoad photoToLoad;

        PhotosLoader(PhotoToLoad photoToLoad){
            this.photoToLoad=photoToLoad;
        }

        @Override
        public void run() {
            try{
                //Check if image already downloaded
                if(imageViewReused(photoToLoad))
                    return;
                // download image from web url
                Bitmap bmp = getBitmap(photoToLoad.url);

                // set image data in Memory Cache
                memoryCache.put(photoToLoad.url, bmp);

                if(imageViewReused(photoToLoad))
                    return;

                // Get bitmap to display
                BitmapDisplayer bd=new BitmapDisplayer(bmp, photoToLoad);

                // Causes the Runnable bd (BitmapDisplayer) to be added to the message queue.
                // The runnable will be run on the thread to which this handler is attached.
                // BitmapDisplayer run method will call
                handler.post(bd);

        }catch(Throwable th){
            th.printStackTrace();
        }
    }
}

private Bitmap getBitmap(String url)
{
    File f=fileCache.getFile(url);

    //from SD cache
    //CHECK : if trying to decode file which not exist in cache return null
    Bitmap b = decodeFile(f);
    if(b!=null)
        return b;

    // Download image file from web
    try {

        Bitmap bitmap=null;
        URL imageUrl = new URL(url);
        HttpURLConnection conn = (HttpURLConnection)imageUrl.openConnection();
        conn.setConnectTimeout(30000);
        conn.setReadTimeout(30000);
        conn.setInstanceFollowRedirects(true);
        InputStream is=conn.getInputStream();

        // Constructs a new FileOutputStream that writes to file
        // if file not exist then it will create file
        OutputStream os = new FileOutputStream(f);

        // See Utils class CopyStream method
        // It will each pixel from input stream and
        // write pixels to output stream (file)
        Utils.CopyStream(is, os);

        os.close();
        conn.disconnect();

        //Now file created and going to resize file with defined height
        // Decodes image and scales it to reduce memory consumption
        bitmap = decodeFile(f);

        return bitmap;

    } catch (Throwable ex){
        ex.printStackTrace();
        if(ex instanceof OutOfMemoryError)
            memoryCache.clear();
        return null;
    }
}

//Decodes image and scales it to reduce memory consumption
private Bitmap decodeFile(File f){

    try {

        //Decode image size
        BitmapFactory.Options o = new BitmapFactory.Options();
        o.inJustDecodeBounds = true;
        FileInputStream stream1=new FileInputStream(f);
        BitmapFactory.decodeStream(stream1,null,o);
        stream1.close();

        //Find the correct scale value. It should be the power of 2.

        // Set width/height of recreated image
        final int REQUIRED_SIZE=85;

        int width_tmp=o.outWidth, height_tmp=o.outHeight;
        int scale=1;
        while(true){
            if(width_tmp/2 < REQUIRED_SIZE || height_tmp/2 < REQUIRED_SIZE)
                break;
            width_tmp/=2;
            height_tmp/=2;
            scale*=2;
        }

        //decode with current scale values
        BitmapFactory.Options o2 = new BitmapFactory.Options();
        o2.inSampleSize=scale;
        FileInputStream stream2=new FileInputStream(f);
        Bitmap bitmap=BitmapFactory.decodeStream(stream2, null, o2);
        stream2.close();
        return bitmap;

    } catch (FileNotFoundException e) {
    }
    catch (IOException e) {
        e.printStackTrace();
    }
    return null;
}

boolean imageViewReused(PhotoToLoad photoToLoad){

    String tag=imageViews.get(photoToLoad.imageView);
    //Check url is already exist in imageViews MAP
    if(tag==null || !tag.equals(photoToLoad.url))
        return true;
    return false;
}

//Used to display bitmap in the UI thread
class BitmapDisplayer implements Runnable
{
    Bitmap bitmap;
    PhotoToLoad photoToLoad;
    public BitmapDisplayer(Bitmap b, PhotoToLoad p){bitmap=b;photoToLoad=p;}
    public void run()
    {
        if(imageViewReused(photoToLoad))
            return;

        // Show bitmap on UI
        if(bitmap!=null)
            photoToLoad.imageView.setImageBitmap(bitmap);
        else
            photoToLoad.imageView.setImageResource(stub_id);
    }
}

public void clearCache() {
    //Clear cache directory downloaded images and stored data in maps
    memoryCache.clear();
    fileCache.clear();
}

}

3

 import android.graphics.Bitmap;
import android.util.Log;

import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;

public class MemoryCache {

    private static final String TAG = "MemoryCache";

    //Last argument true for LRU ordering
    private Map<String, Bitmap> cache = Collections.synchronizedMap(
            new LinkedHashMap<String, Bitmap>(10,1.5f,true));

    //current allocated size
    private long size=0;

    //max memory cache folder used to download images in bytes
    private long limit=1000000;

    public MemoryCache(){

        //use 25% of available heap size
        setLimit(Runtime.getRuntime().maxMemory()/4);
    }

    public void setLimit(long new_limit){

        limit=new_limit;
        Log.i(TAG, "MemoryCache will use up to "+limit/1024./1024.+"MB");
    }

    public Bitmap get(String id){
        try{
            if(!cache.containsKey(id))
                return null;

            return cache.get(id);

        }catch(NullPointerException ex){
            ex.printStackTrace();
            return null;
        }
    }

    public void put(String id, Bitmap bitmap){
        try{
            if(cache.containsKey(id))
                size-=getSizeInBytes(cache.get(id));
            cache.put(id, bitmap);
            size+=getSizeInBytes(bitmap);
            checkSize();
        }catch(Throwable th){
            th.printStackTrace();
        }
    }

    private void checkSize() {
        Log.i(TAG, "cache size="+size+" length="+cache.size());
        if(size>limit){

            //least recently accessed item will be the first one iterated
            Iterator<Entry<String, Bitmap>> iter=cache.entrySet().iterator();

            while(iter.hasNext()){
                Entry<String, Bitmap> entry=iter.next();
                size-=getSizeInBytes(entry.getValue());
                iter.remove();
                if(size<=limit)
                    break;
            }
            Log.i(TAG, "Clean cache. New size "+cache.size());
        }
    }

    public void clear() {
        try{
            // Clear cache
            cache.clear();
            size=0;
        }catch(NullPointerException ex){
            ex.printStackTrace();
        }
    }

    long getSizeInBytes(Bitmap bitmap) {
        if(bitmap==null)
            return 0;
        return bitmap.getRowBytes() * bitmap.getHeight();
    }
}

4

    import java.io.InputStream;
import java.io.OutputStream;

public class Utils {
    public static void CopyStream(InputStream is, OutputStream os)
    {
        final int buffer_size=1024;
        try
        {

            byte[] bytes=new byte[buffer_size];
            for(;;)
            {
                //Read byte from input stream

                int count=is.read(bytes, 0, buffer_size);
                if(count==-1)
                    break;

                //Write byte from output stream
                os.write(bytes, 0, count);
            }
        }
        catch(Exception ex){}
    }
}

And just use it like this in the Adapter getView() method

public View getView(final int position, View convertVie, ViewGroup parent) {
        if (convertView == null) {

            inflater = (LayoutInflater) mContext
                    .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            convertView = inflater.inflate(R.layout.book_item_to_download, parent, false);

            holder = new Holder();

            holder.BookImage = (ImageView) convertView.findViewById(R.id.bookBackground);


            convertView.setTag(holder);
        } else {
            holder = (Holder) convertView.getTag();
        }



        ImageView image = holder.BookImage;

        //DisplayImage function from ImageLoader Class
        imageLoader.DisplayImage(url, image);

        //Then do whatever you want here

        return convertView;
    }

This will load the images to each position in background so that no scrolling issues will happen and it will keep the images in cache so if requested if phone oriented or whatever i will not load all over agian

Upvotes: 4

Related Questions