title: Android: Bitmap not shown in ImageView
body: <p>I'm developing an Activity which loads a list of URLs of images and displays them in a Gallery view. For better performance I decided to asynchronously load the images and cache them on the SD card.</p>
  
  <p>I found this:
  <a href="http://blog.jteam.nl/2009/09/17/exploring-the-world-of-android-part-2/" rel="nofollow">http://blog.jteam.nl/2009/09/17/exploring-the-world-of-android-part-2/</a>
  and adapted the code. As long as I didn't cache the files on the SD card everything worked fine, but now I see a strange behavior. I guess it's easier to describe the problem with code (see comments):</p>
  
  <pre><code>public class AsyncImageLoader {
      private final static String TAG = "AsyncImageLoader";
      private HashMap&lt;String, SoftReference&lt;Bitmap&gt;&gt; bitmapMap;
  
      public AsyncImageLoader() {
          this.bitmapMap = new HashMap&lt;String, SoftReference&lt;Bitmap&gt;&gt;();
      }
  
      // This method is called to load images asynchronously.
      // If the Bitmap is cached in bitmapMap and the reference is
      // still valid, the Bitmap is returned immediately.
      // If the Bitmap is not in bitmapMap, another thread is started
      // to load the image. Once it has been loaded, a callback method
      // is called.
      public Bitmap loadBitmap(final String imageUrl,
              final IImageLoadListener imageCallback, final int minWidth,
              final int minHeight) {
          if (this.bitmapMap.containsKey(imageUrl)) {
              SoftReference&lt;Bitmap&gt; softReference = this.bitmapMap.get(imageUrl);
              Bitmap bitmap = softReference.get();
              if (bitmap != null) {
                  Log.d(TAG, "Using a previously loaded Bitmap container");
                  return bitmap;
              }
          }
          Log.d(TAG, "Need to load the Bitmap container");
          final Handler handler = new Handler() {
              @Override
              public void handleMessage(Message message) {
                  imageCallback.imageLoaded((Bitmap) message.obj, imageUrl);
              }
          };
          new Thread() {
              @Override
              public void run() {
                  Bitmap bitmap = loadImageFromUrl(imageUrl, minWidth, minHeight);
                  bitmapMap.put(imageUrl, new SoftReference&lt;Bitmap&gt;(bitmap));
                  Message message = handler.obtainMessage(0, bitmap);
                  handler.sendMessage(message);
              }
          }.start();
          return null;
      }
  
      // Here is part of the magic behind the scenes:
      // I get an InputStream (see below) and open it just
      // to get the Bitmap's dimensions. Then I calculate the
      // required width (best size for minWidth and minHeight)
      // and then I create a downsampled Bitmap.
      private synchronized static Bitmap loadImageFromUrl(String urlString,
              int minWidth, int minHeight) {
          Bitmap bitmap = null;
          try {
              InputStream is = getInputStream(urlString);
              BitmapFactory.Options options1 = new BitmapFactory.Options();
              options1.inJustDecodeBounds = true;
              BitmapFactory.decodeStream(is, null,
                      options1);
              is.close();
  
              int tempWidth = options1.outWidth;
              int tempHeight = options1.outHeight;
              int scale = 1;
              while (true) {
                  if (tempWidth / 2 &lt; minWidth || tempHeight / 2 &lt; minHeight) {
                      break;
                  }
                  tempWidth /= 2;
                  tempHeight /= 2;
                  scale *= 2;
              }
  
              BitmapFactory.Options options2 = new BitmapFactory.Options();
              options2.inSampleSize = scale;
              bitmap = BitmapFactory.decodeStream(getInputStream(urlString),
                      null, options2);
              // More magic: Once the Bitmap has been downloaded,
              // I create a file on the SD card so that I don't
              // need to download it again.
              File cacheFile = getCacheFile(urlString);
              if (cacheFile == null) {
                  cacheImage(urlString, bitmap);
              }
          } catch (IOException e) {
              Log.e(TAG, e.getMessage(), e);
          }
          return bitmap;
      }
  
      // I check whether a cache file exists for the URL. If it exists
      // I create the InputStream from this file and I create the InputStream
      // from the URL otherwise.
      private synchronized static InputStream getInputStream(String urlString)
              throws IOException {
          File cacheFile = getCacheFile(urlString);
          if (cacheFile != null) {
              // HERE seems to be something wrong. The file
              // is valid and can be read, but if I return a
              // FileInputStream (or InputStream using 
              // cacheFile.toURL().openStream()) the image is not
              // shown.
              Log.d(TAG, "Using " + cacheFile.getAbsolutePath());
              return new FileInputStream(cacheFile);
          } else {
              // In this case, the image is shown everytime!
              Log.d(TAG, "Downloading " + urlString);
              URL url = new URL(urlString);
              return url.openStream();
          }
      }
  
      // This is just a helper method that returns the cache directory
      // and if it doesn't exist it will be created first.
      private synchronized static File getImageCacheDir() {
          File imageCacheDir = new File(Environment.getExternalStorageDirectory()
                  .getAbsolutePath() + "/" + Commons.APP_DIRECTORY + "/cache/");
          if (!imageCacheDir.exists()) {
              imageCacheDir.mkdirs();
          }
          return imageCacheDir;
      }
  
      // This method returns a File object for the cache file if it exists
      // and returns null otherwise.
      private static File getCacheFile(String urlString) {
          File file = new File(getImageCacheDir(), CryptoUtils.md5(urlString)
                  + ".jpg");
          if (!file.exists()) {
              return null;
          }
          return file;
      }
  
      // Even more magic:
      // At first I create a file with a .tmp extension just in case two
      // threads try to read and write at the same time. The image is
      // downloaded into the temporary file. If this succeeds the .tmp
      // extension will be removed.
      private synchronized static void cacheImage(String urlString, Bitmap bitmap) {
          String filename = CryptoUtils.md5(urlString) + ".jpg";
          File tmpFile = new File(getImageCacheDir(), filename + ".tmp");
          if (tmpFile.exists()) {
              Log.d(TAG, "Another process seems to create the cache file.");
              return;
          }
          File file = new File(getImageCacheDir(), filename);
          FileOutputStream os = null;
          try {
              os = new FileOutputStream(tmpFile);
              boolean retval = bitmap
                      .compress(Bitmap.CompressFormat.JPEG, 90, os);
              if (retval) {
                  tmpFile.renameTo(file);
                  Log.d(TAG, "Created cache image. Renaming temporary file.");
              } else {
                  tmpFile.delete();
                  Log.d(TAG,
                          "Could not create cache image. Deleting temporary file.");
              }
          } catch (FileNotFoundException e) {
              Log.e(TAG, e.getMessage(), e);
          } finally {
              if (os != null) {
                  try {
                      os.close();
                  } catch (IOException e) {
                      Log.e(TAG, e.getMessage(), e);
                  }
              }
          }
      }
  
      public interface IImageLoadListener {
          public void imageLoaded(Bitmap imageBitmap, String imageUrl);
      }
  }
  </code></pre>
  
  <p>Here is another big chunk of code:</p>
  
  <pre><code>public class DisplayPhotoActivity extends Activity {
      private final static int PROGRESS_DIALOG = 1;
      private final static int PHOTO_PICKER = 2;
      private final static int GALLERY_REQUEST_CODE = 1;
      private final static int CAMERA_REQUEST_CODE = 2;
      private AsyncImageLoader asyncImageLoader;
      private Gallery gallery;
      private List&lt;Photo&gt; images;
      private ImageAdapter imageAdapter;
      private int loadingCounter;
      private Uri outputFileUri;
      private Display display;
  
      @Override
      protected void onCreate(Bundle savedInstanceState) {
          super.onCreate(savedInstanceState);
          setContentView(R.layout.display_photo);
  
          this.asyncImageLoader = new AsyncImageLoader();
          this.loadingCounter = 0;
  
          this.images = new ArrayList&lt;Photo&gt;();
          this.imageAdapter = new ImageAdapter(this, this.images);
  
          final ImageView imageView = (ImageView) findViewById(R.id.large_image);
  
          this.gallery = (Gallery) findViewById(R.id.gallery);
          this.gallery.setAdapter(this.imageAdapter);
  
          // The Gallery is on top of the Activity and there is a bigger
          // ImageView below it. I want the image to be shown in the bigger
          // ImageView when it has been clicked in the Gallery.
          this.gallery.setOnItemClickListener(new OnItemClickListener() {
              public void onItemClick(AdapterView&lt;?&gt; parent, View v,
                      int position, long id) {
                  // Here is the part where I load the Bitmap.
                  // This part works!
                  Bitmap bitmap = asyncImageLoader.loadBitmap(images
                          .get(position).getUrl(),
                          new AsyncImageLoader.IImageLoadListener() {
                              public void imageLoaded(Bitmap imageBitmap,
                                      String imageUrl) {
                                  if (imageBitmap != null) {
                                      imageView.setImageBitmap(imageBitmap);
                                      gallery.invalidate();
                                  }
                                  loadingCounter--;
                                  if (loadingCounter == 0) {
                                      getParent()
                                              .setProgressBarIndeterminateVisibility(
                                                      false);
                                  }
                              }
                          }, 300, 300);
                  if (bitmap != null) {
                      Log.d("DisplayPhotoActivity", "Image was cached. (I)");
                      imageView.setImageBitmap(bitmap);
                      gallery.invalidate();
                      loadingCounter--;
                      if (loadingCounter == 0) {
                          getParent()
                                  .setProgressBarIndeterminateVisibility(false);
                      }
                  }
              }
          });
  
          this.display = (Display) getIntent().getParcelableExtra("display");
  
          populatePhotos(true);
      }
  
      // There is a known bug and this is the bug fix.
      // See: http://code.google.com/p/android/issues/detail?id=8488
      @Override
      public void onPause() {
          super.onPause();
          System.gc();
      }
  
      @Override
      public void onDestroy() {
          super.onDestroy();
          this.asyncImageLoader = null;
          System.gc();
      }
  
      @Override
      protected Dialog onCreateDialog(int id) {
          switch (id) {
          case PROGRESS_DIALOG:
              ProgressDialog progressDialog = new ProgressDialog(this);
              progressDialog.setMessage("Loading...");
              return progressDialog;
          case PHOTO_PICKER:
              final CharSequence[] items = {
                      getText(R.string.photo_picker_choose_from_gallery),
                      getText(R.string.photo_picker_take_a_photo) };
  
              AlertDialog.Builder builder = new AlertDialog.Builder(this);
              builder.setTitle(getText(R.string.photo_picker_title));
              builder.setItems(items, new DialogInterface.OnClickListener() {
                  public void onClick(DialogInterface dialog, int item) {
                      switch (item) {
                      case 0:
                          onChooseFromGalleryClick();
                          break;
                      case 1:
                          onTakeAPhoto();
                      }
                  }
              });
              return builder.create();
          default:
              return null;
          }
      }
  
      private void onChooseFromGalleryClick() {
          Intent intent = new Intent();
          intent.setType("image/*");
          intent.setAction(Intent.ACTION_GET_CONTENT);
          startActivityForResult(Intent.createChooser(intent,
                  getText(R.string.photo_picker_choose_from_gallery)),
                  GALLERY_REQUEST_CODE);
      }
  
      private void onTakeAPhoto() {
          Intent cameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
          File appDirectory = new File(Environment.getExternalStorageDirectory(),
                  Commons.APP_DIRECTORY);
  
          if (!appDirectory.exists()) {
              appDirectory.mkdirs();
          }
  
          File photo = new File(appDirectory, "/pic.jpg");
  
          this.outputFileUri = Uri.fromFile(photo);
  
          cameraIntent.putExtra(MediaStore.EXTRA_OUTPUT, this.outputFileUri);
          startActivityForResult(Intent.createChooser(cameraIntent,
                  getText(R.string.photo_picker_take_a_photo)),
                  CAMERA_REQUEST_CODE);
      }
  
      @Override
      public boolean onCreateOptionsMenu(Menu menu) {
          MenuItem updateMenuItem = menu.add(this.getResources().getString(
                  R.string.update_menuitem_text));
          updateMenuItem
                  .setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
                      public boolean onMenuItemClick(MenuItem item) {
                          populatePhotos(true);
                          return false;
                      }
                  });
          updateMenuItem.setIcon(R.drawable.ic_menu_refresh);
  
          MenuItem uploadPhotoMenuItem = menu.add(this.getResources().getString(
                  R.string.photo_picker_title));
          uploadPhotoMenuItem
                  .setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
                      public boolean onMenuItemClick(MenuItem item) {
                          showDialog(PHOTO_PICKER);
                          return true;
                      }
                  });
          uploadPhotoMenuItem.setIcon(getResources().getDrawable(
                  android.R.drawable.ic_menu_add));
          return true;
      }
  
      @Override
      protected void onActivityResult(int requestCode, int resultCode, Intent data) {
          if (resultCode == RESULT_OK) {
              Uri uri = null;
              switch (requestCode) {
              case GALLERY_REQUEST_CODE:
                  uri = data.getData();
                  break;
              case CAMERA_REQUEST_CODE:
                  uri = this.outputFileUri;
                  break;
              }
              if (uri == null) {
                  return;
              }
  
              Display display = this.getIntent().getParcelableExtra("display");
  
              this.startService(PhotoUploadService.getIntent(this, display, uri));
          }
      }
  
      private void populatePhotos(boolean forceSync) {
          new ImageListLoader().execute(this.display.getId(), forceSync ? 1 : 0);
      }
  
      private void updateInitialImage(Bitmap bitmap) {
          ImageView imageView = (ImageView) findViewById(R.id.large_image);
          if (imageView.getDrawable() == null) {
              Log.d("DisplayPhotoActivity", "No initial image set.");
              imageView.setImageBitmap(bitmap);
          }
      }
  
      public class ImageListLoader extends AsyncTask&lt;Integer, Void, Void&gt; {
          @Override
          protected void onPreExecute() {
              showDialog(PROGRESS_DIALOG);
          }
  
          @Override
          protected Void doInBackground(Integer... params) {
              if (params.length &lt; 1) {
                  return null;
              }
              int displayId = params[0];
              boolean forceSync = params.length == 2 &amp;&amp; params[1] == 1;
              List&lt;Photo&gt; photos = LocalDatabaseHelper.getInstance()
                      .getPhotosByDisplayId(displayId, forceSync);
              images.clear();
              for (Photo photo : photos) {
                  images.add(photo);
              }
              Log.d("FOO", "Size: " + images.size());
              runOnUiThread(new Runnable() {
                  public void run() {
                      imageAdapter.notifyDataSetChanged();
                  }
              });
              return null;
          }
  
          @Override
          protected void onPostExecute(Void result) {
              dismissDialog(PROGRESS_DIALOG);
          }
      }
  
      public class ImageAdapter extends BaseAdapter {
          private Context context;
          private List&lt;Photo&gt; images;
          private int galleryItemBackground;
  
          public ImageAdapter(Context context, List&lt;Photo&gt; images) {
              this.context = context;
              this.images = images;
              TypedArray attr = this.context
                      .obtainStyledAttributes(R.styleable.DisplayPhotoActivity);
              this.galleryItemBackground = attr
                      .getResourceId(
                              R.styleable.DisplayPhotoActivity_android_galleryItemBackground,
                              0);
              attr.recycle();
  
          }
  
          public int getCount() {
              return this.images.size();
          }
  
          public Object getItem(int position) {
              return position;
          }
  
          public long getItemId(int position) {
              return position;
          }
  
          public View getView(int position, View convertView, ViewGroup parent) {
              getParent().setProgressBarIndeterminateVisibility(true);
              loadingCounter++;
              final ImageView imageView = new ImageView(this.context);
              String imageUrl = this.images.get(position).getUrl();
              Log.d("DisplayPhotoActivity", "getView(): " + imageUrl);
              // Here is almost the same code as in the OnClickListener
              // above, but this does not work as expected. If the image
              // has never been cached (neither as a SoftReference nor as
              // a file on the SD card) the image is downloaded and shown.
              // No problem... But if the image is cached, it won't show up!
              Bitmap bitmap = asyncImageLoader.loadBitmap(
                      imageUrl,
                      new AsyncImageLoader.IImageLoadListener() {
                          public void imageLoaded(Bitmap imageBitmap,
                                  String imageUrl) {
                              Log.d("DisplayPhotoActivity", " - imageLoaded:");
                              if (imageBitmap != null) {
                                  Log.d("DisplayPhotoActivity", "        - imageBitmap != null");
                                  updateInitialImage(imageBitmap);
                                  Log.d("DisplayPhotoActivity", "        - updateInitialImage(imageBitmap)");
                                  imageView.setImageBitmap(imageBitmap);
                                  Log.d("DisplayPhotoActivity", "        - setImageBitmap(imageBitmap)");
                                  int width = Math.round((150 * imageBitmap
                                          .getWidth()) / imageBitmap.getHeight());
                                  imageView
                                          .setLayoutParams(new Gallery.LayoutParams(
                                                  width, 150));
                                  //imageView.invalidate();
                                  //gallery.invalidate();
                              }
                              loadingCounter--;
                              if (loadingCounter == 0) {
                                  getParent()
                                          .setProgressBarIndeterminateVisibility(
                                                  false);
                              }
                          }
                      }, 300, 300);
              if (bitmap != null) {
                  // TODO: Something doesn't work here...
                  Log.d("DisplayPhotoActivity", "Image was cached");
                  Log.d("DisplayPhotoActivity",
                          bitmap.getWidth() + "x" + bitmap.getHeight());
                  updateInitialImage(bitmap);
                  imageView.setImageBitmap(bitmap);
                  int width = Math.round((150 * bitmap.getWidth())
                          / bitmap.getHeight());
                  imageView.setLayoutParams(new Gallery.LayoutParams(width, 150));
                  imageView.invalidate();
                  gallery.invalidate();
                  loadingCounter--;
                  if (loadingCounter == 0) {
                      getParent().setProgressBarIndeterminateVisibility(false);
                  }
              }
  
              imageView.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
              imageView.setBackgroundResource(galleryItemBackground);
              imageView.setImageDrawable(getResources().getDrawable(
                      R.drawable.loading));
              imageView.setLayoutParams(new Gallery.LayoutParams(200, 150));
              return imageView;
          }
  
          public float getScale(boolean focused, int offset) {
              return Math.max(0, 1.0f / (float) Math.pow(2, Math.abs(offset)));
          }
      }
  }
  </code></pre>
  
  <p>Here is the DDMS output:</p>
  
  <pre><code>07-25 02:34:58.496: DEBUG/DisplayPhotoActivity(23589): getView(): http://[...]/files/814efa535ed97cf44ea3dc3a1c15c3fb/1_1311540992715.jpg
  07-25 02:34:58.496: DEBUG/AsyncImageLoader(23589): Need to load the Bitmap container
  07-25 02:34:58.503: DEBUG/DisplayPhotoActivity(23589): getView(): http://[...]/files/814efa535ed97cf44ea3dc3a1c15c3fb/1_1311540992715.jpg
  07-25 02:34:58.503: DEBUG/AsyncImageLoader(23589): Need to load the Bitmap container
  07-25 02:35:01.031: DEBUG/AsyncImageLoader(23589): Created cache image. Renaming temporary file.
  07-25 02:35:01.031: DEBUG/DisplayPhotoActivity(23589):  - imageLoaded:
  07-25 02:35:01.031: DEBUG/DisplayPhotoActivity(23589):     - imageBitmap != null
  07-25 02:35:01.031: DEBUG/DisplayPhotoActivity(23589): No initial image set.
  07-25 02:35:01.031: DEBUG/DisplayPhotoActivity(23589):     - updateInitialImage(imageBitmap)
  07-25 02:35:01.031: DEBUG/DisplayPhotoActivity(23589):     - setImageBitmap(imageBitmap)
  07-25 02:35:01.597: DEBUG/AsyncImageLoader(23589): Using /mnt/sdcard/PDCollector/cache/4057d5bd1cd9ba7204371ec422ea884a.jpg
  07-25 02:35:01.605: DEBUG/AsyncImageLoader(23589): Using /mnt/sdcard/PDCollector/cache/4057d5bd1cd9ba7204371ec422ea884a.jpg
  07-25 02:35:01.800: DEBUG/AsyncImageLoader(23589): Using /mnt/sdcard/PDCollector/cache/4057d5bd1cd9ba7204371ec422ea884a.jpg
  07-25 02:35:01.800: DEBUG/AsyncImageLoader(23589): Using /mnt/sdcard/PDCollector/cache/4057d5bd1cd9ba7204371ec422ea884a.jpg
  07-25 02:35:01.804: DEBUG/DisplayPhotoActivity(23589):  - imageLoaded:
  07-25 02:35:01.804: DEBUG/DisplayPhotoActivity(23589):     - imageBitmap != null
  07-25 02:35:01.804: DEBUG/DisplayPhotoActivity(23589): No initial image set.
  07-25 02:35:01.804: DEBUG/DisplayPhotoActivity(23589):     - updateInitialImage(imageBitmap)
  07-25 02:35:01.804: DEBUG/DisplayPhotoActivity(23589):     - setImageBitmap(imageBitmap)
  07-25 02:35:02.027: DEBUG/AsyncImageLoader(23589): Using /mnt/sdcard/PDCollector/cache/4057d5bd1cd9ba7204371ec422ea884a.jpg
  07-25 02:35:02.027: DEBUG/AsyncImageLoader(23589): Using /mnt/sdcard/PDCollector/cache/4057d5bd1cd9ba7204371ec422ea884a.jpg
  07-25 02:35:02.082: DEBUG/DisplayPhotoActivity(23589):  - imageLoaded:
  07-25 02:35:02.082: DEBUG/DisplayPhotoActivity(23589):     - imageBitmap != null
  07-25 02:35:02.082: DEBUG/DisplayPhotoActivity(23589):     - updateInitialImage(imageBitmap)
  07-25 02:35:02.082: DEBUG/DisplayPhotoActivity(23589):     - setImageBitmap(imageBitmap)
  07-25 02:35:02.082: DEBUG/DisplayPhotoActivity(23589): getView(): http://[...]/files/814efa535ed97cf44ea3dc3a1c15c3fb/1_1311540992715.jpg
  07-25 02:35:02.082: DEBUG/AsyncImageLoader(23589): Using a previously loaded Bitmap container
  07-25 02:35:02.082: DEBUG/DisplayPhotoActivity(23589): Image was cached
  07-25 02:35:02.082: DEBUG/DisplayPhotoActivity(23589): 640x480
  07-25 02:35:02.257: DEBUG/DisplayPhotoActivity(23589):  - imageLoaded:
  07-25 02:35:02.257: DEBUG/DisplayPhotoActivity(23589):     - imageBitmap != null
  07-25 02:35:02.257: DEBUG/DisplayPhotoActivity(23589):     - updateInitialImage(imageBitmap)
  07-25 02:35:02.257: DEBUG/DisplayPhotoActivity(23589):     - setImageBitmap(imageBitmap)
  </code></pre>
  
  <p>I hope someone can explain this strange behavior. Thanks in advance.</p>