Async Loading Images on Android Like a Big Baws

I think probably the most appropriate content for an article with this title would be about using Rx with Glide. Anyway, this one isn’t. The main point of this is to “quickly do the thing” with out-of-box Android facilities, with a focus on maintainable code.

An Approach For Dealing With Resources

  • Make the best use of the xml facilities that I can;
  • In Java, use collections by default when dealing with resource ids;
  • Try to use new source files where appropriate to prevent the Massive Activity anti-pattern (or more properly, the “Massive View Controller” problem as better maps to the well-known acronym, and is dually relevant to iOS.)

As I see the former problem on a daily basis while I am trolling — nay, providing meaningfully to the community of — StackOverflow, I figured I would put this on the Internet. That it might thusly be searched upon one day, and in the eye of that very searcher then, be looked upon fondly.

Suppose you have an ImageView , and you want to populate it form content on the public Inter-tubes. The motivations for this could be several, but in general this is your only practical solution if you’re dynamically generating content and the range of possible content for the image is unbounded.

In my case, I was developing a toy application for the Medium Java SDK I have been working on. The capability I needed was to asynchronously load image content for the profile picture of a Medium user. Or in other words:

loadImagesAsync(ImmutableMap.of(
R.id.user_image, user.getImageUrl()
));

Like I mentioned previously — if you are developing this infrastructure, you might as well make it work on many things, not just a thing. Imagine there is no such thing as “a resource,” there are only “collections of resources.”

Make an AsyncTask

public class DownloadImageTask
extends AsyncTask<String, Void, Bitmap> {

@Override
protected Bitmap doInBackground(String... urls) {
// Logic to download an image from an URL
}
@Override
protected void onPostExecute(Bitmap result) {
// Download is done
}
}

Okay, cool. Now we need a way to actually download an image:

@Override
protected Bitmap doInBackground(String... urls) {
final String url = urls[0];
Bitmap bitmap = null;

try {
final InputStream inputStream = new URL(url).openStream();
bitmap = BitmapFactory.decodeStream(inputStream);
} catch (final MalformedURLException malformedUrlException) {
// Handle error
} catch (final IOException ioException) {
// Handle error
}
return bitmap;
}

Alright, not too bad, all we have to do is collect the thing in onPostExecute and be on our merry way. What I propose to do is actually add a new callback facility to our DownloadImageTask , which will allow a bit more flexibility and continue to keep our logic in this file and out of our Activity code.

Defining a Callback Listener

public DownloadImageTask(final Listener listener) {             
this.listener = listener;
}

Now, when the DownloadImageTask completes, we can inform the listener:

@Override
protected void onPostExecute(Bitmap downloadedBitmap) {
if (null != downloadedBitmap) {
listener.onImageDownloaded(downloadedBitmap);
} else {
listener.onImageDownloadFailed();
}
}

But what is a Listener? It is an interface defined inside of the DownloadImageTask :

public static interface Listener {
void onImageDownloaded(final Bitmap bitmap);
void onImageDownloadError();
}

So, with this in place, we easily start new tasks from our presentation logic and download a bunch of images, getting notified when they are ready.

Using it from a Presenter

All that is left is to add the logic to bind views and downloaded images:

private void loadImagesAsync(final Map<Integer, String> bindings) {
for (final Map.Entry<Integer, String> binding :
bindings.entrySet()) {
new DownloadImageTask(new DownloadImageTask.Listener() {
@Override
public void onImageDownloaded(final Bitmap bitmap) {
((ImageView) findViewById(binding.getKey()))
.setImageBitmap(bitmap);
}
@Override
public void onImageDownloadFailed() {
Log.e(TAG, "Failed to download image from "
+ binding.getKey());
}
}).execute(binding.getValue());
}
}

And there you have it. A fairly lightweight way to Async-load images into all of the ImageView s in your view.

Adding a Simple Caching Layer

Map<String, Bitmap> mBitmapCache = new HashMap<>();

Then in your doInBackground() , first check if there is a bitmap already in the cache, something like:

((MyApplication) context).getBitmapCache().get(resourceId);

Even better would be write the images out to disk at the end of doInBackground() , and try to retrieve them from disk at the beginning of doInBackground() .

Really though, you should probably use Glide at this point for any project of complexity.

Exploiting Parallelism

Due to the way a default AsyncTask is setup, the code above would execute each task one after the other, from a queue. It is still asynchronous from your perspective, it’s just that the actual time it takes to download many images could be improved.

To run them in parallel, execute each AsyncTask on a thread pool:

new DownloadImageTask(new DownloadImageTask.Listener() {
// Blah blah stuff from above ...
}).executeOnExecutor(THREAD_POOL_EXECUTOR, url);

(See also Java 7 docs on ThreadPoolExecutor.)

Closing Thoughts

If you have a whole bunch of images, or are incrementally loading them via a scrolling view, then you’ll probably want to bring in the Big Guns. Intelligent caching will increasingly be important as the number of images rises.

Anyway, hopefully this is useful to someone. Enjoy!

Staff Engineer at Reddit