Image loading in Android is more complex than BitmapFactory.decodeStream(). A proper image library handles caching, downsampling, memory management, GIF decoding, cancellation, and lifecycle awareness. Understanding how Glide and Coil work internally helps you configure them correctly and debug issues.
Architecture: Request → Decode → Cache → Display
Both libraries follow the same pipeline:
Request (URL + target size + options)
↓
Key → Memory Cache lookup
↓ (miss)
Key → Disk Cache lookup
↓ (miss)
Network fetch (OkHttp)
↓
Decode (BitmapFactory, GIF decoder, etc.)
↓
Transform (crop, resize, blur, etc.)
↓
Memory Cache write
↓
Display in ImageView / Composable
Glide: Setup and Usage
// implementation("com.github.bumptech.glide:glide:4.16.0")
// ksp("com.github.bumptech.glide:ksp:4.16.0")
// Basic load
Glide.with(context)
.load(imageUrl)
.placeholder(R.drawable.loading_placeholder)
.error(R.drawable.error_placeholder)
.centerCrop()
.into(imageView)
// Load with custom size (override auto-size detection)
Glide.with(context)
.load(imageUrl)
.override(400, 300) // decode at exactly 400x300 — reduces memory
.diskCacheStrategy(DiskCacheStrategy.ALL) // cache both original and transformed
.into(imageView)
// Preload (cache without displaying)
Glide.with(context).load(nextPageImageUrls).preload()
Glide: Custom AppGlideModule
Configure OkHttp, cache size, and global options:
@GlideModule
class AppGlideModule : AppGlideModule() {
override fun applyOptions(context: Context, builder: GlideBuilder) {
builder
.setMemoryCache(LruResourceCache(30 * 1024 * 1024)) // 30 MB memory cache
.setDiskCache(InternalCacheDiskCacheFactory(context, 250 * 1024 * 1024)) // 250 MB disk
.setDefaultRequestOptions(
RequestOptions()
.format(DecodeFormat.PREFER_RGB_565) // 2 bytes/px vs ARGB_8888's 4 bytes
.diskCacheStrategy(DiskCacheStrategy.AUTOMATIC)
)
}
override fun registerComponents(context: Context, glide: Glide, registry: Registry) {
// Replace Glide's default HttpUrlConnection with OkHttp
registry.replace(GlideUrl::class.java, InputStream::class.java,
OkHttpUrlLoader.Factory(okHttpClient))
}
}
Coil: Setup and Usage
// implementation("io.coil-kt:coil:2.6.0")
// implementation("io.coil-kt:coil-compose:2.6.0")
// In Application.onCreate() — configure singleton
val imageLoader = ImageLoader.Builder(context)
.memoryCache {
MemoryCache.Builder(context)
.maxSizePercent(0.25) // use 25% of available heap
.build()
}
.diskCache {
DiskCache.Builder()
.directory(context.cacheDir.resolve("coil_cache"))
.maxSizeBytes(250 * 1024 * 1024) // 250 MB
.build()
}
.okHttpClient(okHttpClient)
.build()
Coil.setImageLoader(imageLoader)
// In Compose:
AsyncImage(
model = ImageRequest.Builder(context)
.data(imageUrl)
.crossfade(true)
.size(400, 300) // request specific size
.memoryCachePolicy(CachePolicy.ENABLED)
.build(),
contentDescription = "Article image",
contentScale = ContentScale.Crop,
modifier = Modifier.fillMaxWidth().height(200.dp),
placeholder = painterResource(R.drawable.placeholder),
error = painterResource(R.drawable.error)
)
Glide vs Coil Comparison
| Feature | Glide | Coil |
|---|---|---|
| Language | Java (Kotlin-friendly) | Kotlin-first |
| Coroutines | Wrapper needed | Native |
| Compose | rememberGlidePainter extension | AsyncImage built-in |
| GIF support | Built-in | Separate artifact (coil-gif) |
| Video frames | Built-in | Separate artifact |
| Memory management | Pools bitmaps aggressively | Uses standard Kotlin GC |
| Annotation processor | @GlideModule (KSP) | None needed |
Memory: The Critical Config
Both libraries downsample images to the view size before storing in memory. The most common mistake is loading full-resolution images into small thumbnails:
// ❌ Loads a 4000×3000 image into a 100×100 thumbnail
Glide.with(context).load(highResUrl).into(smallImageView)
// ✅ Downsample to actual display size
Glide.with(context).load(highResUrl)
.override(Target.SIZE_ORIGINAL) // or explicit size
.into(imageView)
// ✅ Or let layout size drive it — Glide reads ViewTarget size automatically
// Ensure ImageView has fixed dimensions, not WRAP_CONTENT
Cache Invalidation
// Coil: clear memory cache
imageLoader.memoryCache?.clear()
// Coil: invalidate a specific URL
imageLoader.diskCache?.remove(imageUrl)
// Glide: clear memory (main thread)
Glide.get(context).clearMemory()
// Glide: clear disk (background thread only)
GlobalScope.launch(Dispatchers.IO) {
Glide.get(context).clearDiskCache()
}
Key Takeaways
| Concern | Rule |
|---|---|
| Library choice | Coil for new Kotlin/Compose projects; Glide for existing Java/complex GIF needs |
| Disk cache size | 100–500 MB typical; tune based on content type |
| Memory cache | 20–30 MB or 25% of heap; avoid OOM by not loading full-res into small views |
| Placeholders | Always set placeholder and error for better UX |
| Preloading | Preload next-page images during scroll to reduce perceived latency |
| Cancellation | Both libraries cancel loads when the view/composable leaves composition |