Decoding AVIF Drawables Using AndroidX Graphics

The AV1 Image File Format (AVIF) provides high-efficiency image compression that significantly reduces file sizes while maintaining superior visual fidelity. This guide explains how Android developers can decode AVIF files into usable drawables within Android applications, leveraging native graphics decoders alongside AndroidX graphics utilities, handling decoding pipelines efficiently, and addressing backward compatibility for legacy devices.

Native AVIF Support Overview

Starting with Android 12 (API level 31), Android natively supports AVIF decoding via the platform's core graphics stack. Developers can decode .avif assets into Drawable or Bitmap objects using android.graphics.ImageDecoder and android.graphics.BitmapFactory.

AndroidX enhances this pipeline through components like androidx.core.graphics.drawable.DrawableCompat and general AndroidX resource handling, ensuring drawables integrate cleanly across various UI components and framework versions.

Decoding AVIF to a Drawable Using ImageDecoder

The recommended method for decoding modern image formats like AVIF is ImageDecoder. It outperforms legacy BitmapFactory implementations by supporting direct decoding to AnimatedImageDrawable (for animated AVIF files), hardware bitmap allocation, and memory-efficient scaling.

Step 1: Add the AVIF File to Your Project

Place your AVIF image into the res/raw/ directory (for example, res/raw/sample_image.avif). Avoid placing .avif files directly into res/drawable/ if targeting older build tools, as unindexed formats in that folder can cause resource compilation warnings.

Step 2: Decode the Asset to a Drawable

Use ImageDecoder.createSource() and ImageDecoder.decodeDrawable() on a background thread to prevent blocking the UI:

import android.content.Context
import android.graphics.ImageDecoder
import android.graphics.drawable.Drawable
import android.os.Build
import androidx.annotation.RawRes
import androidx.core.graphics.drawable.DrawableCompat
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext

suspend fun loadAvifDrawable(context: Context, @RawRes resourceId: Int): Drawable? {
    return withContext(Dispatchers.IO) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
            val source = ImageDecoder.createSource(context.resources, resourceId)
            val decodedDrawable = ImageDecoder.decodeDrawable(source) { decoder, info, _ ->
                // Optimize memory by configuring decode properties
                decoder.allocator = ImageDecoder.ALLOCATOR_SOFTWARE // or ALLOCATOR_HARDWARE
                decoder.isLowRamModeEnabled = false
            }
            // Ensure safe usage with AndroidX DrawableCompat wrapper if needed
            DrawableCompat.wrap(decodedDrawable)
        } else {
            // Handle pre-Android 12 devices using a fallback mechanism
            null
        }
    }
}

Step 3: Display the Drawable in the UI

Once decoded, update your user interface on the main thread:

lifecycleScope.launch {
    val avifDrawable = loadAvifDrawable(context, R.raw.sample_image)
    avifDrawable?.let { drawable ->
        imageView.setImageDrawable(drawable)
    }
}

Handling Backward Compatibility (Pre-Android 12)

Because native AVIF decoding is only supported on Android 12 (API 31) and higher, handling older API levels requires a dedicated fallback strategy:

  1. Third-Party C++ Decoder Integration: Integrate an open-source decoder such as libavif using the Android NDK to parse AVIF byte streams into standard android.graphics.Bitmap objects.
  2. Image Loading Libraries: Modern image libraries such as Coil or Glide feature AVIF decoder extensions backed by libavif. These libraries integrate with the AndroidX lifecycle and return standard AndroidX-compatible drawables automatically across all Android versions.
  3. Format Fallback: Provide WebP or PNG alternatives in your resource catalog for devices running API 30 or lower.

Best Practices for Decoding AVIF