Improve Your App's Performance with Bitmap Downsampling
Technical quality report: Release 12 (12.0)
Improve your app's performance with bitmap downsampling.
Images are an important part of modern Android applications. However,
loading a high-resolution image directly into memory can consume
unnecessary resources, slow down the application, and potentially cause
an OutOfMemoryError.
This Google Play recommendation normally means that the application is
loading one or more images at a higher resolution than required by the
screen, thumbnail, or ImageView.
What Is Bitmap Downsampling?
Bitmap downsampling is the process of decoding an image at a smaller resolution before loading it into memory.
For example, suppose an image has a resolution of 4000 × 3000 pixels, but the application displays it inside a 400 × 300 pixel area. Loading the original image wastes memory because most of its pixels are not visible to the user.
Why Is Downsampling Important?
An Android bitmap using ARGB_8888 normally requires
approximately four bytes for every pixel.
The estimated memory requirement can be calculated as follows:
Bitmap memory = width × height × 4 bytes
A 4000 × 3000 image may therefore require:
4000 × 3000 × 4 = 48,000,000 bytes
That is approximately 48 MB of memory for only one image. If several large images are loaded simultaneously, the application may experience:
- Slow image loading
- High memory consumption
- Frozen or unresponsive screens
- Excessive garbage collection
- Poor scrolling performance
OutOfMemoryError- Unexpected application crashes
Downsampling reduces the image resolution before allocating the bitmap, resulting in lower memory usage and improved application performance.
Solution 1: Use BitmapFactory.Options
Android provides BitmapFactory.Options to inspect an image's
dimensions and calculate an appropriate sampling size before decoding it.
Calculate the Sampling Size
fun calculateInSampleSize(
options: BitmapFactory.Options,
requiredWidth: Int,
requiredHeight: Int
): Int {
val imageHeight = options.outHeight
val imageWidth = options.outWidth
var inSampleSize = 1
if (imageHeight > requiredHeight ||
imageWidth > requiredWidth
) {
val halfHeight = imageHeight / 2
val halfWidth = imageWidth / 2
while (
halfHeight / inSampleSize >= requiredHeight &&
halfWidth / inSampleSize >= requiredWidth
) {
inSampleSize *= 2
}
}
return inSampleSize
}
Decode the Downsampled Bitmap
fun decodeSampledBitmapFromFile(
filePath: String,
requiredWidth: Int,
requiredHeight: Int
): Bitmap? {
val options = BitmapFactory.Options().apply {
inJustDecodeBounds = true
}
// Read the image dimensions without allocating
// memory for the complete bitmap.
BitmapFactory.decodeFile(filePath, options)
options.inSampleSize = calculateInSampleSize(
options,
requiredWidth,
requiredHeight
)
// Decode the appropriately sampled image.
options.inJustDecodeBounds = false
return BitmapFactory.decodeFile(filePath, options)
}
The function can then be used as follows:
val bitmap = decodeSampledBitmapFromFile(
filePath = imagePath,
requiredWidth = 800,
requiredHeight = 600
)
imageView.setImageBitmap(bitmap)
Setting inJustDecodeBounds to true allows the
application to inspect the image dimensions without allocating memory for
the complete bitmap.
Solution 2: Use Coil
For modern Android projects, an image-loading library such as Coil can perform image resizing, memory management, and caching automatically.
imageView.load(imageUrl) {
size(800, 600)
crossfade(true)
}
For Jetpack Compose:
AsyncImage(
model = ImageRequest.Builder(LocalContext.current)
.data(imageUrl)
.size(800, 600)
.crossfade(true)
.build(),
contentDescription = "Optimised image",
contentScale = ContentScale.Crop,
modifier = Modifier.size(
width = 400.dp,
height = 300.dp
)
)
The image should be requested at approximately the same size as the UI component displaying it.
Solution 3: Use Glide
Glide can also resize and cache images efficiently:
Glide.with(imageView.context)
.load(imageUrl)
.override(800, 600)
.centerCrop()
.into(imageView)
The override() method instructs Glide to load the image using
the specified dimensions instead of decoding an unnecessarily large
version.
Additional Image Optimisation Recommendations
1. Resize Bundled Images
Images stored inside the application should be resized before being placed in the following directory:
res/drawable
Avoid including a 4000-pixel image if the application only displays it as a small icon or thumbnail.
2. Use WebP Where Appropriate
WebP can reduce an application's image file size while maintaining acceptable visual quality. However, file compression and bitmap downsampling solve two different problems:
- Compression reduces storage and download size.
- Downsampling reduces decoded image dimensions and memory usage.
An image can have a small compressed file size but still consume significant memory after being decoded.
3. Avoid Decoding Full-Resolution Images
Avoid loading an image directly using code such as:
val bitmap = BitmapFactory.decodeFile(imagePath)
imageView.setImageBitmap(bitmap)
This code decodes the complete image without considering the dimensions of the target view.
4. Load Thumbnails for Image Lists
For a RecyclerView, gallery, or media browser, load
thumbnail-sized images instead of full-resolution photographs. The
original image should only be loaded when the user opens the detailed
view.
5. Test on Lower-Memory Devices
An application may work correctly on a high-end development phone but crash on a device with limited memory. Testing should therefore include entry-level and older Android devices.
How to Verify the Improvement
- Build a new release of the application.
- Test every screen that displays large images.
- Monitor memory usage using Android Studio Profiler.
- Scroll through image-heavy lists repeatedly.
- Check Logcat for
OutOfMemoryError. - Upload the new Android App Bundle to Google Play Console.
- Monitor the technical quality report after Google processes the release.
Conclusion
Bitmap downsampling is a simple but important optimisation for Android applications. The application should decode an image based on the dimensions required by the user interface instead of always loading the full-resolution file.
For Release 12 (12.0), implementing appropriate image
resizing through BitmapFactory, Coil, or Glide can reduce
memory consumption, improve scrolling performance, prevent crashes, and
provide a smoother experience across different Android devices.
For additional technical information, refer to the official Android Developers documentation on loading large bitmaps efficiently .
Suggested labels: Android Development, App Performance, Bitmap, Google Play Console, Kotlin, Mobile Development
Reviewed by Admin
on
11:18 PM
Rating:

No comments: