BitmapFactory Memory Management for AVIF Assets

Android’s BitmapFactory manages memory when loading high-resolution AVIF (AV1 Image File Format) assets by streaming compressed data through native decoders and allocating pixel buffers directly in native memory. Because AVIF provides high compression ratios, a small file can expand into an enormous uncompressed bitmap in memory. BitmapFactory controls this footprint through native heap allocation, configuration parameters like inSampleSize and inPreferredConfig, reusable memory buffers via inBitmap, and zero-copy GPU transfers with Bitmap.Config.HARDWARE.

Native Heap Allocation

Since Android 8.0 (API level 26), pixel data decoded by BitmapFactory is stored in the native C++ heap rather than the Java Virtual Machine (JVM) heap. When an AVIF asset is loaded—supported natively since Android 12 (API level 31)—the underlying framework uses platform decoders (such as libgav1 or hardware-accelerated decoders via the media framework). The compressed AVIF byte stream is parsed natively, and raw RGBA pixel data is written straight to native memory allocations. While this prevents Java OutOfMemoryError (OOM) crashes caused by JVM heap saturation, unconstrained native allocations can still exhaust total system RAM and trigger the Android Low Memory Killer (LMK).

Pre-Allocation Inspection via inJustDecodeBounds

High-resolution AVIF files often have relatively small file sizes due to AV1 intra-frame compression, masking the massive memory requirement of their decoded raw dimensions. To manage memory before allocation occurs:

  1. Set BitmapFactory.Options.inJustDecodeBounds = true.
  2. Decode the file source. The native decoder reads only the AVIF container and image sequence headers to query image metadata.
  3. The method returns null for the Bitmap, but populates outWidth, outHeight, and outMimeType.

This step requires negligible memory and allows the application to calculate the exact target memory footprint before initiating pixel decompression.

Memory Reduction via Downsampling (inSampleSize)

Once the original dimensions are known, the memory footprint can be scaled down at decode time using inSampleSize. Setting inSampleSize to an integer power of two (e.g., 2, 4, 8) instructs the native decoder to subsample the AV1 pixels during the decoding pipeline.

For instance, an 8K AVIF image decoded at full resolution in standard 8-bit RGBA requires approximately 134 MB of raw native memory: \[\text{Width (7680)} \times \text{Height (4320)} \times 4 \text{ bytes per pixel} \approx 132.7 \text{ MB}\]

Setting inSampleSize = 2 reduces both dimensions by half, shrinking the native memory footprint by a factor of four to roughly 33 MB before the pixel buffer is committed.

Color Depth and Pixel Format Selection

AVIF natively supports High Dynamic Range (HDR) with 10-bit and 12-bit color depths. BitmapFactory manages memory consumption through inPreferredConfig:

Buffer Reuse via inBitmap

To eliminate memory fragmentation and native garbage collection thrashing during continuous high-resolution image decoding (such as in lists or pagers), BitmapFactory.Options.inBitmap allows an existing, mutable Bitmap allocation to be reused.

When decoding an AVIF into an existing inBitmap, the underlying native decoder writes new pixel data directly into the allocated memory buffer without requesting a new allocation from the operating system, provided the target buffer's byte count is greater than or equal to the incoming decoded AVIF's byte allocation requirements.