Jetpack Compose’s Guide to Using Coil for Image Caching

Analysis

Requirements:

When the image is loaded for the first time, the image needs to be saved permanently in the device, but usually we don’t want to keep these images in the device, we just want to capture them and save them temporarily, so that they can be loaded quickly when needed without initiating http request.

Simulation scenario:

Suppose we need to display some image URLs in our application. What we need to do is to make an http request efficiently. Generally, this request will be larger than an ordinary http request. If you currently have an image list (LazyColumn) implemented in a lazy manner, new items will be displayed when the user scrolls and network requests will be issued to load new images. This will result in very low performance, so the solution is: image caching .

Cache classification:

  • disk cache
  • memory cache

Use Coil to load network images

First, enable network permissions for our application to prevent us from forgetting later! ! !

<uses-permission android:name="android.permission.INTERNET"/>

Just find a picture on Baidu and load online pictures through Coil‘s AsyncImage. Although the previous Image can also be used through rememberImagePainter Loads network images but has been abandoned, so it will not be considered here.

Note: Although the model of AsyncImage is of Any type, it is not actually able to receive all types, but it supports many image formats. That’s why I use Any! ! !

setContent {<!-- -->
ImageCacheForCoilTheme {<!-- -->
        val imageUrl="https://xxxx.jpg"
        
        AsyncImage(
model = imageUrl,
contentDescription = null,
modifier = Modifier
.fillMaxWidth()
.aspectRatio(1280f / 847f)//Picture ratio
)
    }
}

After running, the picture will be displayed perfectly. If you turn on airplane mode and restart, the picture can still be displayed normally. So how does Coil help us load and cache network pictures? Don’t be in a hurry and look down.

Customize ImageLoder

In fact, coil loads images from url through imageLoader, and caches them behind the scenes. ImageLoader can be called directly in MainActivity, which is the default image loader of Coil. Next we will customize our own image loader to meet various needs in our development.

Create MyApplication, inherit Application and implement the interface of ImageLoaderFactory, and rewrite our own image loader in the newImageLoader method Override the default. It can be seen from the code that there are still many types provided, which can be customized according to needs.

class MyApplication : Application(), ImageLoaderFactory {<!-- -->

override fun newImageLoader(): ImageLoader {<!-- -->
//Here we can customize our ImageLoader
return ImageLoader(this).newBuilder()
.memoryCachePolicy(CachePolicy.ENABLED)//Enable memory cache policy
.memoryCache {<!-- -->//Build memory cache
MemoryCache.Builder(this)
.maxSizePercent(0.1)//Only 10% of the remaining memory space can be used at most
.strongReferencesEnabled(true)//Enable strong references
.build()
}
.diskCachePolicy(CachePolicy.ENABLED)//Enable disk cache policy
.diskCache {<!-- --> //Build disk cache
DiskCache.Builder()
.maxSizePercent(0.03)//Only 3% of the remaining memory space can be used at most
.directory(cacheDir)//Stored in the cache directory
.build()
}
//.callFactory {<!-- -->
// //Allows you to intercept requests made by Coil, assuming you request a picture that requires authentication
// //So you only need to append the token to the header of the http request. Coil will not intercept it by default.
// Call.Factory{<!-- -->
// it.newBuilder()
// .addHeader("Authorization", "")
// .build()
// }
//}
.logger(DebugLogger())//Enable debugging logging
.build()
}
}

After the configuration is completed, declare it in the manifest file.

<application
        android:name=".MyApplication"
        ...
</application>

Check the Log after running. It will automatically record the loading status and where it was loaded from of our images.

Coil defaults to a mixture of memory cache and disk cache. If a larger image is involved, Coil will cache it to the disk. If it is a smaller image, it will use the memory cache. From the picture below, you can see where Coil intelligently helps us cache.

It can be seen from the dynamic diagram that after rotating the screen, the image is not loaded from the network again but is loaded from the Disk cache.

Clear cache

Of course, if you want to have more control over the cache, for example, you want to clear the cache data, this is something we often do. Suppose there is a social application. After the user logs in, many recommended pictures will be displayed and cached. However, after the user logs out, we do not want to keep these pictures in the cache and let them occupy memory. In this case, we need to completely clear cache.

Here is an example. After the image is loaded from the cache, the user clicks the clear cache button and then rotates the screen. Check the Log and you will find that the rotated image is loaded from the network, indicating that our cache clearing is successful.

setContent {<!-- -->
ImageCacheForCoilTheme {<!-- -->
val imageUrl ="https://xxx.jpg"
Column(
modifier = Modifier.fillMaxSize()
) {<!-- -->
AsyncImage(
model = imageUrl,
contentDescription = null,
modifier = Modifier
.fillMaxWidth()
.aspectRatio(1280f/847f)
)
Button(
onClick = {<!-- -->
                  //Clear all memory and disk cache
imageLoader.diskCache?.clear()
imageLoader.memoryCache?.clear()
}
) {<!-- -->
Text(text = "Clear cache")
}
}
    }
}

Of course, if you don’t want to clear all caches, but only clear specific image caches, Coil also supports this. Let’s demonstrate next:

In order to see the effect more clearly, I used two pictures from Baidu and Google as demonstrations. First, both pictures have been successfully loaded from the network and displayed. Press the clear button to clear the cache of the second Google photo, and then rotate screen, the result should be: Baidu Photos loads from cache, while Google Photos loads from the web.

setContent {<!-- -->
ImageCacheForCoilTheme {<!-- -->
val baiduUrl="https://www.baidu.com/....png"
val googleUrl="https://www.google.cn/....png"
        
        Column(
modifier = Modifier.fillMaxSize()
) {<!-- -->
AsyncImage(
model = baiduUrl,
contentDescription = null,
modifier = Modifier
.fillMaxWidth()
.aspectRatio(1280f/847f)
)
AsyncImage(
model = googleUrl,
contentDescription = null,
modifier = Modifier
.fillMaxWidth()
.aspectRatio(1280f/847f)
)
Button(
                onClick = {<!-- -->
                    //Clear Google Photos cache
imageLoader.diskCache?.remove(googleUrl)
imageLoader.memoryCache?.remove(MemoryCache.Key(googleUrl))
}
            ){<!-- -->
Text(text = "Clear cache")
}
}
    }
}

As shown in the picture, the effect is consistent with what I explained. Google Photos does re-initiate the network request to load, and loading data from the cache is much faster than the network, so the Log prints Baidu loading successfully first, and then Google.

Notice:

  • Clear all caches using: clear()
  • To clear a specific cache use: remove()

It can be concluded from calling the remove() method that to remove a specific cache, you only need to pass in a key value (single), and the mechanism in ImageLoader is to use this key to determine whether the image has been cached. If not, initiate a request, and vice versa. Loading in the cache and removing the cache are also searched through this key.