title: Android: download images and save them as cache
body: <p>I have this class which downloads images from the web, and I need to modify my code so the downloaded images are saved on the SDCard. Since I take this code from the internet and I'm a beginner, I'm pretty clueless to optimize it for the cache things.</p>
  <p>I'm open to any kind of solution.</p>
  <pre><code>public class ImageThreadLoader {
      private static final String TAG = &quot;ImageThreadLoader&quot;;
  
      // Global cache of images.
      // Using SoftReference to allow garbage collector to clean cache if needed
      private final HashMap&lt;String, SoftReference&lt;Bitmap&gt;&gt; Cache = new HashMap&lt;String,  SoftReference&lt;Bitmap&gt;&gt;();
  
      private final class QueueItem {
          public URL url;
          public ImageLoadedListener listener;
      }
      private final ArrayList&lt;QueueItem&gt; Queue = new ArrayList&lt;QueueItem&gt;();
  
      private final Handler handler = new Handler();  // Assumes that this is started from the main (UI) thread
      private Thread thread;
      private QueueRunner runner = new QueueRunner();;
  
      /** Creates a new instance of the ImageThreadLoader */
      public ImageThreadLoader() {
          thread = new Thread(runner);
      }
  
      /**
       * Defines an interface for a callback that will handle
       * responses from the thread loader when an image is done
       * being loaded.
       */
      public interface ImageLoadedListener {
          public void imageLoaded(Bitmap imageBitmap );
      }
  
      /**
       * Provides a Runnable class to handle loading
       * the image from the URL and settings the
       * ImageView on the UI thread.
       */
      private class QueueRunner implements Runnable {
          public void run() {
              synchronized(this) {
                  while(Queue.size() &gt; 0) {
                      final QueueItem item = Queue.remove(0);
  
                      // If in the cache, return that copy and be done
                      if( Cache.containsKey(item.url.toString()) &amp;&amp; Cache.get(item.url.toString()) != null) {
                          // Use a handler to get back onto the UI thread for the update
                          handler.post(new Runnable() {
                              public void run() {
                                  if( item.listener != null ) {
                                      // NB: There's a potential race condition here where the cache item could get
                                      //     garbage collected between when we post the runnable and it's executed.
                                      //     Ideally we would re-run the network load or something.
                                      SoftReference&lt;Bitmap&gt; ref = Cache.get(item.url.toString());
                                      if( ref != null ) {
                                          item.listener.imageLoaded(ref.get());
                                      }
                                  }
                              }
                          });
                      } else {
                          final Bitmap bmp = readBitmapFromNetwork(item.url);
                          if( bmp != null ) {
                              Cache.put(item.url.toString(), new SoftReference&lt;Bitmap&gt;(bmp));
  
                              // Use a handler to get back onto the UI thread for the update
                              handler.post(new Runnable() {
                                  public void run() {
                                      if( item.listener != null ) {
                                          item.listener.imageLoaded(bmp);
                                      }
                                  }
                              });
                          }
  
                      }
  
                  }
              }
          }
      }
  
      /**
       * Queues up a URI to load an image from for a given image view.
       *
       * @param uri   The URI source of the image
       * @param callback  The listener class to call when the image is loaded
       * @throws MalformedURLException If the provided uri cannot be parsed
       * @return A Bitmap image if the image is in the cache, else null.
       */
      public Bitmap loadImage( final String uri, final ImageLoadedListener listener) throws MalformedURLException {
          // If it's in the cache, just get it and quit it
          if( Cache.containsKey(uri)) {
              SoftReference&lt;Bitmap&gt; ref = Cache.get(uri);
              if( ref != null ) {
                  return ref.get();
              }
          }
  
          QueueItem item = new QueueItem();
          item.url = new URL(uri);
          item.listener = listener;
          Queue.add(item);
  
          // start the thread if needed
          if( thread.getState() == State.NEW) {
              thread.start();
          } else if( thread.getState() == State.TERMINATED) {
              thread = new Thread(runner);
              thread.start();
          }
          return null;
      }
  
      /**
       * Convenience method to retrieve a bitmap image from
       * a URL over the network. The built-in methods do
       * not seem to work, as they return a FileNotFound
       * exception.
       *
       * Note that this does not perform any threading --
       * it blocks the call while retrieving the data.
       *
       * @param url The URL to read the bitmap from.
       * @return A Bitmap image or null if an error occurs.
       */
      public static Bitmap readBitmapFromNetwork( URL url ) {
          InputStream is = null;
          BufferedInputStream bis = null;
          Bitmap bmp = null;
          try {
              URLConnection conn = url.openConnection();
              conn.connect();
              is = conn.getInputStream();
              bis = new BufferedInputStream(is);
              bmp = BitmapFactory.decodeStream(bis);
          } catch (MalformedURLException e) {
              Log.e(TAG, &quot;Bad ad URL&quot;, e);
          } catch (IOException e) {
              Log.e(TAG, &quot;Could not get remote ad image&quot;, e);
          } finally {
              try {
                  if( is != null )
                      is.close();
                  if( bis != null )
                      bis.close();
              } catch (IOException e) {
                  Log.w(TAG, &quot;Error closing stream.&quot;);
              }
          }
          return bmp;
      }
  
  }
  </code></pre>