3

I’m trying to create a simple file manager and I have a problem with thumbnails (bitmaps) loading. I wanted to use AsyncTask and Lru cache using these tips:

http://developer.android.com/training/displaying-bitmaps/index.html

Everything went alright until I tried to implement lru cache. I mean, without lru cache bitmaps are loading but scrolling it’s not smooth. With lru cache, scrolling is smooth but bitmaps don’t want to load at once. I have to scroll down and up and again down and then bitmaps are loading.

Here is what I mean (sorry for bad quality):

http://www.youtube.com/watch?v=Xfkd6Esx7D0

Here is my ArrayAdapter:

public class AdapterFiles extends ArrayAdapter<String>{
private int resource;
private static final String PREFERENCES_NAME = "Preferences";
private static final String CHECKBOX_FIELD = "thumbnails";
private static final String LIST_FIELD = "colorlist";

private SharedPreferences preferences;
private boolean thumbnails;
private OnClickListener onItemMenuClickListener;
private String item;
private ViewHolder viewHolder;
public static ArrayList<Integer> selectedIds = new ArrayList<Integer>();
private String colorrow;
private final ThumbnailLoader tnloader = new ThumbnailLoader();


public AdapterFiles(Context context, int textViewResourceId, int label, List<String> objects) {
        super(context, textViewResourceId, objects);
        preferences = context.getSharedPreferences(PREFERENCES_NAME, Activity.MODE_PRIVATE);
        thumbnails = preferences.getBoolean(CHECKBOX_FIELD, false);
        resource = textViewResourceId;           
}

static class ViewHolder {
    TextView label;
    ImageView ikonka;
    TextView size;
    TextView date;
    ImageButton context_menu;

}


@Override
public View getView(int position, View convertView, ViewGroup parent) {
    RelativeLayout RowView;
        item = getItem(position);
        preferences.getString(LIST_FIELD, "#FF0099CC");



        if(convertView == null) {
        RowView = new RelativeLayout(getContext());
        LayoutInflater inflater = (LayoutInflater)getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        inflater.inflate(resource, RowView, true);
        viewHolder = new ViewHolder();
        viewHolder.label = (TextView)RowView.findViewById(R.id.label);
        viewHolder.ikonka = new ImageView(getContext());
        viewHolder.ikonka = (ImageView)RowView.findViewById(R.id.icon);
        viewHolder.ikonka.setTag(item);
        viewHolder.size = (TextView)RowView.findViewById(R.id.size);
        viewHolder.date = (TextView)RowView.findViewById(R.id.date);
        viewHolder.context_menu = (ImageButton)RowView.findViewById(R.id.context_menu);
        viewHolder.context_menu.setFocusable(false);
        viewHolder.context_menu.setOnClickListener(new OnClickListener() {
            public void onClick(View v) {
                if(onItemMenuClickListener != null) onItemMenuClickListener.onClick(v);
            }
        });
        RowView.setTag(viewHolder);
        } else {
            RowView = (RelativeLayout)convertView;
            viewHolder = (ViewHolder) RowView.getTag();
        }


            colorrow = "#FF99D6EB"; 

        RowView.setBackgroundColor(selectedIds.contains(position) ? Color.parseColor(colorrow) : android.R.color.transparent);  


        File file = new File(item);
        if (file.isDirectory()){
            if(file.canRead()){
            viewHolder.ikonka.setImageResource(R.drawable.folder);
            }
            else{
            viewHolder.ikonka.setImageResource(R.drawable.foldernoway); 
            }
        }else if(item.endsWith(".doc") || item.endsWith(".docx")){
             viewHolder.ikonka.setImageResource(R.drawable.docs);}

        else if(item.endsWith(".xls") || item.endsWith(".xlsx")){
            viewHolder.ikonka.setImageResource(R.drawable.xls);}

        else if(item.endsWith(".ppt") || item.endsWith(".pptx")){
            viewHolder.ikonka.setImageResource(R.drawable.ppt);}

        else if(item.endsWith(".txt")){
            viewHolder.ikonka.setImageResource(R.drawable.txt);}

        else if(item.endsWith(".mp3") || item.endsWith(".wma") || item.endsWith(".m4a") || item.endsWith(".ogg")){
            viewHolder.ikonka.setImageResource(R.drawable.music);}

        else if(item.endsWith(".apk")){
            viewHolder.ikonka.setImageResource(R.drawable.android);}

        else if(item.endsWith(".pdf")){
            viewHolder.ikonka.setImageResource(R.drawable.adobe);}

        else if(item.endsWith(".jpg") || item.endsWith(".JPG") || item.endsWith(".png") || item.endsWith(".jpeg")){



            if(thumbnails == false){
            viewHolder.ikonka.setImageResource(R.drawable.image);   
            }else{

                tnloader.loadBitmap(item, viewHolder.ikonka);
            }
            }           
        else if(item.endsWith(".avi") || item.endsWith(".3gp") || item.endsWith(".mp4")){


                viewHolder.ikonka.setImageResource(R.drawable.video);   

        }
        else if(item.endsWith(".rar") || item.endsWith(".zip") || item.endsWith(".tar")){
            viewHolder.ikonka.setImageResource(R.drawable.zip);}

        else{
            viewHolder.ikonka.setImageResource(R.drawable.noname);
        }



        viewHolder.label.setText(file.getName());

        if (file.isDirectory()){
        viewHolder.size.setText(R.string.folder);}
        else{
            double bytes = file.length();
            double kilobytes = (bytes / 1024);
            double megabytes = (kilobytes / 1024);
            if (bytes < 6000){
        viewHolder.size.setText(bytes + " b");  
            }else{
        viewHolder.size.setText(String.format( "%.2f MB", megabytes ));}    
        }

        Date lastModDate = new Date(file.lastModified());
        SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm");  
        String formattedDateString = formatter.format(lastModDate);

        viewHolder.date.setText(formattedDateString);

        viewHolder.context_menu.setTag(new Integer(position));          

        return RowView;


    } 

    public void setOnItemMenuClickListener(
        OnClickListener onItemMenuClickListner) {
    this.onItemMenuClickListener = onItemMenuClickListner;
    }
}

and ThumbnailLoader:

public class ThumbnailLoader {

Context context;
int memClass = 80;


public void loadBitmap(String filePath, ImageView imageView) {
    final String imageKey = String.valueOf(filePath);

    final Bitmap bitmap = getBitmapFromMemCache(imageKey);
    if (bitmap != null) {
        imageView.setImageBitmap(bitmap);
    } else {
        imageView.setImageResource(R.drawable.image);
        BitmapWorkerTask task = new BitmapWorkerTask(imageView);
        task.execute(filePath);
    }

}



public static boolean cancelPotentialWork(String data, ImageView imageView) {
    final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);

    if (bitmapWorkerTask != null) {
        final String bitmapData = bitmapWorkerTask.getFilePath();
        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 Bitmap loadImageFromSdCard(String filePath, int reqWidth, int reqHeight) {

    // First decode with inJustDecodeBounds=true to check dimensions
    final BitmapFactory.Options options = new BitmapFactory.Options();
    options.inJustDecodeBounds = true;
    BitmapFactory.decodeFile(filePath, options);

    // Calculate inSampleSize
    options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);

    // Decode bitmap with inSampleSize set
    options.inJustDecodeBounds = false;
    return BitmapFactory.decodeFile(filePath, 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) {
    if (width > height) {
        inSampleSize = Math.round((float)height / (float)reqHeight);
    } else {
        inSampleSize = Math.round((float)width / (float)reqWidth);
    }
}
return inSampleSize;}

class BitmapWorkerTask extends AsyncTask<String, Void, Bitmap> {

    private String mFilePath;
    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);
    }

    public String getFilePath() {
        return mFilePath;
    }

    // Decode image in background.
    @Override
    protected Bitmap doInBackground(String... params) {
        mFilePath = params[0];       
        final Bitmap bitmap = loadImageFromSdCard(mFilePath, 72, 72);
        addBitmapToMemoryCache(String.valueOf(mFilePath), bitmap);
        return bitmap;
    }

    // Once complete, see if ImageView is still around and set bitmap.
    @Override
    protected void onPostExecute(Bitmap bitmap) {
        if (isCancelled()) {
            bitmap = null;
        }

        if (imageViewReference != null && bitmap != null) {
            final ImageView imageView = imageViewReference.get();
            final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
            if (this == bitmapWorkerTask && imageView != null) {
                imageView.setImageBitmap(bitmap);
            }
        }
    }
}

static class AsyncDrawable extends BitmapDrawable {
    private final WeakReference<BitmapWorkerTask> bitmapWorkerTaskReference;

    public AsyncDrawable(BitmapWorkerTask bitmapWorkerTask) {
        super();
        bitmapWorkerTaskReference = new WeakReference<BitmapWorkerTask>(bitmapWorkerTask);
    }

    public BitmapWorkerTask getBitmapWorkerTask() {
        return bitmapWorkerTaskReference.get();
    }
}

// Get memory class of this device, exceeding this amount will throw an
// OutOfMemory exception.

// Use 1/8th of the available memory for this memory cache.
final int cacheSize = 1024 * 1024 * memClass / 8;

public LruCache<String, Bitmap> mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {

    @Override
    protected int sizeOf(String key, Bitmap bitmap) {
        // The cache size will be measured in bytes rather than number of items.
        return bitmap.getByteCount();
    }
};

public void addBitmapToMemoryCache(String key, Bitmap bitmap) {
    if (getBitmapFromMemCache(key) == null) {
        mMemoryCache.put(key, bitmap);
    }
}

public Bitmap getBitmapFromMemCache(String key) {
    return mMemoryCache.get(key);
}}

How can I fix it?

UPDATE

I found the solution. Maybe it will help someone.

public class ThumbnailLoader{
static Context context;
int memClass = 80;

public ThumbnailLoader(Context context) {
    ThumbnailLoader.context = context;
}

public void loadBitmap(String filePath, ImageView imageView) {
    final String imageKey = String.valueOf(filePath);
    final Bitmap bitmap = getBitmapFromMemCache(imageKey);
    Bitmap preloadbitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.image);

    if (bitmap != null) {
        imageView.setImageBitmap(bitmap);
    }else if (cancelPotentialWork(filePath, imageView)) {
        BitmapWorkerTask task = new BitmapWorkerTask(imageView);
        final AsyncDrawable asyncDrawable = new AsyncDrawable(context.getResources(), preloadbitmap, task);
        imageView.setImageDrawable(asyncDrawable);
        task.execute(filePath);
    }

}

public static boolean cancelPotentialWork(String data, ImageView imageView) {
    final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);

    if (bitmapWorkerTask != null) {
        final String bitmapData = bitmapWorkerTask.getFilePath();
        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 Bitmap loadImageFromSdCard(String filePath, int reqWidth, int reqHeight) {

    // First decode with inJustDecodeBounds=true to check dimensions
    final BitmapFactory.Options options = new BitmapFactory.Options();
    options.inJustDecodeBounds = true;
    BitmapFactory.decodeFile(filePath, options);

    // Calculate inSampleSize
    options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);

    // Decode bitmap with inSampleSize set
    options.inJustDecodeBounds = false;
    return BitmapFactory.decodeFile(filePath, 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) {
    if (width > height) {
        inSampleSize = Math.round((float)height / (float)reqHeight);
    } else {
        inSampleSize = Math.round((float)width / (float)reqWidth);
    }
}
return inSampleSize;}

class BitmapWorkerTask extends AsyncTask<String, Void, Bitmap> {

    private String mFilePath;
    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);
    }

    public String getFilePath() {
        return mFilePath;
    }

    // Decode image in background.
    @Override
    protected Bitmap doInBackground(String... params) {
        mFilePath = params[0];       
        final Bitmap bitmap = loadImageFromSdCard(mFilePath, 68, 68);
        addBitmapToMemoryCache(String.valueOf(mFilePath), bitmap);
        return bitmap;
    }

    // Once complete, see if ImageView is still around and set bitmap.
    @Override
    protected void onPostExecute(Bitmap bitmap) {
        if (isCancelled()) {
            bitmap = null;
        }

        if (imageViewReference != null && bitmap != null) {
            final ImageView imageView = imageViewReference.get();
            final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
            if (this == bitmapWorkerTask && imageView != null) {
                imageView.setImageBitmap(bitmap);
            }
        }
    }
}

static class AsyncDrawable extends BitmapDrawable {
    private final WeakReference<BitmapWorkerTask> bitmapWorkerTaskReference;

    public AsyncDrawable(Resources res, Bitmap bitmap, BitmapWorkerTask bitmapWorkerTask) {
        super(res, bitmap);
        bitmapWorkerTaskReference = new WeakReference<BitmapWorkerTask>(bitmapWorkerTask);
    }

    public BitmapWorkerTask getBitmapWorkerTask() {
        return bitmapWorkerTaskReference.get();
    }
}

final int cacheSize = 1024 * 1024 * memClass / 8;

public LruCache<String, Bitmap> mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {

    @Override
    protected int sizeOf(String key, Bitmap bitmap) {
        // The cache size will be measured in bytes rather than number of items.
        return bitmap.getByteCount();
    }
};

public void addBitmapToMemoryCache(String key, Bitmap bitmap) {
    if (getBitmapFromMemCache(key) == null) {
        mMemoryCache.put(key, bitmap);
    }
}

public Bitmap getBitmapFromMemCache(String key) {
    return mMemoryCache.get(key);
}
}

And this code should be paste in place where you want to view bitmap:

ThumbnailLoader tnloader = new ThumbnailLoader(getContext());
tnloader.loadBitmap(filepath, imageView);
tshepang
  • 12,111
  • 21
  • 91
  • 136
kormateusz
  • 91
  • 6

1 Answers1

0

Check this question:

Lazy load of images in ListView

PS. i had almost the same issue and as i remember image didn't appear only after loading from net - maybe because of the caching or smth else. This link was helpfull to me.

UPDATE

Also you can store url for each imageview in tag field (setteag\gettag). Tutorial for this you can find here:

Community
  • 1
  • 1
dilix
  • 3,761
  • 3
  • 31
  • 55
  • Thanks, I've already seen this question, even I tried LazyList with changed a part of code to load image from sdcard but I had the same issue. – kormateusz Dec 18 '12 at 17:11
  • 1
    I remembered, also you have to set tag to your view (view.setTag) with url that what you want to load to your imageView and when you load it you have to check this tag with what you've downloaded. – dilix Dec 19 '12 at 07:39
  • 1
    To lear what i mean you can check this link: http://codehenge.net/blog/2011/06/android-development-tutorial-asynchronous-lazy-loading-and-caching-of-listview-images/ – dilix Dec 19 '12 at 07:42
  • Thanks, I solved my problem. The solution is in my first question. – kormateusz Dec 19 '12 at 21:25
  • And why your code didn't work and work only when you scroll back after loading? The solution is to have a standalone loader for each imageview? I think better architetural solution is 1 listView == 1 imageLoader – dilix Dec 20 '12 at 05:40