ZOFTINO.COM android and web dev tutorials

Android Picasso Image Downloading and Caching Library Tutorial

If your app features use images from network, it is very important to download the images efficiently to provide good user experience for your app users. For example, when an image is being downloaded, a second request to download the same image should cancel the first download request otherwise it will impact the performance of your app. To reduce network bandwidth and improve performance of your app, you need to cache images either in memory or disk. You may need to resize, crop and transform images to suit your app or user specific needs.

Picasso image downloading and caching library, provided by square, automatically takes care of all image downloading pitfalls mentioned above. In this tutorial, you can learn about Picasso with examples.

Add Picasso Library

To add Picasso library to your project, add below line to your app’s build.gradle file.

implementation 'com.squareup.picasso:picasso:2.71828'

Loading Image into ImageView

To load an image into ImageView, first you need create Picasso object by calling get method on Picasso class. The get method returns default global Picasso instance. The default Picasso instance has three worker- threads which are used to access disk and network. It uses 15% of available app memory for LRU memory cache and 2% of storage space for disk cache.

Once you have Picasso object, then you need to call load() method on Picasso object passing image URL to it and then call into() method on RequestCreator object passing image view object to it. Method into() loads images asynchronously, it loads images on worker thread. If you want to load an image synchronously, you can use get() method on RequestCreator object.

Picasso.get().load(productImageUrl).into(imageView);

Custom Picasso Instance

You can build your own Picasso object by using Picasso.Builder object. Picasso.Builder object allows you to set your own downloader, default Picasso object uses OkHttpDownloader, set own cache, custom request handler with additional features not supported by default request handler, custom bitmap config object, which is used for decoding image formats, with image formats not supported by default bitmap config, set custom executor service for running requests on background thread and set request transformer which is called just before request is submitted to modify requests.

Below example shows how to create custom Picasso object. It sets memory cache by creating LruCache object with custom configuration and passing it to memoryCache method call on builder and it also sets request transformer object by calling requestTransformer method on builder and passing custom request transformer object to it.

 private Picasso getCustomPicasso(){
    Picasso.Builder builder = new Picasso.Builder(this);
    //set 12% of available app memory for image cache
    builder.memoryCache(new LruCache(getBytesForMemCache(12)));
    //set request transformer
    Picasso.RequestTransformer requestTransformer =  new Picasso.RequestTransformer() {
        @Override
        public Request transformRequest(Request request) {
            Log.d("image request", request.toString());
            return request;
        }
    };
    builder.requestTransformer(requestTransformer);

    return builder.build();
} 

You can set your custom Picasso object as global object by calling setSingletonInstance() method on Picasso class by passing your custom Picasso object to it.

 Picasso.setSingletonInstance(getCustomPicasso());
Picasso.get().load(R.drawable.square_green).into(image); 

Loading Images from Different Sources

Picasso class offers overloaded load() methods which allow you to load images by File, path as string, URI and from drawable resources.

Picasso.get().load(R.drawable.square_green).into(image);

Cancel Image Loading Requests

You can cancel existing image load requests using various cancel methods that Picasso object offers. Below code shows how to cancel requests by passing image view for which the requests were made.

Picasso.get().cancelRequest(image);

Below code shows how to add tag to request and cancel request by tag.

 //tag load request
Picasso.get().load(R.drawable.square_green).tag("product image").into(image);

//cancel request by tag
Object tag = (Object)("product image");
Picasso.get().cancelTag(tag); 

Removing Image from Cache

You can remove a specific image from cache using invalidate method passing image path or URI.

 Picasso.get().invalidate(productImage); 

Picasso Listener

You can add listener to Picasso object using Picasso.Builder. The Picasso listener’s onImageLoadFailed method gets called when image load fails. This is mainly useful for reporting and analysis purpose.

 Picasso.Builder builder = new Picasso.Builder(this);
builder.listener(new Picasso.Listener() {
    @Override
    public void onImageLoadFailed(Picasso picasso, Uri uri, 
                                  Exception exception) {
        Log.d("image load error" uri.getPath());
    }
});
Picasso p = builder.build();
p.get().load(productImage).into(image);
 

Picasso Stats Snapshot

Picasso allows you to get image download stats information using StatsSnapshot object which can obtained by calling getSnapshot method on Picasso object. From StatsSnapshot object, you can get such stats as download count, total download size and cache hits and misses.

        StatsSnapshot ss = Picasso.get().getSnapshot();       
        Log.d("download image stats", ""+ss.cacheHits);
        Log.d("download image stats", ""+ss.cacheMisses);
        Log.d("download image stats", ""+ss.downloadCount);
        Log.d("download image stats", ""+ss.totalDownloadSize); 

Configuring Picasso Request

You can add configuration to Picasso Request object using RequestCreator. Below code shows how to resize and crop downloaded image.

        RequestCreator rc = Picasso.get().load(productImage);
        rc.resize(400,400).centerCrop().into(image); 

