From Punched Cards to Prompts
AndroidIntroduction When computer programming was young, code was punched into cards. That is, holes were punched into a piece of cardboard in a format...
The scrolling list of visuals is a classic mobile interface pattern. Unfortunately, it’s always been a hassle to implement well on Android. If they are stored locally, a native Android implementation will result in stuttering. And if they are stored on the web, you have to worry about canceling pending requests, along with caching and a slew of other concerns.
As a result, many Android developers have written their own dedicated image downloading component once or twice. In fact, our Android book has an exercise where you write one in an app called PhotoGallery, which we’ll talk more about below.
And when you start to need caching, transformations, and better performance, it’s natural to ask if someone else has solved this problem before you. Just a few months back, I found myself in that exact situation with one of our client apps. I researched some solutions, but didn’t find anything compelling enough to commit to.
But right around Google I/O, a couple of interesting new image libraries were introduced: Volley and Picasso. They don’t solve exactly the same problem, but each offers solutions for this image loading issue. I decided I’d port them both into the PhotoGallery example code from our book to see how they measured up against one another.
PhotoGallery is a simple Flickr client that displays the most recent photos on Flickr:
Scroll it down, and you’ll see more pictures. Let’s focus on the image downloading code, though.
PhotoGalleryFragment has a component called ThumbnailDownloader. It is a single thread that is responsible for downloading images, and provides a callback that gets fired when the image is downloaded.
ThumbnailDownloader is initialized inside onCreate() by setting a listener, starting the thread and then calling getLooper() to ensure that its message loop is ready to receive messages:
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
mThumbnailThread = new ThumbnailDownloader<ImageView>(new Handler());
mThumbnailThread.setListener(new ThumbnailDownloader.Listener<ImageView>() {
public void onThumbnailDownloaded(ImageView imageView, Bitmap thumbnail) {
if (isVisible()) {
imageView.setImageBitmap(thumbnail);
}
}
});
mThumbnailThread.start();
mThumbnailThread.getLooper();
}
The listener here is responsible for actually setting the image on the ImageViews that PhotoGallery is populating.
In onDestroyView(), old requests are cleared out:
@Override
public void onDestroyView() {
super.onDestroyView();
mThumbnailThread.clearQueue();
}
And in onDestroy(), the thread is cleared out entirely:
@Override
public void onDestroy() {
super.onDestroy();
mThumbnailThread.quit();
}
Inside the adapter for PhotoGallery’s GridView, a default image is set, and a request is queued on the thumbnail thread:
private class GalleryItemAdapter extends ArrayAdapter<GalleryItem> {
public GalleryItemAdapter(ArrayList<GalleryItem> items) {
super(getActivity(), 0, items);
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = getActivity().getLayoutInflater()
.inflate(R.layout.gallery_item, parent, false);
}
GalleryItem item = getItem(position);
ImageView imageView = (ImageView)convertView
.findViewById(R.id.gallery_item_imageView);
imageView.setImageResource(R.drawable.brian_up_close);
mThumbnailThread.queueThumbnail(imageView, item.getUrl());
return convertView;
}
}
And that’s it. ThumbnailDownloader itself is a very simple image downloader. It downloads each image one by one from Flickr on a single thread. If a request is invalid or out of date, it skips over to the next request. It has some nice properties, too: it’s simple, small and easy to understand.
There are a few drawbacks to this implementation, though. One is that I have a lot of integration with the lifecycle of my fragment: I have to initialize my thread in three ordered steps, I have to manually clear out stale requests, and I have to shut down my thread when I’m done with it. I could solve some of these problems by centralizing my image downloader in a singleton, but then my implementation would need to be able to handle multiple client fragments simultaneously, making it more complicated.
Okay, enough boring setup. Let’s get to the juice.
Picasso comes from the good folks at Square, and it’s the last entry in their Seven Days of Open Source leading up to Google I/O. It’s focused, small and has a wonderfully tiny interface.
Pulling it into your project is as straightforward as these things get. If you’re using maven, add a few lines to your pom file. If you’re not, just download a jar file and include it.
Square claims easy integration into your code, too, stating that
Picasso allows for hassle-free image loading in your application—often in one line of code!
That one line of code looks like this:
Picasso.with(context).load("http://i.imgur.com/DvpvklR.png").into(imageView);
Square tells the truth. This code is almost exactly what my code in PhotoGallery ended up looking like. All the code onCreate(), onDestroyView() and onDestroy() ended up going away, and my adapter implementation turned into this:
private class GalleryItemAdapter extends ArrayAdapter<GalleryItem> {
public GalleryItemAdapter(ArrayList<GalleryItem> items) {
super(getActivity(), 0, items);
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = getActivity().getLayoutInflater()
.inflate(R.layout.gallery_item, parent, false);
}
GalleryItem item = getItem(position);
ImageView imageView = (ImageView)convertView
.findViewById(R.id.gallery_item_imageView);
imageView.setImageResource(R.drawable.brian_up_close);
Picasso.with(getActivity())
.load(item.getUrl())
.noFade()
.into(imageView);
return convertView;
}
}
That’s the entire implementation. I broke my implementation out into more than one line for clarity, but other than that it is the same. Picasso also includes the ability to specify a placeholder image, but I didn’t use it. As of this writing, Picasso’s implementation seems to override your ImageView’s scaling behavior, so I set my own placeholder.
I also had to disable a default behavior. Out of the box, Picasso displays a slick fade-in animation when your image loads. This is actually nice to see in Android—we rarely get any free visual spiff. In our case, though, the fade-from-white animation looked a little odd on PhotoGallery’s black background. I turned it off with a call to noFade().
I don’t demonstrate it here, but the other interesting thing you can do is transform the image in various ways, by scaling it, cropping it, and so on and so forth. Most of this is what I’d call nifty. Particularly nifty is that the transformations are performed prior to caching, which would be a big win in some scenarios.
So what do I get with those five lines?
A couple of things jumped out at me as being handy for embedding Picasso into existing apps:
And how can Picasso be extended? My example doesn’t explore it much, but you’ve got a few options:
Finally, one thing I found to be a hassle with Picasso: scaling and fitting my images correctly. Picasso doesn’t respect the scaleType
attribute on your ImageViews, and the following code fails at runtime: (note: this issue has been fixed now. hooray!)
Picasso.with(getActivity())
.load(item.getUrl())
.placeholder(R.drawable.brian_up_close)
.centerCrop()
.noFade()
.into(imageView);
This is Picasso’s raison d’etre. So why is it difficult?
Picasso’s strength is also its weakness: it caches scaled and cropped image requests. This means that it has to know how big the image is at the time you request it. Unfortunately, you will not know how big the image needs to be at the time you usually build the request: right after you create your view.
During I/O itself, we heard about a completely different solution: a library called Volley from the Android dev team.
I’ll admit to being extremely excited about Volley after seeing Ficus Kirkpatrick’s presentation. (Just ask Chris Stewart if you don’t believe me.) See, Volley isn’t an image loading library—it’s an asynchronous networking library. And what’s the hard part of image loading? Generally it’s the networking and caching parts!
Here’s an example of what a Volley request and response look like. It’s a Vollified version of the code that fetches the initial list of picture XML data from Flickr:
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mQueue = Volley.newRequestQueue(getActivity());
GalleryItemRequest itemsRequest = GalleryItemRequest.newRecentItemsRequest(null,
new Listener<ArrayList<GalleryItem>>() {
@Override
public void onResponse(ArrayList<GalleryItem> items) {
mItems = items;
setupAdapter();
}
});
itemsRequest.setTag(this);
mQueue.add(itemsRequest);
}
@Override
public void onDestroy() {
super.onDestroy();
mQueue.cancelAll(this);
}
GalleryItemRequest is a custom Volley request object I wrote to parse an XML network request into a set of model objects. The old implementation fetched the items from doInBackground() in an AsyncTask and setup the adapter in onPostExecute(). This implementation has a smaller footprint in my controller code, and also has the advantage that the request gets cleaned up in onDestroy() if the user navigates away for some reason.
This is exciting because traditionally, networking in Android has been hairy. Asynchronous networking would be ideal, but in Android it’s problematic, because your controller components are popping in and out of existence all the time. In practice, something like our AsyncTask implementation is common. We explain how it all works in our book, but I have often wished for something simpler. Volley looked like it’d be just the ticket for me.
So how do we integrate image loading? The very first step is actually to integrate this library into our app. Unfortunately, this isn’t as easy as with Picasso. Volley lives in AOSP, but it is not (for the time being) exposed as a library through, for example, the support library. That means there’s no github page, no jar file or maven distribution. I ended up downloading a copy of Volley’s source to my machine and compiling my own jar.
Once you’ve done that, the first step is to create two things: a RequestQueue and an ImageLoader.
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
mQueue = Volley.newRequestQueue(getActivity());
mImageLoader = new ImageLoader(mQueue, new ImageCache() {
@Override
public void putBitmap(String key, Bitmap value) { }
@Override
public Bitmap getBitmap(String key) {
return null;
}
});
...
}
Note that in a real-world app, your fragments wouldn’t be cluttered with this sort of initialization code. Instead, you’d probably have a shared instance of these two components across the entire app.
Now, the ImageLoader requires an implementation of ImageCache. As of this post, Volley doesn’t include any implementations out of the box, so I have provided an empty one that doesn’t cache anything. This doesn’t mean there’s no caching—Volley caches HTTP response data for you. Not having an image cache here does mean that my images will be decoded every time they’re displayed, though.
Next is to integrate into the adapter. Volley can work with bare ImageViews, but it is a little verbose. To make it easier, Volley provides a class called NetworkImageView. I’ve created an alternate layout file called gallery_item_network.xml that includes a NetworkImageView. I then inflate and configure it:
private class GalleryItemAdapter extends ArrayAdapter<GalleryItem> {
public GalleryItemAdapter(ArrayList<GalleryItem> items) {
super(getActivity(), 0, items);
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = getActivity().getLayoutInflater()
.inflate(R.layout.gallery_item_network, parent, false);
}
GalleryItem item = getItem(position);
NetworkImageView imageView = (NetworkImageView)convertView
.findViewById(R.id.gallery_item_imageView);
imageView.setDefaultImageResId(R.drawable.brian_up_close);
imageView.setImageUrl(item.getUrl(), mImageLoader);
return convertView;
}
}
And that’s it.
So what’s nice about Volley?
This last point is worth an aside: Android does not handle high-res images well at all. I have a small obsession with the pattern of catching OutOfMemoryError in Android apps. It seems like a ridiculous tactic, but it’s the only way to reliably handle some image scenarios.
Sure enough, when I looked through Volley I found that it catches OutOfMemoryError. I knew I’d want to test it, so I modified PhotoGallery to download the original resolution image instead of the thumbnail version. The original implementation blows up. The Picasso version doesn’t blow up (it catches OutOfMemoryError, too), but it fails to load any images that are too large. Not only does Volley not blow up, but it displays all these large images with aplomb.
So what about embedding Volley into an existing app?
Extending with Volley is a different story than it is for Picasso. See, Picasso is totally focused on image loading. As a result, if you have quirks in your image loading process, then there’s a hook there to hang your quirk on.
Volley, on the other hand, is totally focused on handling individual, small HTTP requests. So if your HTTP request handling has some quirks, Volley probably has a hook for you. If, on the other hand, you have a quirk in your image handling, the only real hook you have is ImageCache. It’s not nothing, but it’s not a lot, either.
The other drawback to that focus is that as soon as one “request” on the front end is really multiple HTTP requests, you can’t extend Volley. Instead, you have to build something on top of it.
Good question. If you’ve gotten this far, you can tell that Picasso and Volley are very different animals. Picasso does just one thing, while Volley tries to solve a more general problem.
So my feeling is that, if you have a large, stable, pre-existing project, you are probably better off using Picasso. Integration is painless, performance seems good, and if the fade-in works for you, you get some free visual fun, too.
If, on the other hand, your app is new, or if it’s small enough that you can think about swapping out the back end completely, and it deals mostly with small HTTP payloads, Volley is worth considering. Once you define your requests, using them from within a fragment or activity is painless. And unlike parallel AsyncTasks, you don’t have to worry about spinning up too many threads, or potential missteps with shared state.
Or what about using both at the same time? If Volley’s image management causes you severe pain, then you’re probably fine using both. I wouldn’t start off using both, however. Picasso solves a couple of pain points that Volley doesn’t address, and it’s trivial to integrate, which means that you should be perfectly fine putting off switching to Picasso until it’s necessary.
Introduction When computer programming was young, code was punched into cards. That is, holes were punched into a piece of cardboard in a format...
Jetpack Compose is a declarative framework for building native Android UI recommended by Google. To simplify and accelerate UI development, the framework turns the...
Big Nerd Ranch is chock-full of incredibly talented people. Today, we’re starting a series, Tell Our BNR Story, where folks within our industry share...