Bitmaps, images, and large objects
Compressed file size is not display memory. A 4000 × 3000 ARGB_8888 bitmap is roughly:
4000 × 3000 × 4 bytes ≈ 48 MB
Decoding several such images can exhaust memory even when each JPEG is only 2 MB.
Decode for the display target
Image loading libraries provide pooling, sampling, lifecycle cancellation, disk/memory caches, EXIF handling, and transformations. Supply requested dimensions/content scale rather than decoding the original into a 96 dp thumbnail.
AsyncImage(
model = ImageRequest.Builder(LocalContext.current)
.data(photo.url)
.size(256, 256)
.crossfade(true)
.build(),
contentDescription = photo.description,
)
Exact APIs depend on library/version; the principle is target-sized decoding.
Pixel formats and fidelity
ARGB_8888 is commonly 4 bytes/pixel. Lower precision formats may reduce memory but can degrade gradients/transparency/color and are not universally appropriate. Hardware bitmaps can reduce Java heap/copy cost but have mutability/software-rendering/readback limitations. Measure the real pipeline.
Lists and cancellation
- Stable keys prevent wrong-image reuse/state churn.
- Cancel off-screen requests with lifecycle/list ownership.
- Avoid unique transformed bitmap variants per scroll position.
- Preload only a bounded near-future window.
- Do not retain Activity through image callbacks/targets.
- Placeholder/error sizes should avoid layout jumps.
Large binary data
Never put bitmaps/byte arrays in Bundle, SavedStateHandle, Intent, WorkManager Data, Room row blobs by default, or a global StateFlow. Persist/stream them and pass stable URI/ID. Binder transaction limits and memory copies can crash or stall the app.
Processing pipeline
content URI/file -> metadata bounds -> sampled decode/stream
-> transform one bounded region
-> encode to partial file
-> validate -> atomically publish
For upload, stream encoded file rather than decode to bitmap unless transformation is needed. For region/zoom viewers, use tiled/region decoding. Guard pixel dimensions against decompression bombs before allocation.
Mistakes
- ❌
BitmapFactory.decodeStream()original for a tiny avatar. - ✅ inspect bounds and request/downsample to display size.
- ❌ convert image to Base64 String and keep it in state.
- ✅ store file/URI and stream it.
- ❌ fix OOM only by adding
largeHeap=true. - ✅ measure retention/decoded sizes/cache and reduce peak working set.
- ❌ recycle a bitmap still used by UI/library.
- ✅ let correct owner/library manage lifecycle.
Sources
Interview question
Why can a 500 KB photo cause OOM? Encoded size does not determine decoded memory; pixel dimensions × bytes per pixel plus copies/caches/transforms determine peak working set.