Fresco stores images in three different types of caches, organized hierarchically, with the cost of retrieving an image increasing the deeper you go.

1. Bitmap cache

The bitmap cache stores decoded images as Android Bitmap objects. These are ready for display or postprocessing.

On Android 4.x and lower, the bitmap cache’s data lives in the ashmem heap, not in the Java heap. This means that images don’t force extra runs of the garbage collector, slowing down your app.

Android 5.0 and newer has much improved memory management than earlier versions, so it is safer to leave the bitmap cache on the Java heap.

Your app should clear this cache when it is backgrounded.

2. Encoded memory cache

This cache stores images in their original compressed form. Images retrieved from this cache must be decoded before display.

If other transformations, such as resizing, rotation or transcoding were requested, that happens before decode.

3. Disk cache

(Yes, we know phones don’t have disks, but it’s too tedious to keep saying local storage cache…)

Like the encoded memory cache, this cache stores compressed image, which must be decoded and sometimes transformed before display.

Unlike the others, this cache is not cleared when your app exits, or even if the device is turned off.

When disk cache is about to be to the size limits defined by DiskCacheConfig Fresco uses LRU logic of eviction in disk cache (see DefaultEntryEvictionComparatorSupplier.java).

The user can, of course, always clear it from Android’s Settings menu.

Checking to see if an item is in cache

You can use the methods in ImagePipeline to see if an item is in cache. The check for the memory cache is synchronous:

1
2
3
ImagePipeline imagePipeline = Fresco.getImagePipeline();
Uri uri;
boolean inMemoryCache = imagePipeline.isInBitmapMemoryCache(uri);

The check for the disk cache is asynchronous, since the disk check must be done on another thread. You can call it like this:

1
2
3
4
5
6
7
8
9
10
11
12
DataSource<Boolean> inDiskCacheSource = imagePipeline.isInDiskCache(uri);
DataSubscriber<Boolean> subscriber = new BaseDataSubscriber<Boolean>() {
    @Override
    protected void onNewResultImpl(DataSource<Boolean> dataSource) {
      if (!dataSource.isFinished()) {
        return;
      }
      boolean isInCache = dataSource.getResult();
      // your code here
    }
  };
inDiskCacheSource.subscribe(subscriber, executor);

This assumes you are using the default cache key factory. If you have configured a custom one, you may need to use the methods that take an ImageRequest argument instead.

Evicting from cache

ImagePipeline also has methods to evict individual entries from cache:

1
2
3
4
5
6
7
ImagePipeline imagePipeline = Fresco.getImagePipeline();
Uri uri;
imagePipeline.evictFromMemoryCache(uri);
imagePipeline.evictFromDiskCache(uri);

// combines above two lines
imagePipeline.evictFromCache(uri);

As above, evictFromDiskCache(Uri) assumes you are using the default cache key factory. Users with a custom factory should use evictFromDiskCache(ImageRequest) instead.

Clearing the cache

1
2
3
4
5
6
ImagePipeline imagePipeline = Fresco.getImagePipeline();
imagePipeline.clearMemoryCaches();
imagePipeline.clearDiskCaches();

// combines above two lines
imagePipeline.clearCaches();

Using one disk cache or two?

Most apps need only a single disk cache. But in some circumstances you may want to keep smaller images in a separate cache, to prevent them from getting evicted too soon by larger images.

To do this, just call both setMainDiskCacheConfig and setSmallImageDiskCacheConfig methods when configuring the image pipeline.

What defines small? Your app does. When making an image request, you set its CacheChoice:

1
2
ImageRequest request = ImageRequestBuilder.newBuilderWithSource(uri)
    .setCacheChoice(ImageRequest.CacheChoice.SMALL)

If you need only one cache, you can simply avoid calling setSmallImageDiskCacheConfig. The pipeline will default to using the same cache for both and CacheChoice will be ignored.

Trimming the caches

When configuring the image pipeline, you can set the maximum size of each of the caches. But there are times when you might want to go lower than that. For instance, your application might have caches for other kinds of data that might need more space and crowd out Fresco’s. Or you might be checking to see if the device as a whole is running out of storage space.

Fresco’s caches implement the DiskTrimmable or MemoryTrimmable interfaces. These are hooks into which your app can tell them to do emergency evictions.

Your application can then configure the pipeline with objects implementing the DiskTrimmableRegistry and MemoryTrimmableRegistry interfaces.

These objects must keep a list of trimmables. They must use app-specific logic to determine when memory or disk space must be preserved. They then notify the trimmable objects to carry out their trims.