Jun 152011
 
a lazy android logo, for the lazy loading android development tutorial

In my

last post I created a simple Android Twitter feed reader based on the Twitter Search API, demonstrating an application of custom ListView layouts and integration of internet data sources. Any readers who tried out the code would immediately notice that, though functional, the implementation produced significant lag in the UI when scrolling through the tweets. This is because the getView() method of a ListView adapter can be called one or more times each time a ListView item comes into view – we are given no guarantees on when or how this method will be called. Therefore, downloading the Twitter profile images within getView() was extremely inefficient, as each image was being downloaded by the UI thread as the ListView item came into view, and was usually downloaded repeatedly after that. Today I’ll refactor the Twitter example to add asynchronous lazy loading and caching of images (Twitter profile images, in our case). Some of the code included has been based on the excellent

demonstration provided by Github user thest1. Through this example, I’ll demonstrate asynchronous operations in Android, using local storage for caching data, ViewHolders, and a few other advanced techniques for optimizing app performance. First, let’s recall where we left off last time. We were using a custom ArrayAdapter to set the view components of each item in our custom ListView:


public class TweetItemAdapter extends ArrayAdapter {
private ArrayList tweets;

public TweetItemAdapter(Context context, int textViewResourceId,
ArrayList tweets) {
super(context, textViewResourceId, tweets);
this.tweets = tweets;
}

@Override
public View getView(int position, View convertView, ViewGroup parent) {
View v = convertView;
if (v == null) {
LayoutInflater vi =
(LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
v = vi.inflate(R.layout.listitem, null);
}

Tweet tweet = tweets.get(position);
if (tweet != null) {
TextView username = (TextView) v.findViewById(R.id.username);
TextView message = (TextView) v.findViewById(R.id.message);
ImageView image = (ImageView) v.findViewById(R.id.avatar);

if (username != null) {
username.setText(tweet.username);
}

if(message != null) {
message.setText(tweet.message);
}

if(image != null) {
image.setImageBitmap(getBitmap(tweet.image_url));
}
}
return v;
}
}

Each time getView() was called, a method getBitmap(url) was initiated, which reached out to the web to pull down the image required:


public Bitmap getBitmap(String bitmapUrl) {
try {
URL url = new URL(bitmapUrl);
return BitmapFactory.decodeStream(url.openConnection().getInputStream());
}
catch(Exception ex) {return null;}
}

Since all of this is happening on the UI thread, massive UI lag resulted, which would create a poor user experience for a production app. The root problems are:

