the gridview in the below code still does not hold images in memory and load them using the LRUCache once the user scrolls. The view still gets recycled.
I am following best practices like using AsyncTask and ViewHolder from the Google Docs. Also, loading about 400 high quality pictures is still taking about 10 seconds before the first image is shown. What can I do to fix the recycling issue and speed the loading of the high-res images up?
public class CustomGridOfFilesEXIFDataAdapter extends ArrayAdapter<EXIFDataMarkerHolder> {
private LayoutInflater layoutInflater;
public int position;
private LruCache<String, Bitmap> mMemoryCache;
ArrayList<Uri> imageList;
//http://www.coderzheaven.com/2013/09/01/faster-loading-images-gridviews-listviews-android-menory-caching-complete-implemenation-sample-code/
public CustomGridOfFilesEXIFDataAdapter(Context context, ArrayList<EXIFDataMarkerHolder> listOfFiles) {
super(context, 0, listOfFiles);
layoutInflater = LayoutInflater.from(context);
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<String, Bitmap>(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 View getView(int position, View convertView, ViewGroup parent) {
ViewHolderItems viewHolder;
EXIFDataMarkerHolder fileExifData = getItem(position);
if(convertView==null){
convertView = layoutInflater.inflate(R.layout.maps_image_grid_single, null);
viewHolder = new ViewHolderItems();
viewHolder.vhImage = (ImageView) convertView.findViewById(R.id.img);
viewHolder.vhImage.setScaleType(ImageView.ScaleType.CENTER_CROP);
viewHolder.position = position;
convertView.setTag(viewHolder);
}else{
viewHolder = (ViewHolderItems) convertView.getTag();
}
if (viewHolder.vhImage != null) {
final String imageKey = fileExifData.filepath;
final Bitmap bm = getBitmapFromMemCache(imageKey);
if (bm == null) {
if (cancelPotentialDownload(fileExifData.filepath, viewHolder.vhImage)) {
ImageConfiguratorAsyncTask task = new ImageConfiguratorAsyncTask(viewHolder.vhImage);
DownloadedDrawable downloadedDrawable = new DownloadedDrawable(task);
viewHolder.vhImage.setImageDrawable(downloadedDrawable);
task.execute(fileExifData.filepath);
}
}
}
return convertView;
}
static class ViewHolderItems {
ImageView vhImage;
int position;
}
//region "Async File Retrieval"
class ImageConfiguratorAsyncTask extends AsyncTask<String, ViewHolderItems, Bitmap> {
private String filepath;
private final WeakReference<ImageView> imageViewReference;
public ImageConfiguratorAsyncTask(ImageView imageView) {
imageViewReference = new WeakReference<ImageView>(imageView);
}
#Override
protected Bitmap doInBackground(String... params) {
Bitmap b = downloadBitmap(params[0]);
addBitmapToMemoryCache(String.valueOf(params[0]), b);
return b;
}
#Override
protected void onPostExecute(Bitmap bitmap) {
if (isCancelled()) {
bitmap = null;
}
if (imageViewReference != null) {
ImageView imageView = imageViewReference.get();
ImageConfiguratorAsyncTask bitmapDownloaderTask = getBitmapDownloaderTask(imageView);
if (this == bitmapDownloaderTask) {
imageView.setImageBitmap(bitmap);
}
}
}
private Bitmap downloadBitmap(String filepath) {
try {
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(filepath, options);
options.inSampleSize = ImageManipulation.calculateInSampleSize(options, 125, 125);
options.inJustDecodeBounds = false;
Bitmap scaledDownFile = BitmapFactory.decodeFile(filepath, options);
scaledDownFile = Bitmap.createScaledBitmap(scaledDownFile, 125, 125, true);
//scaledDownFile = ImageManipulation.decodeBitmapFromFile(filepath, 50, 50);
return scaledDownFile;
} catch (Exception e) {
} finally {
}
return null;
}
}
static class DownloadedDrawable extends ColorDrawable {
private final WeakReference<ImageConfiguratorAsyncTask> bitmapDownloaderTaskReference;
public DownloadedDrawable(ImageConfiguratorAsyncTask bitmapDownloaderTask) {
super(Color.WHITE);
bitmapDownloaderTaskReference =
new WeakReference<ImageConfiguratorAsyncTask>(bitmapDownloaderTask);
}
public ImageConfiguratorAsyncTask getBitmapDownloaderTask() {
return bitmapDownloaderTaskReference.get();
}
}
private static boolean cancelPotentialDownload(String url, ImageView imageView) {
ImageConfiguratorAsyncTask bitmapDownloaderTask = getBitmapDownloaderTask(imageView);
if (bitmapDownloaderTask != null) {
String bitmapUrl = bitmapDownloaderTask.filepath;
if ((bitmapUrl == null) || (!bitmapUrl.equals(url))) {
bitmapDownloaderTask.cancel(true);
} else {
// The same URL is already being downloaded.
return false;
}
}
return true;
}
private static ImageConfiguratorAsyncTask getBitmapDownloaderTask(ImageView imageView) {
if (imageView != null) {
Drawable drawable = imageView.getDrawable();
if (drawable instanceof DownloadedDrawable) {
DownloadedDrawable downloadedDrawable = (DownloadedDrawable)drawable;
return downloadedDrawable.getBitmapDownloaderTask();
}
}
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);
}
//endregion
}
public class ImageManipulation {
public static Bitmap decodeBitmapFromFile(String filepath, int reqWidth, int reqHeight) {
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(filepath, options);
// Calculate inSampleSize
options.inSampleSize = ImageManipulation.calculateInSampleSize(options, 20, 20);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
Bitmap scaledDownFile = BitmapFactory.decodeFile(filepath, options);
scaledDownFile = ImageManipulation.getResizedBitmap(scaledDownFile, 100);
return scaledDownFile;
}
public static Bitmap getResizedBitmap(Bitmap image, int maxSize) {
int width = image.getWidth();
int height = image.getHeight();
float bitmapRatio = (float)width / (float) height;
if (bitmapRatio > 0) {
width = maxSize;
height = (int) (width / bitmapRatio);
} else {
height = maxSize;
width = (int) (height * bitmapRatio);
}
return Bitmap.createScaledBitmap(image, width, height, true);
}
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) {
final int halfHeight = height / 2;
final int halfWidth = width / 2;
// Calculate the largest inSampleSize value that is a power of 2 and keeps both
// height and width larger than the requested height and width.
while ((halfHeight / inSampleSize) > reqHeight
&& (halfWidth / inSampleSize) > reqWidth) {
inSampleSize *= 2;
}
}
return inSampleSize;
}
}
Related
I'm trying to solve this issue about mixed up images in gridview when scrolling. I already saw similar posts here about this issue but unfortunately I didn't solve it yet.
I use asynctask to load the images in the gridView, and I have only imageView in the grid.
Thanks!
Here is my code:
#Override
public View getView(final int position, View convertView, ViewGroup parent) {
ImageView img;
if (convertView == null) {
img = new ImageView(GalleryActivity.this);
WindowManager wm = (WindowManager) GalleryActivity.this.getSystemService(Context.WINDOW_SERVICE);
Display display = wm.getDefaultDisplay();
Point size = new Point();
display.getSize(size);
int width = size.x;
width = width / 3 - 4;
img.setLayoutParams(new AbsListView.LayoutParams(width, width));
img.setScaleType(ImageView.ScaleType.CENTER_CROP);
} else {
img = (ImageView) convertView;
}
File dics = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM);
File dir = new File(dics, "yyy");
if (!dir.exists() && !dir.mkdirs()) {
img.setImageResource(holder(position + 1));
} else {
String file_name = dir.getAbsolutePath() + "/" + (position + 1) + ".jpg";
if (new File(file_name).exists()) {
BitmapWorkerTask task = new BitmapWorkerTask(img);
task.execute(file_name);
items[position].setStatus(1);
} else {
img.setImageResource(holder(position + 1));
}
}
return img;
}
class BitmapWorkerTask extends AsyncTask<String, Void, Bitmap> {
private final WeakReference<ImageView> imageViewReference;
private String data;
public BitmapWorkerTask(ImageView imageView) {
imageViewReference = new WeakReference<ImageView>(imageView);
}
// Decode image in background.
#Override
protected Bitmap doInBackground(String... params) {
data = params[0];
return decodeSampledBitmapFromResource(data,100,100);
}
// 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 Bitmap decodeSampledBitmapFromResource(String resId,
int reqWidth, int reqHeight) {
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(resId,options);
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
options.inJustDecodeBounds = false;
return BitmapFactory.decodeFile(resId);
}
public 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) {
final int halfHeight = height / 2;
final int halfWidth = width / 2;
while ((halfHeight / inSampleSize) >= reqHeight
&& (halfWidth / inSampleSize) >= reqWidth) {
inSampleSize *= 2;
}
}
return inSampleSize;
}
The reason behind your problem is that the BitmapWorkerTask is not finished by starting order so it could override the newest result by older one.
To avoid this you have to keep one BitmapWorkerTask for each view and cancel the old one if it's still working so it doesn't override the result of the new BitmapWorkerTask
You can use setTag and getTag methods to achive this purpose and keep a reference of the latest BitmapWorkerTask working on each view.
You are not setting Tag to ImageView while starting image loading thread that why is set image to current one show at screen when thread completes it job .
you should pass some id as a parameter in BitmapWorkerTask class .
I'm having 11 images in my resources. I use a GridView to display them.
Because images take a lot of space in my ImageAdapter class I calculate sample size and then decode the resouce as per the tutorial here to efficient load an image.
Before I return the decoded bitmap from decodeSampledBitmapFromResource I'm adding the bitmap to LruCache :
Bitmap b = BitmapFactory.decodeResource(res, resId, options);
String key = Integer.toString(resId);
// Log.i("byte", "b.getByteCount()==" + b.getByteCount());
addBitmapToMemoryCache(key, b); //add to cache
which leads to my getView() to try and get cached bitmap if it's not null - else use the method I mentioned above.
For adding and getting bitmaps from cache I'm using :
public void addBitmapToMemoryCache(String key, Bitmap bitmap) {
if (getBitmapFromMemCache(key) == null) {
Log.i("addbitmaptocache", "Add key= " + key);
mMemoryCache.put(key, bitmap);
}
}
public Bitmap getBitmapFromMemCache(String key) {
Log.i("getbitmaptocache", "GET KEY= " + key);
return mMemoryCache.get(key);
}
What happens is that if ( cachedBitmap != null ) is never true which makes me believe something is wrong.
full code for the class :
public class ImageAdapter extends BaseAdapter {
private Context mContext;
private int wid;
private static final String AdapterTAG="adapterTAG";
// private ImageView imageView;
private Bitmap mBitmap;
private LruCache<String, Bitmap> mMemoryCache;
public ImageAdapter(Context c, int wid) {
mContext = c;
this.wid = wid;
}
public int getCount() {
return mThumbIds.length;
}
public Object getItem(int position) {
return null;
}
public long getItemId(int position) {
return 0;
}
// create a new ImageView for each item referenced by the Adapter
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolderItem viewHolder;
int new_width = wid/2;
if (convertView == null) {
LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = inflater.inflate(R.layout.grid_item, parent, false);
// well set up the ViewHolder
viewHolder = new ViewHolderItem();
viewHolder.textViewItem = (TextView) convertView.findViewById(R.id.textId);
viewHolder.imageViewItem = (ImageView) convertView.findViewById(R.id.imageId);
// store the holder with the view.
convertView.setTag(viewHolder);
} else{
viewHolder = (ViewHolderItem) convertView.getTag();
}
/** ******************** Caching ******************** **/
final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
// Log.i("max", "maxMemory== " + maxMemory );
// Use 1/8th of the available memory for this memory cache.
final int cacheSize = maxMemory / 4;
// Log.i("cachesize", "cachesize== " + cacheSize);
mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
#Override
protected int sizeOf(String key, Bitmap bitmap) {
return bitmap.getByteCount() / 1024;
}
};
//Log.i("mMemoryCache", "mMemoryCache= " + mMemoryCache);
viewHolder.textViewItem.setId(position);
viewHolder.imageViewItem.getLayoutParams().width = new_width -5;
viewHolder.imageViewItem.getLayoutParams().height = new_width -5;
viewHolder.imageViewItem.setScaleType(ImageView.ScaleType.CENTER_CROP);
viewHolder.imageViewItem.setPadding(0, 0, 0, 0);
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = false;
final String imageKey = String.valueOf(mThumbIds[position]);
final Bitmap cachedBitmap = getBitmapFromMemCache(imageKey); // use cached Bitmap or ... decode
if ( cachedBitmap != null ) {
Log.i("cached", "CACHED BITMAP FOR THE WIN!!!!");
viewHolder.imageViewItem.setImageBitmap(cachedBitmap);
} else {
viewHolder.imageViewItem.setImageBitmap(decodeSampledBitmapFromResource(mContext.getResources(), mThumbIds[position] , new_width, 200));
}
return convertView;
}
static class ViewHolderItem {
TextView textViewItem;
ImageView imageViewItem;
}
// references to our images
private Integer[] mThumbIds = {
R.drawable.wallpaper0, R.drawable.wallpaper1,
R.drawable.wallpaper2, R.drawable.wallpaper3,
R.drawable.wallpaper4, R.drawable.wallpaper5,
R.drawable.wallpaper6, R.drawable.wallpaper7,
R.drawable.wallpaper8, R.drawable.wallpaper9,
R.drawable.wallpaper10
};
public Bitmap decodeSampledBitmapFromResource(Resources res, int resId,
int reqWidth, int reqHeight) {
// Log.i("req", "reqWidth= " + reqWidth + " reqHeight=" + reqHeight ); // params
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(res, resId, options);
int imageHeight = options.outHeight;
int imageWidth = options.outWidth;
String imageType = options.outMimeType;
Log.i("options", "Width== " + imageWidth + " Height== " + imageHeight + " Type== " + imageType );
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
options.inPurgeable = true;
options.inInputShareable = true;
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
Bitmap b = BitmapFactory.decodeResource(res, resId, options);
String key = Integer.toString(resId);
// Log.i("byte", "b.getByteCount()==" + b.getByteCount());
addBitmapToMemoryCache(key, b); //add to cache
return b;
}
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) {
final int halfHeight = height / 2;
final int halfWidth = width / 2;
// Calculate the largest inSampleSize value that is a power of 2 and keeps both
// height and width larger than the requested height and width.
while ((halfHeight / inSampleSize) > reqHeight
&& (halfWidth / inSampleSize) > reqWidth) {
inSampleSize *= 2;
}
}
Log.i("sample", "size=" +inSampleSize );
return inSampleSize;
}
public void addBitmapToMemoryCache(String key, Bitmap bitmap) {
if (getBitmapFromMemCache(key) == null) {
Log.i("addbitmaptocache", "Add key= " + key);
mMemoryCache.put(key, bitmap);
}
}
public Bitmap getBitmapFromMemCache(String key) {
Log.i("getbitmaptocache", "GET KEY= " + key);
return mMemoryCache.get(key);
}
}
You should not be instantiating the mMemoryCache in the getView() method. Place that in the constructor. That's why it can never find the bitmap in cache, because you are constantly destroying and recreating it.
I want to load some images from my sd-card and set them as backgrounds for my application layouts. I want to do this efficiently so I don't get OutOfMemory Exception and do it in the background . So I've read and used almost the exact code about working with bitmaps in Processing Bitmaps
at android developers. Here's my code:
public class ImageLoader {
private final Context context;
private int imageWidth;
private int imageHeight;
public ImageLoader(Context context, int imageWidth, int imageHeight) {
this.context = context;
setImageSize(imageWidth, imageHeight);
}
public ImageLoader(Context context, int imageSize) {
this.context = context;
setImageSize(imageSize);
}
public void setImageSize(int width, int height) {
imageWidth = width;
imageHeight = height;
}
public void setImageSize(int size) {
setImageSize(size, size);
}
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) {
final int halfHeight = height / 2;
final int halfWidth = width / 2;
// Calculate the largest inSampleSize value that is a power of 2 and keeps both
// height and width larger than the requested height and width.
while ((halfHeight / inSampleSize) > reqHeight
&& (halfWidth / inSampleSize) > reqWidth) {
inSampleSize *= 2;
}
}
return inSampleSize;
}
public static Bitmap decodeSampleBitmapFromResource(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 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 loadBitmap(int resId, ImageView imageView) {
if (cancelPotentialWork(resId, imageView)) {
final BitmapWorkerTask task = new BitmapWorkerTask(imageView);
final AsyncDrawable asyncDrawable =
new AsyncDrawable(context.getResources(), ???mPlaceHoderBitmap???, task);
imageView.setImageDrawable(asyncDrawable);
task.execute(resId);
}
}
class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
private final WeakReference<ImageView> imageViewReference;
private int data = 0;
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(Integer... params) {
data = params[0];
return decodeSampleBitmapFromResource(context.getResources(), data, imageWidth, imageHeight);
}
#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<ImageLoader.BitmapWorkerTask>(bitmapWorkerTask);
}
public BitmapWorkerTask getBitmapWorkerTask() {
return bitmapWorkerTaskReference.get();
}
}
}
However in loadBitmap(int resId, Bitmap bitmap, ImageView imageView) method I don't know how to pass the bitmap. If I use BitmapFactory.decode* I may get exception. How should I pass the bitmap argument for my image data source?
In that article:
public void loadBitmap(int resId, ImageView imageView) {
if (cancelPotentialWork(resId, imageView)) {
final BitmapWorkerTask task = new BitmapWorkerTask(imageView);
final AsyncDrawable asyncDrawable =
new AsyncDrawable(getResources(), mPlaceHolderBitmap, task);
imageView.setImageDrawable(asyncDrawable);
task.execute(resId);
}
}
The bitmap you are passing to AsyncDrawable is supposed to be a place holder while actual image is loaded.
So you can decode a place holder image once and just pass that.
Create a Bitmap object in Activity:
Bitmap placeHolder=null;
and in onCreate
placeHolder=BitmapFactory.decodeResource(getResources(), R.drawable.place_holder);
Now every time you want to load some image from SD card call:
loadBitmap(<resId>,placeHolder,imageView);
I'm working on a fullscreen Gallery and I am developing my custom adapter.
I want to show a place holder image for all my views at first the application starts and with scrolling the views(images) the bitmaps load from filepath.
I'm using Loading bitmaps efficiantly tutorial to load the bitmaps.
My problem is that that placeHolder image is just shown in the app and it does not load other images.
I tried to set mPlaceHolder bitmap in different places such as doInBackground and onPostExecute in my AsyncTask class, but it doesn't help me out.
Am i missing something to do?
here is my adapter code:
public View getView(int position, View convertView, ViewGroup parent) {
ImageView imageView;
if (convertView == null) {
imageView = new ImageView(mContext);
imageView.setAdjustViewBounds(true);
imageView.setPadding(20, 0, 20, 0);
imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
} else {
imageView = (ImageView) convertView;
}
try {
loadBitmap(imagePaths.get(position), imageView);
} catch (Exception e) {
// TODO: handle exception
}
//Bitmap bitmap =decodeFiles(imagePaths.get(position),width,height);
/*
try{
image = decodeFile(new File(imagePaths.get(position)),screenMaxSize);
}
catch (Exception e) {
// TODO: handle exception
Log.i("erroe", imagePaths.get(position));
}
*/
//imageView.setImageBitmap(bitmap);
//imageView.setLayoutParams(new GridView.LayoutParams(90, 70));
return imageView;
}
public void loadBitmap(String filePath, ImageView imageView) {
if (cancelPotentialWork(filePath, imageView)) {
final BitmapWorkerTask task = new BitmapWorkerTask(imageView);
final Bitmap *mPlaceHolderBitmap* = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.gallery_icon);
final AsyncDrawable asyncDrawable =
new AsyncDrawable(mContext.getResources(), mPlaceHolderBitmap, task);
imageView.setImageDrawable(asyncDrawable);
task.execute(filePath);
}
}
public Bitmap decodeFiles(String pathName, int reqWidth,int reqHeight){
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(pathName, options);
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
/*
int scale = 1;
if (o.outHeight > screenMaxSize || o.outWidth > screenMaxSize) {
scale = (int)Math.pow(2, (int) Math.round(Math.log(screenMaxSize / (double) Math.max(o.outHeight, o.outWidth)) / Math.log(0.5)));
}
*/
//Decode with inSampleSize
options.inJustDecodeBounds = false;
return BitmapFactory.decodeFile(pathName, options);
}
private int calculateInSampleSize(Options options, int reqWidth, int reqHeight) {
// TODO Auto-generated method stub
// Raw height and width of image
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
final int halfHeight = height / 2;
final int halfWidth = width / 2;
// Calculate the largest inSampleSize value that is a power of 2 and keeps both
// height and width larger than the requested height and width.
while ((halfHeight / inSampleSize) > reqHeight
&& (halfWidth / inSampleSize) > reqWidth) {
inSampleSize *= 2;
}
}
return inSampleSize;
}
public static boolean cancelPotentialWork(String filePath, ImageView imageView) {
final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
if (bitmapWorkerTask != null) {
final String bitmapData = bitmapWorkerTask.data;
if (bitmapData != filePath) {
// 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;
}
class BitmapWorkerTask extends AsyncTask<String, Void, Bitmap> {
private final WeakReference<ImageView> imageViewReference;
private String data = "";
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) {
data = params[0];
return decodeFiles(data, width, height);
}
// 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();
}
}
It's probably because you are not notifying your adapter in post Execute:
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);
}
}
<YoutAdapterName>.this.notifyDataSetChanged()//<<---do it here
}
Make mPlaceHolderBitmap an adapter level variable and set it up before calling loadBitmap().
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);
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: