Picasso: A powerful image downloading and caching library for Android

This is the third instalment of my Code More Efficiently series and it is meant for Android developers who deal with loading images in their code. If you have to manage, load, display, or otherwise work with images in your app, you’d agree that the majority of the issues that you face in this area are regarding performance, memory, or both. The solution is often a 3 tier cache: memory > disk > network, but writing such a system is tedious. That’s where Picasso can only help, but excel.

What is Picasso?

Picasso is a part of Square Open Source and a library for handling images on Android. Picasso makes it easy to download, transform, and cache images in your app. You can check out the Picasso Github portal for more information or download the source code from Github.

Why Picasso?

You can use Picasso almost anywhere in your app where an image needs to be loaded and displayed. You might think “but I already know how to load images. I have a method that takes in a File and returns a Bitmap”.  Maybe even you do it all asynchronously in the background (If you’re loading your images on the UI thread, we need to talk!). Either way, that’s only a small part of the big picture. Your solution so far is effective, but not quite efficient. Imagine the scenario where the user goes back & forth between two activities (say, user profiles): You’re loading the same two pictures from disk (or network, ouch!) every time! That’s not optimal at all. Once you add caching to your solution to optimize that, you’ve spent a long time on basic functionality; on boilerplate. Lucky for you, the cool kids at Square have already spent time implementing all of that. Not only that, but they’ve open sourced their solution for any and all to use. Instead of re-inventing the wheel, you can get to work on the engine!

Using Picasso

There are two general ways to use Picasso: In one method you use the default global Picasso instance that is initialized automatically and the other method allows you to create an instance that you can customize for finer control. If you choose to go with the latter, make sure you initialize Picasso before making requests. Regardless of which method you use, the end result is similar: a Picasso instance is created that you can use to manage your image processing. My preference is the second method because it allows me to enable or disable various debugging and logging features based on the configuration that I’m building.

Default Global Instance

To use the default global instance, all you have to do is call the static method Picasso.with (Context context) every time you need an instance. The default instance is initialized with the following defaults

  • LRU memory cache of 15% available application RAM
  • Disk cache of 2% storage space of no less than 5MB and up to 50MB
  • 3 worker threads to download your images

Custom Singleton Instance

If the defaults don’t meet your needs, don’t worry! You can control these values and some behaviours via the Picasso.Builder class. Once you create your ideal Picasso instance, use Picasso.setSingletonInstance (Picasso picasso) to set the global instance. Once this is done, all calls to Picasso.with (Context context) will return the instance that you just created. If you choose to go with this method, make sure you initialize Picasso before making requests.

What Can I Load?

Picasso is capable of loading the following:

  • Drawable resources via the public RequestCreator load(int resourceId) method.
  • Local image files via
    • The public RequestCreator load(File file) method.
    • The public RequestCreator load(android.net.Uri uri) method where the provided Uri has a “file” scheme.
    • The public RequestCreator load(String path) where the provided path starts with “file:”
  • Image Assets via
    • The public RequestCreator load(android.net.Uri uri) method where the provided Uri has a file scheme and the path starts with android_asset (e.g. file:///android_asset/images/vader.png).
    • The public RequestCreator load(String path) method where the provided path starts with "file:///android_asset/" (e.g. "file:///android_asset/images/vader.png").
  • Remote images via
    • The public RequestCreator load(android.net.Uri uri) method, with the uri pointing to a remote image.
    • The public RequestCreator load(String path) method, with the path pointing to a remote image URL.
  • Contact pictures via
    • The public RequestCreator load(android.net.Uri uri) method, where the uri is either.
      • A contact content uri (e.g. content://com.android.contacts/contacts/40)
      • A contact lookup content uri (e.g. content://com.android.contacts/contacts/lookup/3570i79ecefb78f182378)
      • A contact photo content uri (e.g. content://com.android.contacts/contacts/40/photo)
    • The public RequestCreator load(String path) method, with the path being one of the above content uris.

Features

Aside from loading and caching images, Picasso has some other useful features such as adapter re-use detection, built-in and custom image transformations, place holders, error indicators, and debugging indicators that let you know whether your image was loaded from memory cache, disk cache, or network.

ImageView Fire and Forget

The most basic form of using Picasso is what I like to call Fire and Forget. You get Picasso working on your image and a target ImageView (or other targets) and move on to other things knowing that Picasso will load the image in the background and assigns it to the target when done. Here are a few examples:

// load a remote image into myImageView
Picasso.with (this)
  .load ("http://media.giphy.com/media/KNMLU8DKouGQM/giphy.gif")
  .into (myImageView);
// load a contact picture into myImageView
// inform a callback of the result
Picasso.with(context)
  .load (ContentUris.
     withAppendedId (ContactsContract.Contacts.CONTENT_URI, rawId)
  .into (myImageView, new Callback () {
    public void onError () {
      // oops!
    }

    public void onSuccess () {
      // Yay!
    }
  });
// load a drawable resource into a custom target
Picasso.with (context)
  .load (R.drawable.my_image)
  .into (new Target () {
    public void onBitmapFailed (Drawable error) {
      // load failed. Use the error drawable if provided
    }
    
    public void onBitmapLoaded (Bitmap bmp, LoadedFrom from) {
      // image successfully loaded
      // from = net/disk/memory
    }

    public void onPrepareLoad (Drawable placeholder) {
      // geting ready to load
      // use the placeholder if not null
    }    
  });

Adapters

Back in the Butter Knife segment I mentioned view holders and why you should use them. Here’s another place where actually reusing your views (like you’re supposed to) will benefit you: Picasso can detect view re-use and will cancel the previous download. Other than that, you just use Picasso as you normally would to put an image into a target.

@Override public View getView (final int position,
           final View convertView,
           final ViewGroup parent) {
  final View v = null == convertView 
    ? newView (parent) 
    : convertView;
  final Contact contact = getItem (position);
  final ListItemRow tag = (ListItemRow) v.getTag ();
  tag.title.setText (contact.getName());
   
  Picasso.with(myContext)
    .load (contact.getUri ())
    .into (tag.icon);
 
  return v;
}

Transformations

Picasso provides basic transformations as well as an interface for custom transformations that you can apply to the images before they are displayed. If you already have the original untransformed image in the cache it won’t be downloaded again (yay for efficiency). The resize and rotate transformations are built in, but you have to implement any other transformation that you need (it’s real easy).

Resize

Picasso’s resize functionality gives you a variety of ways to define the size. There are two resize methods: resize (int w, int h) and resizeDimen (int w, int h). The method names should be self explanatory, but the first method allows you to specify the pixel size of the image while the second method allows you specify a dimen resource. The dimensions only work right if the original image aspect ratio matches with your specified dimensions, but fear not, Picasso lets you specify how to deal with such mismatches. Only when resize is being used you can specify either centerCrop or centerInside to define how the image is to be resized. You can also use fit to resize the image to the target ImageView’s bounds.

// center crop the image to fill 800x600 pixels
Picasso.with(myContext)
      .load (imageUri1)
      .resize (800, 600)
      .centerCrop()
      .into (imageView1);
// center the image inside a rectangle with
// width = R.dimen.defaultWidth
// height = R.dimen.defaultHeight
Picasso.with(myContext)
      .load (imageUri2)
      .resizeDimen (R.dimen.defaultWidth,
                    R.dimen.defaultHeight)
      .centerInside()
      .into (imageView2);

Rotate

Another built-in transformation offered by Picasso is rotation. Similar to resize, you have options: You can either rotate the image around its center (default) using the rotate (float degrees) method or you can specify the pivot coordinates via the rotate (float degrees, float pivotX, float pivotY) method.

/* 
rotate the image by 45 degrees around its center
*/
Picasso.with(myContext)
      .load (imageUri1)
      .rotate (45f)
      .into (imageView1);
/* 
rotate the image by 60 degrees around 
its top left corner
*/
Picasso.with(myContext)
      .load (imageUri2)
      .rotate (60.f, 0f, 0f)
      .into (imageView2);

Custom Transformations

In the demo code, I’m using the Transformation interface to create a Gaussian blur effect. Other examples are grey scale,  overlays, transparency, and whatever else you can think of doing to that bitmap before putting it in the ImageView! For the sake of brevity, let’s try a semi-transparent (50%, 0x800000ff) blue overlay transformation:

public class BlueOverlay50Pct
  implements Transformation {
  
  @Override
  public Bitmap transform (final Bitmap source) {
    final Bitmap overlaid =      
      source.copy(source.getConfig (),
      true);

      final Canvas canvas = new Canvas (overlaid);
      canvas.drawARGB (128, 0, 0, 255);

      source.recycle ();
      return overlaid;
    }

  @Override
  public String key () {
    return "ca.mahram.demo.picasso.xform.BlueOverlay50PctTransformation";
  }
}

Now, to use this transformation

Picasso.with(myContext)
      .load (imageUri)
      .transform (new BlueOverlay50Pct())
      .into (imageView);

Place Holders & Error Indicators

For the times where you want to display a default or a working image while the actual image is being loaded, Picasso offers place holders. Place holders are assigned to the target before the load starts and are replaced once the asset is loaded. In case of errors, the place holder stays (unless an error indicator image is specified). Similarly, you may want to display a standard error indicator if the image cannot be loaded (e.g. the source file didn’t exist, was not in the right format, downloading remote url failed, an invalid image file was downloaded, etc.).

// placeholder only
Picasso.with(myContext)
    .load (imageUri)
    .placeHolder (R.drawable.my_placeholder)
    .into (imageView);
// placeholder and error
Picasso.with(myContext)
    .load (imageUri)
    .placeHolder (R.drawable.my_placeholder)
    .error (R.drawable.image_failed)
    .into (imageView);
// error indicator only
Picasso.with(myContext)
    .load (imageUri)
    .error (R.drawable.image_failed)
    .into (imageView);

Debugging

Sometimes the down side of using code that you didn’t write is that it isn’t as easy to debug or optimize. In the case of Picasso, for example, if you’re generating your URLs in a way that they slightly differ for the same image, you’re defeating the point of that cache. Some times you know you’re doing counter-productive things and other times you don’t. How can you tell if Picasso actually used a cached image or downloaded the image again? Another issue is that when an Image fails to load how do you know what happened? Of course you can use the log to tell you that, but you don’t want the log printing all that extra information in your release builds. Picasso has your back here as long as you customize the global instance with the proper configuration. For obvious reasons, the default global instance has these settings disabled.

Source Indicators

Source indicators are small coloured triangles at the top left corner of your image telling you where your image was loaded from. A red indicator tells you that the image was loaded from network, a blue indicator marks an image that was loaded from disk (file or disk cache), and a green indicator means the image was loaded from the memory cache. 

Debug Indicators
From http://square.github.io/picasso/

Imagine the case where you’re working on app with users where each user has a profile picture. Going back and forth between the profiles of Jack and Jill should only download their profile pictures the first time. Consecutive visits should load the images from the memory cache. If that is the case, you’re golden! What would a blue indicator tell you here? To me, it says that you’re either using images that are too big for both to fit in your memory cache or that your memory cache is too small (or maybe a little from column A, a little from column B). Based on the colour of your indicators in debug mode, you can fine tune your app’s behaviour.

A common pitfall where your remote Image server allows you to specify the sizes in your request URL is that because you’re specifying a slightly different URL for the same image each time, Picasso ends up downloading the same picture multiple times (each time it has different dimensions). The solution to consider is to always download the largest image and use the resize feature based on where the image is being used. This allows you to minimize how often you fetch the same image from the network.

Logging

As you would expect, logging allows you to see what happens to each request. Let’s look at a sample Picasso log output here and talk about what is logged:

03-21 16:39:50.450  12596-12596/? D/Picasso﹕ Main        created      [R6] Request{https://farm4.staticflickr.com/3751/9291680816_1c636a3c7e_k_d.jpg}
03-21 16:39:50.461  12596-12596/? D/Picasso﹕ Main        created      [R7] Request{http://mahram.ca/nonexistent/images/1426981190471 resize(200,120) centerCrop}
03-21 16:39:50.471  12596-12596/? D/Picasso﹕ Main        created      [R8] Request{https://lh6.googleusercontent.com/-pCSNJqj16y4/U2yDCFLzR1I/AAAAAAAAknw/jo07-88TX5g/w579-h868-no/_DSC1503.jpg resize(800,600) centerInside}
03-21 16:39:50.481  12596-12757/? D/Picasso﹕ Hunter      executing    [R6]+27ms
03-21 16:39:50.491  12596-12624/? D/Picasso﹕ Dispatcher  enqueued     [R6]+33ms
03-21 16:39:50.491  12596-12759/? D/Picasso﹕ Hunter      executing    [R7]+25ms
03-21 16:39:50.531  12596-12624/? D/Picasso﹕ Dispatcher  enqueued     [R7]+24ms
03-21 16:39:50.531  12596-12624/? D/Picasso﹕ Dispatcher  enqueued     [R8]+69ms
03-21 16:39:50.541  12596-12758/? D/Picasso﹕ Hunter      executing    [R8]+71ms
03-21 16:39:50.901  12596-12757/? D/Picasso﹕ Hunter      decoded      [R6]+445ms
03-21 16:39:50.901  12596-12624/? D/Picasso﹕ Dispatcher  batched      [R6]+446ms for completion
03-21 16:39:51.101  12596-12596/? D/Picasso﹕ Main        completed    [R6]+646ms from DISK
03-21 16:39:51.101  12596-12624/? D/Picasso﹕ Dispatcher  delivered    [R6]+647ms
03-21 16:39:51.111  12596-12758/? D/Picasso﹕ Hunter      decoded      [R8]+649ms
03-21 16:39:51.121  12596-12758/? D/Picasso﹕ Hunter      transformed  [R8]+657ms
03-21 16:39:51.121  12596-12624/? D/Picasso﹕ Dispatcher  batched      [R8]+657ms for completion
03-21 16:39:51.331  12596-12624/? D/Picasso﹕ Dispatcher  delivered    [R8]+860ms
03-21 16:39:51.341  12596-12596/? D/Picasso﹕ Main        completed    [R8]+870ms from NETWORK
03-21 16:39:52.793  12596-12624/? D/Picasso﹕ Dispatcher  batched      [R7]+2329ms for error
03-21 16:39:53.013  12596-12596/? D/Picasso﹕ Main        errored      [R7]+2544ms
03-21 16:39:53.013  12596-12624/? D/Picasso﹕ Dispatcher  delivered    [R7]+2544ms

You may notice that each line starts with either Main, Hunter, or Dispatcher. These are the names of the threads that are performing each task. You’re making the request from the main thread so that’s where the requests are created. The Hunter is responsible for actually loading the image. Picasso has a variety of hunter based on where the image is being loaded from (e.g. network, contacts, file, etc). Once the image is loaded (or fails) the Dispatcher is responsible for, well, dispatching the result! If the request assigned an ImageView to the request, then the loaded bitmap (or the error indicator) is placed in the ImageView. If a Target implementation was used, the appropriate callback is called. The rest is pretty self explanatory: each request gets an id (printed in square brackets), and each log output gets a time-stamp relative to the creation of the request (e.g. +2544ms).

Building

If you’re using Android Studio/Gradle, you can easily add Picasso to your dependencies:
compile 'com.squareup.picasso:picasso:2.5.0'
Otherwise, to download the latest Picasso JAR, visit the Picasso Github portal and add the JAR to your dependencies as you normally would.

Slides & Code

octocat You can find my Picasso Demo code on Github
girl-before-a-mirror I have also prepared a few slides for this segment as well.