  1. Images are being downloaded from the UI thread
  2. Images are being downloaded many, many more times than they need to be, since getView() is called as often as Android feels like calling it

So, what can we do?

First, Problem #1. The obvious solution is to download the image and set it to the ImageView in a separate thread. Easy, right? This is the first thing every developer tries when they encounter this problem. Unfortunately, its not so simple. The Android UI is absolutely, completely,

not thread-safe. So, calling the ImageView in a worker thread could lead to your DOOM. Ok, ok…it could lead to weird bugs that are tough to track down. Either way, undesirable. Additionally, if we have a lot of tweets in our ListView, we may end up downloading a large number of profile images that our user never scrolls down to see, wasting network data usage and battery power, two things we want to minimize use of in mobile apps. The best solution to this issue is to lazy load the images in a separate thread, while placing them in our ImageViews with the main UI thread. A good solution is to cache the images somewhere the UI thread can access them, and let the UI thread know when they are available for display. This approach has the added benefit of addressing Problem #2, as well as our inefficient network/battery use, by downloading each image once and storing it in a local cache.

To start, we’ll be adding a class to manage both our local image cache and a download queue for the images. Let’s call this class ImageManager, and get it started:


public class ImageManager {
private HashMap imageMap = new HashMap();
private File cacheDir;

public ImageManager(Context context) {
// Make background thread low priority, to avoid affecting UI performance
imageLoaderThread.setPriority(Thread.NORM_PRIORITY-1);

// Find the dir to save cached images
String sdState = android.os.Environment.getExternalStorageState();
if (sdState.equals(android.os.Environment.MEDIA_MOUNTED)) {
File sdDir = android.os.Environment.getExternalStorageDirectory();
cacheDir = new File(sdDir,"data/codehenge");
}
else
cacheDir = context.getCacheDir();

if(!cacheDir.exists())
cacheDir.mkdirs();
}
}

I’ve added the first things we know we will need, which are a map (imageMap) to store images for display, and reference to the directory where the longer-term image cache will be stored. In the constructor, find this directory by querying the device to see if external storage is mounted, and if not, by getting the default cache location. If we are using external storage (e.g. an SD card), we create a directory called “data/codehenge” for our app’s cache.

Now we will need a few classes to manage a proper queue of images for download. For each image put into the queue, we need to know the URL to find it at, and we will eventually need to know the ImageView to put it in. Here’a an ImageRef class to take care of that:


private class ImageRef {
public String url;
public ImageView imageView;

public ImageRef(String u, ImageView i) {
url=u;
imageView=i;
}
}

I’m making this a private class in my ImageManager class, but you could separate it out if you wanted to. For the actual queue, I’m using a stack of ImageRef objects. However, I’m wrapping this in its own class so I can add extra functionality. Because we know getView() can be called arbitrarily and often, for now I’ll add the ability to clear all ImageRef objects from the queue that are pointing to a given ImageView, so we don’t get too bottled up.


private class ImageQueue {
private Stack imageRefs = new Stack();

//removes all instances of this ImageView
public void Clean(ImageView view) {
for(int i = 0 ;i < imageRefs.size();) {
if(imageRefs.get(i).imageView == view)
imageRefs.remove(i);
else ++i;
}
}
}

We need a way to add images to the queue, so let's write a small queueImage() method:


private void queueImage(String url, ImageView imageView) {
// This ImageView might have been used for other images, so we clear
// the queue of old tasks before starting.
imageQueue.Clean(imageView);
ImageRef p=new ImageRef(url, imageView);

synchronized(imageQueue.imageRefs) {
imageQueue.imageRefs.push(p);
imageQueue.imageRefs.notifyAll();
}

// Start thread if it's not started yet
if(imageLoaderThread.getState() == Thread.State.NEW)
imageLoaderThread.start();
}

Using the method above we're able to push an imageRef for an image into the queue (remember to lock this action!) and start the background imageLoaderThread, if it isn't already started.

Now we're ready to start some asynchronous coding. Basically, we need a thread to run in the background, watch the queue, and get images (either from our semi-persistent cache, or by downloading them) as they are queued. For this, let's create an ImageQueueManager class:


private class ImageQueueManager implements Runnable {
@Override
public void run() {
try {
while(true) {
// Thread waits until there are images in the
// queue to be retrieved
if(imageQueue.imageRefs.size() == 0) {
synchronized(imageQueue.imageRefs) {
imageQueue.imageRefs.wait();
}
}

// When we have images to be loaded
if(imageQueue.imageRefs.size() != 0) {
ImageRef imageToLoad;

synchronized(imageQueue.imageRefs) {
imageToLoad = imageQueue.imageRefs.pop();
}

Bitmap bmp = getBitmap(imageToLoad.url);
imageMap.put(imageToLoad.url, bmp);

// TODO: Display image in ListView on UI thread

}

if(Thread.interrupted())
break;
}
} catch (InterruptedException e) {}
}
}

This is a big one, but not too complex. Basically, this class is meant to run as a single background thread, so its implementing the Runnable interface and overriding its run() method. When run, this thread process will loop until interrupted, waiting for an image to show up in the queue. When an image is queued, it pops each imageRef from the stack in turn and calls getBitmap() (which we have yet to define) to get an Bitmap object, puts the image in our map, and will, once we define it, fire off a process on the UI thread to display the image in the ListView. I've put a TODO comment here, we'll come back to it in a little bit.

For now, we know what getBitmap() needs to do, so let's define it:


private Bitmap getBitmap(String url) {
String filename = String.valueOf(url.hashCode());
File f = new File(cacheDir, filename);

// Is the bitmap in our cache?
Bitmap bitmap = BitmapFactory.decodeFile(f.getPath());
if(bitmap != null) return bitmap;

// Nope, have to download it
try {
bitmap =
BitmapFactory.decodeStream(new URL(url).openConnection().getInputStream());
// save bitmap to cache for later
writeFile(bitmap, f);

return bitmap;
} catch (Exception ex) { ex.printStackTrace(); return null; }
}

private void writeFile(Bitmap bmp, File f) {
FileOutputStream out = null;

try {
out = new FileOutputStream(f);
bmp.compress(Bitmap.CompressFormat.PNG, 80, out);
} catch (Exception e) { e.printStackTrace(); }
finally {
try {
if (out != null ) out.close();
} catch(Exception ex) {}
}
}

If you remember my last tutorial, some of this will look familiar. Namely, I have taken the BitmapFactory code straight out of the custom TweetItemAdapter and dropped it here, with some added functionality around it. Now, if we have the bitmap file cached locally, get it from there. If not, we use BitmapFactory to download it, and write it to the cache for next time. Note that in the writeFile() method we are compressing the bitmap into PNG format at 80% quality to save storage space and time. You can tweak this however you like, and check out the performance difference for your application.

We're now done with ImageManager, and can move on to integrating it with our UI components.

There are two paths we can take to display our image. First, if we have it sitting in our cache and available when getView() asks for it, we can just display it and move on. Alternatively, if we have to queue the download of the image, we need to be able to jump back into the UI thread as soon as the image is available to push it into view. Starting with the first path, let's look at an updated version of TweetItemAdapter:


public class TweetItemAdapter extends ArrayAdapter {
private ArrayList tweets;
private Activity activity;
private static LayoutInflater inflater = null;
public ImageManager imageManager;

public TweetItemAdapter(Activity a, int textViewResourceId,
ArrayList tweets) {
super(a, textViewResourceId, tweets);
this.tweets = tweets;
activity = a;

imageManager =
new ImageManager(activity.getApplicationContext());
}

public static class ViewHolder{
public TextView username;
public TextView message;
public ImageView image;
}

@Override public View getView(int position, View convertView, ViewGroup parent) {
View v = convertView;
ViewHolder holder;

if (v == null) {
LayoutInflater vi = (LayoutInflater) activity.getSystemService(Context.LAYOUT\_INFLATER\_SERVICE);
v = vi.inflate(R.layout.listitem, null);
holder = new ViewHolder();
holder.username = (TextView) v.findViewById(R.id.username);
holder.message = (TextView) v.findViewById(R.id.message);
holder.image = (ImageView) v.findViewById(R.id.avatar);
v.setTag(holder);
} else holder=(ViewHolder)v.getTag();

final Tweet tweet = tweets.get(position);

if (tweet != null) {
holder.username.setText(tweet.username);
holder.message.setText(tweet.message);
holder.image.setTag(tweet.image_url);
imageManager.displayImage(tweet.image_url, activity, holder.image);
}
return v;
}
}

There are a few changes to this class sincelast time, but nothing severe. Out adapter now needs an instance of our ImageManager, and it also needs a reference to its Activity object, which you'll recall has to be passed to the ImageManager when it displays an image. We've also introduced the usage of a ViewHolder, which is a handy tool that optimizes performance a bit. Using a ViewHolder basically means we don't have to call findViewById for every single view, every time we want it, which adds up to a decent amount of computational savings. For some more information on ViewHolders and why you want to use them, check out this post by Charlie Collins. You'll also notice that when initially populating the View for a given Tweet, I now set the tag of the ImageView to the url of the image to be displayed. We'll use this later to verify that we are setting the image in the correct ImageView. Look at the end of getView() and you'll see where we address the "display the image immediately is possible" path. When we have both a Tweet object and a View that are not null, we call a method called displayImage() through the ImageManager:


public void displayImage(String url, Activity activity, ImageView imageView) {
if(imageMap.containsKey(url))
imageView.setImageBitmap(imageMap.get(url));
else {
queueImage(url, imageView);
imageView.setImageResource(R.drawable.icon);
}
}
This is the place where we set the bitmap immediately if it is in our imageMap, or push it into our queue and instead put a placeholder there. The placeholder, R.drawable.icon, is a default Android icon you will probably find automatically included in your project. The reason for the placeholder is that we expect this method to be invoked anytime getView() is called, whether or not the image has been downloaded yet. So, if it hasn't, the placeholder will be displayed until we have a proper Twitter avatar. If you like, you can make this placeholder a different image, a blank image, or nothing at all.

Now we need address our second path by writing some code to display the bitmap in the ListView, using the UI thread. As before, this is easily implemented as a class using the Runnable interface, like so:


//Used to display bitmap in the UI thread
private class BitmapDisplayer implements Runnable {
Bitmap bitmap;
ImageView imageView;

public BitmapDisplayer(Bitmap b, ImageView i) {
bitmap=b;
imageView=i;
}

public void run() {
if(bitmap != null)
imageView.setImageBitmap(bitmap);
else
imageView.setImageResource(R.drawable.icon);
}
}

Now we have a class that can be run on a thread, that is instantiated with a bitmap and an ImageView it needs to be displayed in, whose run() method sets in the ImageView or replaces it with a placeholder image. Let's add this into the space we left for it in ImageQueueManager:


private class ImageQueueManager implements Runnable {
@Override
public void run() {
try {
while(true) {
// Thread waits until there are images in the
// queue to be retrieved
if(imageQueue.imageRefs.size() == 0) {
synchronized(imageQueue.imageRefs) {
imageQueue.imageRefs.wait();
}
}

// When we have images to be loaded
if(imageQueue.imageRefs.size() != 0) {
ImageRef imageToLoad;

synchronized(imageQueue.imageRefs) {
imageToLoad = imageQueue.imageRefs.pop();
}

Bitmap bmp = getBitmap(imageToLoad.url);
imageMap.put(imageToLoad.url, bmp);
Object tag = imageToLoad.imageView.getTag();

// Make sure we have the right view - thread safety defender
if(tag != null && ((String)tag).equals(imageToLoad.url)) {
BitmapDisplayer bmpDisplayer =
new BitmapDisplayer(bmp, imageToLoad.imageView);

Activity a =
(Activity)imageToLoad.imageView.getContext();

a.runOnUiThread(bmpDisplayer);
}
}

if(Thread.interrupted())
break;
}
} catch (InterruptedException e) {}
}
}

The ImageQueueManager class is now complete. Remember, when we have images in the queue, we get the bitmap from cache or download, then put it in our map. Now we continue by checking the tag to verify the bitmap we have belongs in this ImageView, create a new BitmapDisplayer object, get the activity from the ImageView, and use it to run the BitmapDisplayer operations in the UI thread. Notice the check of the ImageView tag: This allows us to be absolutely certain that we are putting the bitmap we have into the ImageView that wants it, which is basically our last stand against the inexplicable behavior of ListViews and getView().

Last, but not least, is our Activity class, which remains basically unchanged from the previous tutorial. Here it is, for the sake of completeness:


public class Example extends Activity {

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);

ArrayList tweets = getTweets("android", 1);

ListView listView = (ListView) findViewById(R.id.ListViewId);
listView.setAdapter(new TweetItemAdapter(this, R.layout.listitem, tweets));
}

public ArrayList getTweets(String searchTerm, int page) {
String searchUrl = "http://search.twitter.com/search.json?q=@" + searchTerm + "&rpp=25&page=" + page;

ArrayList tweets = new ArrayList();

HttpClient client = new DefaultHttpClient();
HttpGet get = new HttpGet(searchUrl);

ResponseHandler responseHandler = new BasicResponseHandler();

String responseBody = null;
try {
responseBody = client.execute(get, responseHandler);
} catch(Exception ex) {
ex.printStackTrace();
}

JSONObject jsonObject = null;
JSONParser parser=new JSONParser();

try {
Object obj = parser.parse(responseBody);
jsonObject=(JSONObject)obj;

} catch(Exception ex) {
Log.v("TEST","Exception: " + ex.getMessage());
}

JSONArray arr = null;

try {
Object j = jsonObject.get("results");
arr = (JSONArray)j;
} catch(Exception ex) {
Log.v("TEST","Exception: " + ex.getMessage());
}

for(Object t : arr) {
Tweet tweet = new Tweet(
((JSONObject)t).get("from_user").toString(),
((JSONObject)t).get("text").toString(),
((JSONObject)t).get("profile_image_url").toString()
);
tweets.add(tweet);
}

return tweets;
}

/** Classes **/

public class Tweet {
public String username; public String message; public String image_url;

public Tweet(String username, String message, String url) {
this.username = username;
this.message = message;
this.image_url = url;
}
}
}

And we're done! The code is complete, and ready to run. You will see a notable increase in UI responsiveness, and should really not notice any lag at all unless you swipe through a large list at warp speed.

I've put the full project from this tutorial up on

Github, so go ahead and grab it, fork it, try it out for yourself. Happy coding!

Get 50% off my Node.js course here

If you liked this article, help me out by sharing a 50% discount to my Node.js course here: Thanks!

