androidengineers.Book a session

Media & Graphics

Image Loading (Glide/Coil) Internals

article20 minHard

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

FeatureGlideCoil
LanguageJava (Kotlin-friendly)Kotlin-first
CoroutinesWrapper neededNative
ComposerememberGlidePainter extensionAsyncImage built-in
GIF supportBuilt-inSeparate artifact (coil-gif)
Video framesBuilt-inSeparate artifact
Memory managementPools bitmaps aggressivelyUses 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

ConcernRule
Library choiceCoil for new Kotlin/Compose projects; Glide for existing Java/complex GIF needs
Disk cache size100–500 MB typical; tune based on content type
Memory cache20–30 MB or 25% of heap; avoid OOM by not loading full-res into small views
PlaceholdersAlways set placeholder and error for better UX
PreloadingPreload next-page images during scroll to reduce perceived latency
CancellationBoth libraries cancel loads when the view/composable leaves composition

YOUR LEARNING JOURNEY

0 of 177 available lessons completed

Progress saved in this browser. No account needed.
Image Loading (Glide/Coil) Internals | Android System Design | Android Engineers