Reputation: 3794
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
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