You should follow me on Twitter here:

  • http://www.facebook.com/logan.hendershot Logan Hendershot

    Looks good, saving for later!

  • Pingback: Android Development Tutorial: Asynchronous Lazy Loading and … « Affordable Hosting Gen.

  • Pingback: Android Development Tutorial: Asynchronous Lazy Loading and … « Affordable Hosting Gen.

  • Pingback: Android Development Tutorial: Asynchronous Lazy Loading and … | Web Host Aid

  • Pingback: Robert McGhee » June 17th

  • Steve

    Good stuff!

    Let’s say you want to pass the position of the tweet back from the ArrayAdapter to the main activity that set the listadapter. Normally I would do setTag(position) and a getTag() to retrieve the position…
    Since you’re already using the setTag for the viewholder, is there another way to do this?

    • Anonymous

      My first thought is there’s no reason you can’t embed metadata into the tag string. Shove a delimited list of strings, or some JSON into the tag, and parse it on the other side.

      • Steve

        Hm, k I’ll try that, thanks!

        • Ravikumar_1111

          Awesome code,thank you dude

  • Sathya Sri69

    Hi androidian,
    I would like to very thankful to you for this great helpful post. it is working for me great.
    But i would like to suggest one thing that, before going to load actual image from url we are displaying default icon in place of imageview but i need to display progressbar to know the user what is happenning before loading image.

    Can you please update me with this?

    Thanks in advance

    sathish,
    sathya.sri69@gmail.com

    • Anonymous

      This is a great idea for a follow-up post. I wrote the code today, I’ll be putting up a new post in the next few days to illustrate adding an indeterminate progress bar while the image is loading.

      In the meantime, I’ve checked in a new branch (progress_bar) of the TweetView project at Github with this feature added so you can see how it can be done. Enjoy!

      • sathish

        Thanks for your quick concern. i will see and update it. thanks again

        • http://www.codehenge.net Constantine Aaron Cois

          No problem. The new post is up here. Hope it helps!

  • Pingback: Android Development Tutorial: ProgressBar Example | Codehenge

  • tehfist

    Thank you very much, this code is awesome :)

    I just had a little issue with the image displaying on the current views
    on the “ImageQueueManager” class, i couldn’t pass the condition
    if(tag != null && ((String)tag).equals(imageToLoad.url)) {

    so i removed it and i worked well for the moment !

    • Anonymous

      Glad its working! That may cause you problems down the line – that check is there to make sure you are loading the image into the proper view. I’m guessing what’s happening is that you are unable to set the tag on the ImageView appropriately for some reason. Look for the line:

      holder.image.setTag(tweet.image_url);

      in the getView() method of the TweetItemAdapter class, and set a breakpoint. Make sure a non-null url is being set as the tag when a ViewHolder is being created..

  • Jdetoni

    Great code!!

    Thank you very much!! I was thinking about put cache in my app and your job is perfect. Congrats man, very well.

    • Shirkeshashi04 Android

      could you tell me how this line is works and how to get the value of activity class

      Activity a = (Activity)imageToLoad.imageView.getContext();

  • Yakir31

    hi!
    very nice code!
    can you please write a code for selecting item on the list ?

    i mean public void onItemClick(AdapterView parent, View view,
    int position, long id)

    thanks!

    • http://www.codehenge.net Constantine Aaron Cois

      I’ll look into putting together a small tutorial on that functionality.

      For now, what kind of things are you trying to do when the item is clicked?

  • Pete Houston

    Seriously, awesome article!!!

    • Shashikant

      hi have u tried it?
      but I geeting error here. could u tell me the answer.

      Here , getting empty file (null data ) into the bitmap Object. could u tell me why?
      reply me on shashikant@rvmk.com

      Bitmap bitmap = BitmapFactory.decodeFile(f.getPath());

      if(bitmap != null) return bitmap;

      // Nope, have to download it
      try {

      bitmap = BitmapFactory.decodeStream(new URL(url).openConnection().getInputStream());

      • http://www.codehenge.net Constantine Aaron Cois

        At which point in the above code are you seeing the bitmap object being null? If after the first line, it means you haven’t stored anything at the designated file path. If after the last line, it means the url you have specified does not resolve, or returns null data somehow.

    • Shirkeshashi04 Android

      hi I got the FileNotFoundException in this tutorial because here in
      writeFile(Bitmap bmp, File f) out object is null after execute the following line.

      out = new FileOutputStream(f);

      I have also change my cacheDir name

  • http://www.facebook.com/umakant.patil Umakant Patil

    Problem with some heavy images. It take time to load the image and after image is loaded it doesn’t update image. We need to scroll the ListView for that. :)

    • http://www.codehenge.net Constantine Aaron Cois

      If you need to change the image after it has been loaded, you will have to call notifyDataSetChanged() on the ListView.

  • http://ayublin.com ayub

    Nice :) I have downloaded the source, what is the different of “org.json.simple” with built in “org.json” library ?

    • http://www.codehenge.net Constantine Aaron Cois

      Just a different library that I prefer. Has, IMO, easier syntax and more logical operation.

  • Manjunath Cy

    nice ….So nice…..am fresher….Hope i ll get such article….Thank you very much ..

  • hemant

    Hi,

    My app has a lot of activities and a lot of adapters. Your code basically creates a new object for every adapter and when someone goes back to the activity it starts the same process all over again. Can I implement global cache somehow?

    • hemant

      nice article by the way :)

    • Nick Nodarakis

      I am dealing with the same thing, my application has a lot of activities and a lot of adapters. I think the solution is to make the ImageManager class singleton in order to achieve global cache.

    • Carl

      I was in the same boat, made it a singleton right off the bat. Only issue is I get crashes after about 120 – 200 images. Probably needs more edits to be a proper singleton implementation, free up its memory.

  • srujana kalidindi

    woww!!! *bows in reverence*. I’ll definitely try this out. bump yakir31. Do put together a tutorial on the onItemClicked functionality.

  • http://twitter.com/_jm Jawaad Mahmood

    Hi Constantine,

    First – This was _incredibly_ helpful in clearing up some problems for me; thanks!

    Thought I’d drop a stupid bug I introduced in my code by not paying attention :P

    Much like Umakant said earlier, it seems like I had to “scroll” the screen for it to actually update the images that are being displayed.

    I couldn’t figure it out so I started adding logging (etc) and then I realized I hadn’t actually added the image URL tag (holder.image.setTag(o.thumbnail)).

    Hit myself in the face when I realized it and fixed it after. Thanks for sharing dude!

    • http://www.codehenge.net Constantine Aaron Cois

      Thanks for sharing this! That’s definitely one of those simple, infuriating bugs that you can bang your head against for hours. I’ve been there way too many times. Hopefully this saves some others a few hours of frustration.

    • Ryan

      Wow that was my issue, and I just happened to stumble upon your comment! Thanks!

    • Raed

      You are the best!!!!!!!

  • Sanket

    I am getting a classcast exception at the line
    Activity a =
    (Activity)imageToLoad.imageView.getContext();

    Any idea why this could be happening.
    My Activity extends superActivity which in turn extends Activity

    • http://www.codehenge.net Constantine Aaron Cois

      Make sure that getContext() is returning an object of type superActivity. Also, make sure that you can cast a superActivity to an Activity in another scenario. Try something like:

      superActivity s = new superActivity;
      Activity a = (Activity)s;

      and see if you get an error.

      • Sanket

        thanks!
        Actually the issue was that the inflater for the dataadapter was being created using the applicationcontext.changing this to activitycontext solved the issue.

  • Guna

    Awesome article, helped me out in tough situation. Thanks a lot!!!!!!1

  • http://www.facebook.com/eljeanc Jean Jimenez

    Hi everybody, i’m buildind an app that needs to download images and this tutorial is amazing, I just would like to know how i can make that the downloaded images can be reuse when i the user close the app and start it again i mean just download new ones and uses again the images that were already downloades.

    Regards
    Jean

  • Shashikant

    Here , getting empty file (null data ) into the bitmap Object. could u tell me why?
    reply me on shashikant@rvmk.com

    Bitmap bitmap = BitmapFactory.decodeFile(f.getPath());

    if(bitmap != null) return bitmap;

    // Nope, have to download it
    try {

    bitmap = BitmapFactory.decodeStream(new URL(url).openConnection().getInputStream());

  • Shirkeshashi04 Android

    I am also using ImageManager class to load the image asynchronously in my project also getting url into the file object(i.e. filename) but after that when image is going to stored into the bitmap object from file the file (f) object the bitmap object shows the null value(their is no any image or value).Please reply me on shirkeshashi04.android@gmail.com

    The code is here:

    private Bitmap getBitmap(String url) {
    String filename = String.valueOf(url.hashCode());// here in filename I getting url
    File f = new File(cacheDir, filename);//here is also getting url

    // Is the bitmap in our cache

    Bitmap bitmap = BitmapFactory.decodeFile(f.getPath());
    // here bitmap object is null

    if(bitmap != null) return bitmap;

    // Nope, have to download it
    try {
    bitmap =
    BitmapFactory.decodeStream(new URL(url).openConnection().getInputStream());
    // save bitmap to cache for later
    writeFile(bitmap, f);

    return bitmap;
    } catch (Exception ex) {
    ex.printStackTrace();
    return null;
    }
    }

    • Shirkeshashi04 Android

      there are 2 more xml files if needed I will send you
      thanks

  • Shirkeshashi04 Android

    hi I got the FileNotFoundException in this tutorial because here in
    writeFile(Bitmap bmp, File f) out object is null after execute the following line.

    out = new FileOutputStream(f);

    I have also change my cacheDir name

  • Sardeenz

    Thanks, this works great.

    • Shirkeshashi04 Android

      I getting the tag value , but on the next line I getting error again in ImageQueueManager class:

      Activity a = (Activity)imageToLoad.imageView.getContext();

      I think , I have remove activity object from the display() method , is it write?
      here is my code:
      imgdownloader.displayImage(country.getUri(), iv);

      This is my getView:

      public View getView(int position, View convertView, ViewGroup parent) {

      if(convertView == null)
      convertView = mInflater.inflate(mViewResourceId, null);

      Country country = getItem(position);

      ImageView iv = (ImageView)convertView.findViewById(R.id.option_icon);

      TextView tv = (TextView)convertView.findViewById(R.id.option_text);

      tv.setText(country.getName());

      imgdownloader.displayImage(country.getUri(), iv);

      return convertView;

      }

      how the Activity is set here:

      Activity a = (Activity)imageToLoad.imageView.getContext();

      package com.test;

      import java.util.ArrayList;
      import java.util.List;

      import android.app.Activity;
      import android.app.ListActivity;
      import android.content.Context;
      import android.content.res.Resources;
      import android.content.res.TypedArray;
      import android.os.Bundle;

      public class AdvancedListViewActivity extends ListActivity{
      public void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.main1);

      Context ctx = getApplicationContext();
      Resources res = ctx.getResources();

      MyImageLoader myimageloader;

      Bundle bun= this.getIntent().getExtras();
      String name=bun.getString(“name1″);
      int icn=bun.getInt(“name2″);

      String url=”http://farm7.static.flickr.com…”;
      List countryList=new ArrayList();

      Country cntrynew =new Country();

      cntrynew.setUri(url);
      cntrynew.setName(name);
      countryList.add(cntrynew);

      String[] countryNames = res.getStringArray(R.array.country_names);
      String[] countryFlagUrls = res.getStringArray(R.array.nameurl);

      for(int ii=0; ii<countrynames.length;ii++){
      Country cntry =new Country();

      cntry.setUri(countryFlagUrls[ii]);
      cntry.setName(countryNames[ii]);
      countryList.add(cntry);
      }
      TypedArray icons = res.obtainTypedArray(R.array.country_icons);

      int nn=icons.length();

      MyImageLoader img=new MyImageLoader(getApplicationContext());

      setListAdapter(new ImageAndTextAdapter(ctx, R.layout.main_list,countryList,img));

      }

      }

      package com.test;

      import java.io.BufferedInputStream;
      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.MalformedURLException;
      import java.net.URL;
      import java.net.URLConnection;
      import java.util.Collections;
      import java.util.HashMap;
      import java.util.Map;
      import java.util.Stack;
      import java.util.WeakHashMap;

      import android.app.Activity;
      import android.content.Context;
      import android.graphics.Bitmap;
      import android.graphics.BitmapFactory;
      import android.widget.ImageView;

      public class MyImageLoader {

      private HashMap imageMap = new HashMap();
      // private Map imageViews=Collections.synchronizedMap(new WeakHashMap());

      private ImageQueue imageQueue = new ImageQueue();
      private Thread imageLoaderThread = new Thread(new ImageQueueManager());
      private File cacheDir;

      public MyImageLoader(Context context) {
      // Make background thread low priority, to avoid affecting UI performance
      imageLoaderThread.setPriority(Thread.NORM_PRIORITY-1);

      // Find the dir to save cached images
      String sdState = android.os.Environment.getExternalStorageState();
      if (sdState.equals(android.os.Environment.MEDIA_MOUNTED)) {
      File sdDir = android.os.Environment.getExternalStorageDirectory();
      // cacheDir = new File(sdDir,”data/codehenge”);
      cacheDir = new File(sdDir,”sash”);

      }
      else
      cacheDir = context.getCacheDir();

      if(!cacheDir.exists())
      cacheDir.mkdirs();
      }

      static int i = 0;

      //Provides classes to manage a variety of visual elements that are intended for display only, such as bitmaps and gradients.
      /*public static Drawable downLoad(String url)
      {*/

      private class ImageQueue {
      private Stack imageRefs = new Stack();

      //removes all instances of this ImageView
      public void Clean(ImageView view)
      {
      for(int i = 0 ;i < imageRefs.size();)
      {
      if(imageRefs.get(i).imageView == view)
      imageRefs.remove(i);
      else
      ++i;
      }
      }
      }
      private class ImageRef {
      public String url;
      public ImageView imageView;

      public ImageRef(String u, ImageView i) {
      url=u;
      imageView=i;
      }
      }
      private class ImageQueueManager implements Runnable
      {
      public void run() {
      try {
      while(true)
      {
      if(imageQueue.imageRefs.size() == 0) {
      synchronized(imageQueue.imageRefs) {
      imageQueue.imageRefs.wait();
      }
      }
      // When we have images to be loaded
      if(imageQueue.imageRefs.size() != 0)
      {
      ImageRef imageToLoad;
      synchronized(imageQueue.imageRefs) {
      imageToLoad = imageQueue.imageRefs.pop();
      }
      System.out.println("Going to Downlad Image from URL " + imageToLoad.url);
      Bitmap bmp = getBitmap(imageToLoad.url);//get the url
      /*….testing Image…*/
      if(bmp!=null)
      {
      System.out.println("Image is here….");
      }
      else
      {
      System.out.println("Image is not present is this variable..");
      }

      //it is a object of hashmap
      imageMap.put(imageToLoad.url, bmp);
      //we store to check wheter it is write

      Object tag = imageToLoad.imageView.getTag();

      //Display image in ListView on UI thread
      // Make sure we have the right view – thread safety defender
      if(tag!=null && ((String)tag).equals(imageToLoad.url))
      {
      //take 2 arg i.e.imageview ,bitmap
      BitmapDisplayer bmpDisplayer =
      new BitmapDisplayer(bmp, imageToLoad.imageView);

      Activity a = (Activity)imageToLoad.imageView.getContext();

      a.runOnUiThread(bmpDisplayer);
      }
      }
      if(Thread.interrupted())
      break;
      }
      } catch (InterruptedException e) {}
      }
      }
      private Bitmap getBitmap(String url)
      {
      String filename = String.valueOf(url.hashCode());
      System.out.println(“the filename object contain:”+filename);
      File f = new File(cacheDir, filename);
      System.out.println(“The file content is:”+f.getName());

      // Is the bitmap in our cache?
      Bitmap bitmap = BitmapFactory.decodeFile(f.getPath());
      // System.out.println(“The file content is:”+bitmap.toString());

      if(bitmap!=null)
      {
      System.out.println(“The bitmap image is here”);
      }
      else
      {
      System.out.println(“The image is not here….”);
      }

      if(bitmap != null)
      return bitmap;

      // Hope, have to download it
      try {
      bitmap =BitmapFactory.decodeStream(new URL(url).openConnection().getInputStream());
      // save bitmap to cache for later
      if(bitmap!=null)
      {
      System.out.println(“The bitmap image is here”);
      }
      else
      {
      System.out.println(“The image is not here….”);
      }

      writeFile(bitmap, f);

      return bitmap;
      }
      catch (Exception ex) {
      ex.printStackTrace();
      return null;
      }
      }

      private void writeFile(Bitmap bmp, File f) {
      FileOutputStream out = null;

      try {
      out = new FileOutputStream(f);
      bmp.compress(Bitmap.CompressFormat.PNG, 80, out);
      } catch (Exception e) {
      e.printStackTrace();
      }
      finally {
      try { if (out != null )
      out.close(); }
      catch(Exception ex) {}
      }
      }

      private class BitmapDisplayer implements Runnable {
      Bitmap bitmap;
      ImageView imageView;
      public BitmapDisplayer(Bitmap b, ImageView i)
      {
      bitmap=b;
      imageView=i;
      }

      public void run() {
      if(bitmap != null)
      imageView.setImageBitmap(bitmap);
      else
      imageView.setImageResource(R.drawable.icon);
      }
      }

      public void displayImage(String url,ImageView imageView)
      {
      if(imageMap.containsKey(url))
      imageView.setImageBitmap(imageMap.get(url));
      else {
      imageView.setTag(url);
      // queueImage(url,imageView);
      queueImage(url,imageView);
      imageView.setImageResource(R.drawable.icon);
      }
      }

      private void queueImage(String url, ImageView imageView) {
      // This ImageView might have been used for other images, so we clear
      // the queue of old tasks before starting.
      imageQueue.Clean(imageView);
      ImageRef p=new ImageRef(url, imageView);

      synchronized(imageQueue.imageRefs) {
      imageQueue.imageRefs.push(p);
      imageQueue.imageRefs.notifyAll();
      }

      // Start thread if it’s not started yet
      if(imageLoaderThread.getState() == Thread.State.NEW)
      imageLoaderThread.start();
      }

      }

      package com.test;

      import java.util.ArrayList;
      import java.util.List;

      import android.app.Activity;
      import android.content.Context;
      import android.content.res.TypedArray;
      import android.view.LayoutInflater;
      import android.view.View;
      import android.view.ViewGroup;
      import android.widget.ArrayAdapter;
      import android.widget.ImageView;
      import android.widget.TextView;

      //public class ImageAndTextAdapter extends ArrayAdapter
      public class ImageAndTextAdapter extends ArrayAdapter
      {

      private LayoutInflater mInflater;

      private int icon;
      //The Activity recall has to be passed to the MyImageLoader when it displays an image.
      //private Activity activity;
      MyImageLoader imgdownloader = null;

      private int mViewResourceId;
      private Context ct;
      String mUrl;
      TypedArray[] mIcon;
      List countryList=null;

      //public ImageAndTextAdapter(Context ctx, int viewResourceId,String[] strings, TypedArray icons,int icn)
      //public ImageAndTextAdapter(Context ctx, int viewResourceId,List lst,MyImageLoader imgldr,TypedArray[] img)
      public ImageAndTextAdapter(Context ctx, int viewResourceId,List lst,MyImageLoader imgldr)

      {
      //super(ctx, viewResourceId, list);
      super(ctx, viewResourceId, lst);

      mInflater = (LayoutInflater)ctx.getSystemService(Context.LAYOUT_INFLATER_SERVICE);

      ct=ctx;
      //mIcon=img;
      countryList=lst;

      mViewResourceId = viewResourceId;
      //mUrl=url;
      imgdownloader=imgldr;

      // activity=a;

      }

      public int getCount()
      {
      return countryList.size();
      }
      public Country getItem(int position)
      {
      return (Country) countryList.get(position);
      }
      public long getItemId(int position)
      {
      return position;
      }

      //Using a ViewHolder means we don’t have to call findViewById
      //for every single view, every time ,
      //which adds up to a decent amount of computational savings
      public static class ViewHolder{

      public ImageView image;
      }

      public View getView(int position, View convertView, ViewGroup parent) {

      if(convertView == null)
      convertView = mInflater.inflate(mViewResourceId, null);

      Country country = getItem(position);

      ImageView iv = (ImageView)convertView.findViewById(R.id.option_icon);

      TextView tv = (TextView)convertView.findViewById(R.id.option_text);

      tv.setText(country.getName());

      imgdownloader.displayImage(country.getUri(), iv);

      return convertView;

      }
      }

      package com.test;

      import android.net.Uri;

      public class Country {

      public String mName=null;
      //public int mImage;
      public String mUri=null;

      /*public Country(String name, String uri )
      {
      this.mName=name;
      this.mUri=uri;
      }*/

      public void setName(String nm)
      {
      this.mName=nm;
      }
      public String getName()
      {
      return mName;
      }

      /*public void setImage(int img)
      {
      this.mImage=img;
      }
      public int getImage()
      {
      return mImage;
      }*/

      public void setUri(String url)
      {
      this.mUri=url;
      }
      public String getUri()
      {
      return mUri;

      }

      }

      countries.xml

      Bhutan
      Colombia
      Italy
      Jamaica
      Kazakhstan
      Kenya

      @drawable/bhutan
      @drawable/colombia
      @drawable/italy
      @drawable/jamaica
      @drawable/kazakhstan
      @drawable/kenya

      http://farm5.static.flickr.com
      http://farm4.static.flickr.com
      http://farm4.static.flickr.com
      http://farm5.static.flickr.com
      http://farm5.static.flickr.com
      http://farm4.static.flickr.com

      </countrynames.length;ii++){

  • Shirkeshashi04 Android

    Hi I am Shashi,
    really this is nice tutorial.
    The same thing I am going to used in my project. I have implemented Imageloader class in my project but here in tag variable I getting null value, Please have a look & help me.

    Object tag = imageToLoad.imageView.getTag();
    // value of tag =null

    The tag value is null so the project get’s interrupted .

    if(Thread.interrupted())
    break;

    • http://www.codehenge.net Constantine Aaron Cois

      So you are seeing a null value – first thing to do is to go and find where that value should be set, right? Look for this in TweetItemAdapter.getView() in the example code. Specifically, look for the line:

      holder.image.setTag(tweet.image_url);

      This is where we set the tag for a given ImageView in the example. You have to have some equivalent code in your implementation, to make sure you are setting the correct image.

      • Shirkeshashi04 Android

        Thanks for the reply and Sorry for the disturbance because I am new in android. I am developing list view using notification for this I am using this tutorial to download country flag from the web. This code is much helpful to achieve the same task.

        Now I getting the tag value , but on the next line I getting error again in ImageQueueManager class:

        Activity a = (Activity)imageToLoad.imageView.getContext();

        I think , I have remove activity object from the display() method , is it write?
        here is my code:
        imgdownloader.displayImage(country.getUri(), iv);

        This is my getView:

        public View getView(int position, View convertView, ViewGroup parent) {

        if(convertView == null)
        convertView = mInflater.inflate(mViewResourceId, null);

        Country country = getItem(position);

        ImageView iv = (ImageView)convertView.findViewById(R.id.option_icon);

        TextView tv = (TextView)convertView.findViewById(R.id.option_text);

        tv.setText(country.getName());

        imgdownloader.displayImage(country.getUri(), iv);

        return convertView;

        }

        Please reply me.

        • Shirkeshashi04 Android

          how the Activity is set here:

          Activity a = (Activity)imageToLoad.imageView.getContext();

          • Shirkeshashi04 Android

            package com.test;

            import java.util.ArrayList;
            import java.util.List;

            import android.app.Activity;
            import android.app.ListActivity;
            import android.content.Context;
            import android.content.res.Resources;
            import android.content.res.TypedArray;
            import android.os.Bundle;

            public class AdvancedListViewActivity extends ListActivity{
            public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.main1);

            Context ctx = getApplicationContext();
            Resources res = ctx.getResources();

            MyImageLoader myimageloader;

            Bundle bun= this.getIntent().getExtras();
            String name=bun.getString(“name1″);
            int icn=bun.getInt(“name2″);

            String url=”http://farm7.static.flickr.com/6083/6041682945_13abc15d66_s.jpg”;
            List countryList=new ArrayList();

            Country cntrynew =new Country();

            cntrynew.setUri(url);
            cntrynew.setName(name);
            countryList.add(cntrynew);

            String[] countryNames = res.getStringArray(R.array.country_names);
            String[] countryFlagUrls = res.getStringArray(R.array.nameurl);

            for(int ii=0; ii<countryNames.length;ii++){

            Country cntry =new Country();

            cntry.setUri(countryFlagUrls[ii]);
            cntry.setName(countryNames[ii]);
            countryList.add(cntry);
            }
            TypedArray icons = res.obtainTypedArray(R.array.country_icons);

            int nn=icons.length();

            MyImageLoader img=new MyImageLoader(getApplicationContext());

            setListAdapter(new ImageAndTextAdapter(ctx, R.layout.main_list,countryList,img));

            }

            }

            package com.test;

            import java.io.BufferedInputStream;
            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.MalformedURLException;
            import java.net.URL;
            import java.net.URLConnection;
            import java.util.Collections;
            import java.util.HashMap;
            import java.util.Map;
            import java.util.Stack;
            import java.util.WeakHashMap;

            import android.app.Activity;
            import android.content.Context;
            import android.graphics.Bitmap;
            import android.graphics.BitmapFactory;
            import android.widget.ImageView;

            public class MyImageLoader {

            private HashMap imageMap = new HashMap();
            // private Map imageViews=Collections.synchronizedMap(new WeakHashMap());

            private ImageQueue imageQueue = new ImageQueue();
            private Thread imageLoaderThread = new Thread(new ImageQueueManager());
            private File cacheDir;

            public MyImageLoader(Context context) {
            // Make background thread low priority, to avoid affecting UI performance
            imageLoaderThread.setPriority(Thread.NORM_PRIORITY-1);

            // Find the dir to save cached images
            String sdState = android.os.Environment.getExternalStorageState();
            if (sdState.equals(android.os.Environment.MEDIA_MOUNTED)) {
            File sdDir = android.os.Environment.getExternalStorageDirectory();
            // cacheDir = new File(sdDir,”data/codehenge”);
            cacheDir = new File(sdDir,”sash”);

            }
            else
            cacheDir = context.getCacheDir();

            if(!cacheDir.exists())
            cacheDir.mkdirs();
            }

            static int i = 0;

            //Provides classes to manage a variety of visual elements that are intended for display only, such as bitmaps and gradients.
            /*public static Drawable downLoad(String url)
            {*/

            private class ImageQueue {
            private Stack imageRefs = new Stack();

            //removes all instances of this ImageView
            public void Clean(ImageView view)
            {
            for(int i = 0 ;i < imageRefs.size();)
            {
            if(imageRefs.get(i).imageView == view)
            imageRefs.remove(i);
            else
            ++i;
            }
            }
            }
            private class ImageRef {
            public String url;
            public ImageView imageView;

            public ImageRef(String u, ImageView i) {
            url=u;
            imageView=i;
            }
            }
            private class ImageQueueManager implements Runnable
            {
            public void run() {
            try {
            while(true)
            {
            if(imageQueue.imageRefs.size() == 0) {
            synchronized(imageQueue.imageRefs) {
            imageQueue.imageRefs.wait();
            }
            }
            // When we have images to be loaded
            if(imageQueue.imageRefs.size() != 0)
            {
            ImageRef imageToLoad;
            synchronized(imageQueue.imageRefs) {
            imageToLoad = imageQueue.imageRefs.pop();
            }
            System.out.println("Going to Downlad Image from URL " + imageToLoad.url);
            Bitmap bmp = getBitmap(imageToLoad.url);//get the url
            /*….testing Image…*/
            if(bmp!=null)
            {
            System.out.println("Image is here….");
            }
            else
            {
            System.out.println("Image is not present is this variable..");
            }

            //it is a object of hashmap
            imageMap.put(imageToLoad.url, bmp);
            //we store to check wheter it is write

            Object tag = imageToLoad.imageView.getTag();

            //Display image in ListView on UI thread
            // Make sure we have the right view – thread safety defender
            if(tag!=null && ((String)tag).equals(imageToLoad.url))
            {
            //take 2 arg i.e.imageview ,bitmap
            BitmapDisplayer bmpDisplayer =
            new BitmapDisplayer(bmp, imageToLoad.imageView);

            Activity a = (Activity)imageToLoad.imageView.getContext();

            a.runOnUiThread(bmpDisplayer);
            }
            }
            if(Thread.interrupted())
            break;
            }
            } catch (InterruptedException e) {}
            }
            }
            private Bitmap getBitmap(String url)
            {
            String filename = String.valueOf(url.hashCode());
            System.out.println(“the filename object contain:”+filename);
            File f = new File(cacheDir, filename);
            System.out.println(“The file content is:”+f.getName());

            // Is the bitmap in our cache?
            Bitmap bitmap = BitmapFactory.decodeFile(f.getPath());
            // System.out.println(“The file content is:”+bitmap.toString());

            if(bitmap!=null)
            {
            System.out.println(“The bitmap image is here”);
            }
            else
            {
            System.out.println(“The image is not here….”);
            }

            if(bitmap != null)
            return bitmap;

            // Hope, have to download it
            try {
            bitmap =BitmapFactory.decodeStream(new URL(url).openConnection().getInputStream());
            // save bitmap to cache for later
            if(bitmap!=null)
            {
            System.out.println(“The bitmap image is here”);
            }
            else
            {
            System.out.println(“The image is not here….”);
            }

            writeFile(bitmap, f);

            return bitmap;
            }
            catch (Exception ex) {
            ex.printStackTrace();
            return null;
            }
            }

            private void writeFile(Bitmap bmp, File f) {
            FileOutputStream out = null;

            try {
            out = new FileOutputStream(f);
            bmp.compress(Bitmap.CompressFormat.PNG, 80, out);
            } catch (Exception e) {
            e.printStackTrace();
            }
            finally {
            try { if (out != null )
            out.close(); }
            catch(Exception ex) {}
            }
            }

            private class BitmapDisplayer implements Runnable {
            Bitmap bitmap;
            ImageView imageView;
            public BitmapDisplayer(Bitmap b, ImageView i)
            {
            bitmap=b;
            imageView=i;
            }

            public void run() {
            if(bitmap != null)
            imageView.setImageBitmap(bitmap);
            else
            imageView.setImageResource(R.drawable.icon);
            }
            }

            public void displayImage(String url,ImageView imageView)
            {
            if(imageMap.containsKey(url))
            imageView.setImageBitmap(imageMap.get(url));
            else {
            imageView.setTag(url);
            // queueImage(url,imageView);
            queueImage(url,imageView);
            imageView.setImageResource(R.drawable.icon);
            }
            }

            private void queueImage(String url, ImageView imageView) {
            // This ImageView might have been used for other images, so we clear
            // the queue of old tasks before starting.
            imageQueue.Clean(imageView);
            ImageRef p=new ImageRef(url, imageView);

            synchronized(imageQueue.imageRefs) {
            imageQueue.imageRefs.push(p);
            imageQueue.imageRefs.notifyAll();
            }

            // Start thread if it’s not started yet
            if(imageLoaderThread.getState() == Thread.State.NEW)
            imageLoaderThread.start();
            }

            }

            package com.test;

            import java.util.ArrayList;
            import java.util.List;

            import android.app.Activity;
            import android.content.Context;
            import android.content.res.TypedArray;
            import android.view.LayoutInflater;
            import android.view.View;
            import android.view.ViewGroup;
            import android.widget.ArrayAdapter;
            import android.widget.ImageView;
            import android.widget.TextView;

            //public class ImageAndTextAdapter extends ArrayAdapter
            public class ImageAndTextAdapter extends ArrayAdapter
            {

            private LayoutInflater mInflater;

            private int icon;
            //The Activity recall has to be passed to the MyImageLoader when it displays an image.
            //private Activity activity;
            MyImageLoader imgdownloader = null;

            private int mViewResourceId;
            private Context ct;
            String mUrl;
            TypedArray[] mIcon;
            List countryList=null;

            //public ImageAndTextAdapter(Context ctx, int viewResourceId,String[] strings, TypedArray icons,int icn)
            //public ImageAndTextAdapter(Context ctx, int viewResourceId,List lst,MyImageLoader imgldr,TypedArray[] img)
            public ImageAndTextAdapter(Context ctx, int viewResourceId,List lst,MyImageLoader imgldr)

            {
            //super(ctx, viewResourceId, list);
            super(ctx, viewResourceId, lst);

            mInflater = (LayoutInflater)ctx.getSystemService(Context.LAYOUT_INFLATER_SERVICE);

            ct=ctx;
            //mIcon=img;
            countryList=lst;

            mViewResourceId = viewResourceId;
            //mUrl=url;
            imgdownloader=imgldr;

            // activity=a;

            }

            public int getCount()
            {
            return countryList.size();
            }
            public Country getItem(int position)
            {
            return (Country) countryList.get(position);
            }
            public long getItemId(int position)
            {
            return position;
            }

            //Using a ViewHolder means we don’t have to call findViewById
            //for every single view, every time ,
            //which adds up to a decent amount of computational savings
            public static class ViewHolder{

            public ImageView image;
            }

            public View getView(int position, View convertView, ViewGroup parent) {

            if(convertView == null)
            convertView = mInflater.inflate(mViewResourceId, null);

            Country country = getItem(position);

            ImageView iv = (ImageView)convertView.findViewById(R.id.option_icon);

            TextView tv = (TextView)convertView.findViewById(R.id.option_text);

            tv.setText(country.getName());

            imgdownloader.displayImage(country.getUri(), iv);

            return convertView;

            }
            }

            package com.test;

            import android.net.Uri;

            public class Country {

            public String mName=null;
            //public int mImage;
            public String mUri=null;

            /*public Country(String name, String uri )
            {
            this.mName=name;
            this.mUri=uri;
            }*/

            public void setName(String nm)
            {
            this.mName=nm;
            }
            public String getName()
            {
            return mName;
            }

            /*public void setImage(int img)
            {
            this.mImage=img;
            }
            public int getImage()
            {
            return mImage;
            }*/

            public void setUri(String url)
            {
            this.mUri=url;
            }
            public String getUri()
            {
            return mUri;

            }

            }

            countries.xml

            Bhutan
            Colombia
            Italy
            Jamaica
            Kazakhstan
            Kenya

            @drawable/bhutan
            @drawable/colombia
            @drawable/italy
            @drawable/jamaica
            @drawable/kazakhstan
            @drawable/kenya

            http://farm5.static.flickr.com/4026/4415651678_2f8d47d3a7_s.jpg
            http://farm4.static.flickr.com/3037/3043228626_cd4fdbd3ae_s.jpg
            http://farm4.static.flickr.com/3222/2838905452_44eb17e794_s.jpg
            http://farm5.static.flickr.com/4026/4424182649_233e346be0_s.jpg
            http://farm5.static.flickr.com/4026/4644890342_668f75acb1_s.jpg
            http://farm4.static.flickr.com/3361/3442412997_65aa085a59_s.jpg

          • Shirkeshashi04 Android

            there are 2 more XMK files of needed reply me
            thanks

          • Umamaheswari N

            thanks For Code . . it’s Very Useful for all one ..

  • Evan

    Good stuff, but there’s a potential for wasting memory with the imageMap.
    You don’t need to keep the reference to the object in imageMap after you add it to the imageview. I have an endless scrolling adapter, and the map can just grow and grow.

    I just added a line to remove the Bitmap from the map after it was added to the ImageView like so:

    public void displayImage(String url, Activity activity, ImageView imageView) {
    if(imageMap.containsKey(url))
    imageView.setImageBitmap(imageMap.get(url));
    imageMap.remove(url); // we don’t need to keep this on the map
    else {
    queueImage(url, imageView);
    imageView.setImageResource(R.drawable.icon);
    }
    }

    • http://www.codehenge.net Constantine Aaron Cois

      Good idea! If you have time, toss that in a fork of the Github project and issue a pull request. That’s a good thing to add to the baseline demo for other devs to use.

      Thanks for the improvement!

      • Evan

        Done, also converted to use SoftReference in the cache map, as I was getting OOM errors with an endless scrolling adapter until I changed that.

  • Shirkeshashi04 Android

    How To Download a File Only If It Has Been Updated in this example.

    For that I have created Separate FileCache class & get all the file functionality from ImageManager class . it’s work properly .

    I have to do following functionality with this:

    1. Create a HTTP HEAD request.
    2. Read the “Last-Modified” header and convert the string
    3. Read the last modification timestamp of the local file.
    4. Compare the two timstamps.
    5. Download the file from the server if it has been updated.
    6.Save the downloaded file.
    7. Set the last modification timestamp of the file to match the “Last-Modified” header on the server.

    please reply

    • http://www.codehenge.net Constantine Aaron Cois

      Good idea! This will allow the app to update profile pics when they are changed, without re-downloading pics every time. It would be fantastic if you would fork the github project, add this feature, and issue a pull request. I promise I’ll accept the new feature quickly!

  • Anton westbom

    Great stuff worked like a charm with the manager and an imageButton only thing I missed was to set the Tag of the imageButton to the url.
    thank you!

  • Shirkeshashi04 Android

    Hi This is nice tutorial .
    I have separated the functionality of file cache from the ImageManager class. For this I have developed new class i.e. FileCache class and create object of same class in ImageManager class to access this funtionality.
    here I have to implement the functionality like to download the images from the server . first check if file is in SD-card or not? if yes, then check the last-modified date of file(image file) in SD-card and compare it with server last-updated file date using timestamp. If the server updated file is newer than the SD-card file then download it from server to SD-card and display else return the SD-card file . Because of this it’s increase the speed to display the file in list view.
    I have some problem with this i.e. to implement the HEAD method to get the meta-information (i.e. “last-modified” date and “modifiedSince”)from the server of particular file. could you help how to implement this class.
    here is my code:

    public class FileCache {

    File sdDir;

    public FileChache(Activity activity)
    {

    String sdState = android.os.Environment.getExternalStorageState();

    if (android.os.Environment.getExternalStorageState().equals(android.os.Environment.MEDIA_MOUNTED))
    {
    sdDir=new File(android.os.Environment.getExternalStorageDirectory(),”sa”);
    }
    else
    sdDir=activity.getCacheDir();

    if(!sdDir.exists())
    sdDir.mkdirs();
    }

    public File getFile(String url)
    {
    String filename= String.valueOf(url.hashCode());
    System.out.println(“The File name is:”+filename);
    File f= new File(sdDir,filename);
    if(f.exists())
    {
    //Check the last update of file

    try
    {

    HttpClient client= new DefaultHttpClient();
    //HttpGet get = new HttpGet(url);
    HttpHead method = new HttpHead(url);
    HttpResponse response= client.execute(method);

    Header[] s=response.getAllHeaders();
    System.out.println(“THe header from the httpclient:”);
    for(int i=0; i < s.length; i++){
    Header hd = s[i];

    System.out.println("Header Name: "+hd.getName()
    +" "+" Header Value: "+ hd.getValue());
    }

    // here I am getting problem i.e. to implement the HEAD method to get "last-modified" date or "modified-since"

    how to get that header date into date format for comparison.
    please have a look and reply me for the same.

  • Dann Rees

    Hey there, great article. However for some reason getView() isn’t getting called for me and so the list appears empty. Any suggestions? Thanks!

    • http://www.codehenge.net Constantine Aaron Cois

      getView() isn’t being called? That has to be some sort of problem with your adapter. Are you using the github code, or is this from your own implementation?

      • Dann Rees

        Hey, I’m using pretty much the same code. Just changed slightly…

        public class JobItemAdapter extends ArrayAdapter {
        private ArrayList jobs;
        private Activity activity;
        public ImageManager imageManager;

        public JobItemAdapter(Activity a, int textViewResourceId, ArrayList thejobs) {
        super(a, textViewResourceId, thejobs);
        this.jobs = thejobs;
        activity = a;

        imageManager =
        new ImageManager(activity.getApplicationContext());
        }

        @Override
        public int getCount()
        {
        Log.v(“Dann”,”getCount: “+jobs.size());
        return jobs.size();

        }
        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
        View v = convertView;
        ViewHolder holder;
        Log.v(“Dann”,”getView: “+v);
        if (v == null) {
        LayoutInflater vi =
        (LayoutInflater)activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        v = vi.inflate(R.layout.list_item, null);
        holder = new ViewHolder();
        holder.jobtitle = (TextView) v.findViewById(R.id.txtJobTitle);
        holder.location = (TextView) v.findViewById(R.id.txtLocation);
        holder.image = (ImageView) v.findViewById(R.id.imageView1);
        v.setTag(holder);
        }
        else
        holder=(ViewHolder)v.getTag();

        final Job thejob = jobs.get(position);
        if (thejob != null) {
        holder.jobtitle.setText(Job.jobtitle);
        holder.location.setText(Job.location);
        holder.image.setTag(thejob.image_url);
        imageManager.displayImage(thejob.image_url, activity, holder.image);
        }
        return v;
        }

  • Gustavo Uach

    holy sh1t, great article man….. just love it

  • Alex

    Hi, first thanks for that arcticle! Helped me a lot.
    But I having a problem with it.
    If you scroll fast through the list, there is a point were no image at all are displayed.
    And after that even if you scroll normal or slow no image gets loaded again.
    The only thing that help is to remove the image from the imagemap after setting it to the view.
    But even then this problem remains but not so often.

    Anyone expiricend this problem to?

    • Nicole

      I got exactly the same problem, do you find the solution? Thank you.

  • Adam

    Is this actually working for people? In my implementation images load, then when I scroll down and scroll back up they’re gone. And some images don’t load at all

    • http://www.codehenge.net Constantine Aaron Cois

      Have you tried the project from Github? Does it exhibit the same behavior? Sounds like you may not have the tag being set properly, or your app doesn’t have appropriate filesystem permissions for caching…

      • Adam

        yes you are right. I had to add the permission for external storage Thanks!

  • Sarinaditya

    Yes good tutorial.Loved the the other tutorials too

  • Durgeshpathakk

    I AM GETTING THIS ERRO Can only iterate over an array or an instance of java.lang.Iterable pls help

  • Ryan R.

    Hey guys I am encountering a strange bug where the images load fine but when I scroll around some pictures disappear and are gone. I’m running the code unaltered from the github. Check out this screen here: http://dl.dropbox.com/u/3679180/listasyncbug.png

    • Ryan R.

      Hmmm, this bug does not show up when I use the progress_bar branch. I think it has something to do with the SoftReference in the hashmap within ImageManager.java…

      Master:
      private HashMap<String, SoftReference> imageMap = new HashMap<String, SoftReference>();

      vs.

      Progress_bar branch
      private HashMap imageMap = new HashMap();

      Using the latter, the images do not disappear when scrolling. Not sure why?

      • http://www.codehenge.net Constantine Aaron Cois

        I think you may be right – but i’m not completely sure why the softreferences would fix it, either. I can’t reproduce the behavior in either branch. If you find out any information on what was gong on and what changes with softreferences, I’d be very interested to hear about it!

        • Annonymous

          Removing the softreferences worked for me as well. Couldn’t figure out why, but may be noteworthy to update the git repository.

        • Michael Stratman

          great tutorial, did exactly what I wanted to do. Getting rid of the soft reference fixed the issue with images being left blank after scrolling down and back up. Thanks!

      • Aaa

        Can you please post the other changes you had to do to make it work ?
        Thanks !

        • Ryan R

          I replaced the HashMap with the one used in the Progress_bar branch.

          Basically just use:
          private HashMap imageMap = new HashMap();

          You will need to fix a few spots in the code where a SoftReference is passed instead of a bitmap. Check out the Progress_bar sample to get it to work.

          Not sure what disadvantages this has, but I’ve been testing my code all week and it seems to be fine.

      • http://profiles.google.com/pock3t Lucas F.

        Thanks a lot, I had same issue and your fix worked for me !

  • Anonymous

    Great Tutorial.. Does this code handle ListView’s recycled views properly?

  • http://profiles.google.com/pekechis Juan Diego Pérez

    it’s not the tutorial…such a great, clean and elegant code…thank you so much

  • stephen

    i m getting an error on jsonparser

    • Hawkhere

      you need to add json_simple-1.1.jar into build path i guess

  • Han Junho

    I’ve changed your “displayImage(…)” function like this.

    ‘Cause I have some problem with your code. If I got back from the other Activity, Hashmap included some null pointers(maybe cause of SoftReference), and My list activity shows blank image. So, I fixed your code some.. If HashMap returns null, I do queueImage() again.

    public void displayImage(String url, Context context, ImageView imageView) {
    if(mImageCacheMap.containsKey(url)) {

    Bitmap bitmapToShow = mImageCacheMap.get(url).get();

    if(bitmapToShow != null) {
    //> Image was cached well
    imageView.setImageBitmap(bitmapToShow);
    return;
    }
    }

    //> Image was cached failed
    imageView.setImageResource(R.drawable.default_photo);
    queueImage(url, context, imageView);
    }

    Thank you for your great codes, seriously. and I hope my words help you. :)

  • http://twitter.com/Abhijitalways Abhijit Nukalapati

    Thanks for the great explanation! Just a tip – the runOnUiThread seems to block the UI and freeze certain phones such as Droid X. However, when I replaced it with a handler, it seemed to work without any problem.

  • Mustafaev M

    Useless..Your tutorial not work. JSONParser is not found.

    • Hawkhere

      you need to add json_simple-1.1.jar into build path i guess

    • http://www.codehenge.net Constantine Aaron Cois

      You should refrain from calling someone’s work useless, if you expect to get help. Hawkhere is right, did you add json_simple-1.1.jar to the path, and make sure that your objects are using that class, instead of the org.json class?

  • Anonymous

    This appears like it will load way to many threads. Only took a glance though. Here is my solution, it seems to work well across the board. http://www.ryangmattison.com/post/2011/12/15/AsynchronousImageProvider.aspx

    • http://www.codehenge.net Constantine Aaron Cois

      It certainly can, but that entirely depends on your search parameters. This is just an instructive demonstration, and is meant to illustrate the techniques, not provide a full-firehose scalable solution.

      Thanks for the link to your work, looks like an excellent resource!

  • Thankful Man

    Thx so much bro! I got your code from Github and it worked out of the box! (Almost) Just had to change the Twitter class to my own and voila, it worked so great! And it even caches the images to the sdcard for later!!!

    You rock!!! You’re my hero!!!

  • sebataz

    This class is just what I needed, of course I adapted it for my needs. But I have a question, sometimes the call to the Clean method will cause an ArrayIndexOutOfBoundsException. I try to check the size of the stack before the actual remove, but it doesn’t solve the problem.

  • Ryan R

    How can I add an animation to the image when it loads, for example, a fade in? I tried but the animation is very choppy.
    Secondly, how can we show selector states for each image view, for example, pressed and focused states.

  • Chris

    Awesome Tutorial .. Beautifully explained Thank you very much :)

  • Jenkies

    This tutorial was going wonderfully until I had to switch to android 4.0.

    Any information on why the Twitter Client Activity doesn’t work beyond 2.2 for me?

  • Sunjunliang52

    thank u ! good

  • Saltonpepper

    Great tutorial – matched my design pattern exactly, and worked within an hour. Thanks!

  • Maidul Islam

    Thanks I learn a lot of things from your blog.Now I am facing some problem.Please have a look that.
    I uses this link.
    https://maps.googleapis.com/maps/api/place/search/json?location=40.7143528,-74.0059731&radius=1000&types=restaurant&sensor=false&key=AIzaSyDbiWWIOmc08YSb9DAkdyTWXh_PirVuXpM

    I want add this map.put(“icon”, “icon: ” + e.getString(“icon”));

    new String[] {“icon”, “name”, “vicinity” },
    new int[] {R.id.imageview1, R.id.item_title, R.id.item_subtitle });
    Please look this matter.I am hang over from this from last weeks.
    How can i show this images.Please let me know.
    Hope fully I can expect answer from you ASAP.
    Please help me.
    Thanks and Regards,
    Maidul

  • Chris R.

    Hi, I have a strange bug that I can’t pin down: When my list is populated, all icons are displayed correctly *except* the one at the top of the list. Instead a random one from the rest of the list is displayed. When I fling up and down the list, the icon is displayed correctly. This happens everytime the activity gets shown. This behaviour appears always but stops when disk caching is commented out. Any ideas or hints? I am going crazy about this. Thanks!

    • Bill S

      I am having a similar problem to the one described by Chris R. In my case, one of the images in a list gets repeated for some reason. It happens most but not all of the time. Watching it closely, I can see that the right image is written to the list row initially, but then it gets overwritten by another image that actually belongs to a different row.

      Did you solve your problem? Any suggestions? Thanks!

      • Bill S

        I found a solution to this problem. I was getting an occasional overwritten image because getView() may be called multiple times on the same list item getView just to optimize view rendering and discard the result. Unfortunately that’s a problem for this code, since it interprets and executes every result, through a runnable if needed. The result is that an incorrect bitmap can sometimes be written on top of a correct one.

        The solution is to make sure your ListView has height set to wrap_content, instead of fill_parent. Then ListView doesn’t have to call getView() multiple times, and you don’t get the occasional duplicate.

        For more info, see: http://stackoverflow.com/questions/2618272/custom-listview-adapter-getview-method-being-called-multiple-times-and-in-no-co

  • Gyubok Baik

    Hi, I have two issues I cannot solve from this code.

    1. Images don’t initially appear, I need to scroll down and up again for the images to be updated.
    2. The only image that doesn’t get pulled is a .bmp and I was wondering whether this is an issue with the code

  • Android Developer

    Unable to resolve error java.lang.NoClassDefFoundError: org.json.simple.parser.JSONParser on line JSONParser parser=new JSONParser(); in Example.java
    I have added this jar file in my Build Path, but still not able to resolve this. Error occurs after successful compilation. Please help. Thanks in advance.

  • Fansher

    Hi

    I’m experiencing some problems with the code, which i have not altered but just downloaded.

    I get an error with the lines
    imageManager = new ImageManager(activity.getApplicationContext());

    where it says “the constructor ImageManager(context) is undefined”

    also i get an error with
    imageManager.displayImage(tweet.image_url, activity, holder.image);

    Where it says “The method displayImage(String, ImageView, int) in the type
    ImageManager is not applicable for the arguments (String,
    Activity, ImageView)”

    Can somebody please help me with this problem?

    • Fansher

      Never mind, solved the problem on my own.

      • http://www.codehenge.net Constantine Aaron Cois

        Great! Mind sharing your experience, in case someone else comes along with the same problem?

    • http://twitter.com/derrichh Derric@HoodHype.com

      I am having the same problem at:
      imageManager = new ImageManager(activity.getApplicationContext());

      mind sharing what you did?

  • Guest

    You should implement image loading on setViewBinder in the adapter this way it will be even faster and called only once

  • ramakrishna k

    I am trying to use this for remoteviews for my appwidget. I want to load images in my appwidget from database. But unfortunately its not working. Can you guide me how to update a remoteview using this.

  • Thomas

    Yes Quite

  • truehybridx

    Very helpful for the tutorial thanks!!

    I do believe a Stack for the ImageRefs could be improved upon, as it stands the bottom image appears and works its way to the top of the list (given the first in first out behavior of the stack)

  • Pingback: how to implement lazyloading in the adapter : Android Community - For Application Development

  • codefan

    I’m getting an error for line “imageManager.displayImage(camera.thumbnail_url, activity, holder.image);”

    The method displayImage(String, ImageView, int) in the type ImageManager is not applicable for the arguments (String, Activity, ImageView)

    Any ideas?

  • Quique

    Hi! Great tutorial.
    I’ve cloned the project from Github.
    I have an issue:
    the method displayImage from ImageManager has the following form:
    public void displayImage(String url, ImageView imageView, int defaultDrawableId)
    but it is called from TweetItemAdapter using the following sentence:
    imageManager.displayImage(tweet.image_url, activity, holder.image);
    What should I do?

    Thank you in advance.

    • Untouchab1e

      Yeah, I realized the same thing. Not sure how to solve it. The code on Github is not the same as the one described in the post here.

  • Untouchab1e

    Would be great if you could update the code on your Github with the code described in this post. Cheers!

52 queries in 19.981 seconds