To just download an image into cache, you can use fetch method on RequestCreator object. This allows you to pre download certain images into cache.

 Picasso.get().load(productImage).fetch(); 

If image download fails, you can make the request serve default image by setting error image on RequestCreator object as shown below.

        RequestCreator rc = Picasso.get().load(productImage);
        rc.error(R.drawable.shape_drawable_two).into(image);

You can make an image-loading request to download the image without looking up cache and to not store the downloaded image into cache by specifying memory policy on RequestCreator object.

        RequestCreator rc = Picasso.get().load(productImage);
        rc.memoryPolicy(MemoryPolicy.NO_CACHE,MemoryPolicy.NO_STORE).into(image); 

Similarly, you can make a request to download the image from disk cache only by specifying network policy.

Picasso.get().load(productImage).networkPolicy(NetworkPolicy.OFFLINE) .into(image); 

You can make the downloaded image to rotate by calling rotate() on RequestCreator object. You can set an image to be used while the requested image being downloaded by calling placeholder() method on RequestCreator object passing drawable resource id to it. You can transform the downloaded image by adding transformation object to RequestCreator object.

        RequestCreator rc = Picasso.get().load(productImage);
        rc.rotate(20).placeholder(R.drawable.shape_drawable) .into(image); 

Picasso Example Output

android picasso image downloading example

Activity

import android.app.ActivityManager;
import android.net.Uri;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.widget.ImageView;
import android.widget.TextView;

import com.squareup.picasso.LruCache;
import com.squareup.picasso.MemoryPolicy;
import com.squareup.picasso.Picasso;
import com.squareup.picasso.Request;
import com.squareup.picasso.RequestCreator;
import com.squareup.picasso.StatsSnapshot;


public class ShowProductActivity extends AppCompatActivity {
    private static final String TAG = "ShowProductActivity";
    private String productImage =
 "https://www.android.com/static/2016/img/devices/phones/htc-10/htc-10-02_w_1x.jpg";
    private ImageView image;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_product);

        ((TextView)findViewById(R.id.product)).setText("HTC 10");
        ((TextView)findViewById(R.id.price)).setText("$450");

        image = findViewById(R.id.product_img);

        Picasso.setSingletonInstance(getCustomPicasso());
        loadImage();
    }

    private Picasso getCustomPicasso(){
        Picasso.Builder builder = new Picasso.Builder(this);
        //set 12% of available app memory for image cache
        builder.memoryCache(new LruCache(getBytesForMemCache(12)));
        //set request transformer
        Picasso.RequestTransformer requestTransformer =  new Picasso.RequestTransformer() {
            @Override
            public Request transformRequest(Request request) {
                Log.d("image request", request.toString());
                return request;
            }
        };
        builder.requestTransformer(requestTransformer);

        builder.listener(new Picasso.Listener() {
            @Override
            public void onImageLoadFailed(Picasso picasso, Uri uri,
                                          Exception exception) {
                Log.d("image load error", uri.getPath());
            }
        });

        return builder.build();
    }
    //returns the given percentage of available memory in bytes
    private int getBytesForMemCache(int percent){
        ActivityManager.MemoryInfo mi = new ActivityManager.MemoryInfo();
        ActivityManager activityManager = (ActivityManager)
                getSystemService(ACTIVITY_SERVICE);
        activityManager.getMemoryInfo(mi);

        double availableMemory= mi.availMem;

         return (int)(percent*availableMemory/100);
    }

    private void loadImage(){
        //print picasso snap stats
        StatsSnapshot ss = Picasso.get().getSnapshot();
        Log.d("download image stats", ""+ss.cacheHits);
        Log.d("download image stats", ""+ss.cacheMisses);
        Log.d("download image stats", ""+ss.downloadCount);
        Log.d("download image stats", ""+ss.totalDownloadSize);

        //clear cache and cancel pending requests
        Picasso.get().invalidate(productImage);
        Picasso.get().cancelRequest(image);

        //set image rotation and placeholder image
        RequestCreator rc = Picasso.get().load(productImage);
        rc = rc.rotate(20).placeholder(R.drawable.shape_drawable);

        //set error image, memory policy
        rc = rc.error(R.drawable.shape_drawable_two);
        rc.memoryPolicy(MemoryPolicy.NO_CACHE,MemoryPolicy.NO_STORE);

        //resize and crop
        rc = rc.resize(400,400).centerCrop();
        //load image to imageview
        rc.into(image);
    }
}

Layout

<android.support.constraint.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_marginEnd="8dp"
    android:layout_marginStart="8dp">
    <ImageView
        android:id="@+id/product_img"
        android:layout_width="wrap_content"
        android:layout_height="300dp"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
    <TextView
        android:id="@+id/product"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textAppearance="@style/TextAppearance.AppCompat.Headline"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/product_img" />
    <TextView
        android:id="@+id/price"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/product"/>
    <Button
        android:id="@+id/buy"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        style="@style/Widget.AppCompat.Button.Colored"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/price"
        android:text="Buy"/>
</android.support.constraint.ConstraintLayout>