Google Play Console Flagged Your Bitmaps — Here Are the Configs That Actually Fix It
Background
This guy at Google really knows how to stir things up. I had just barely managed to bring down the crash and ANR data a bit, and recently I found he gave me another technical quality rectification suggestion. The gist of it is:
He really doesn't let me rest, always concerned about whether my KPIs are met, always giving me technical improvements. But then again, this is a bitmap optimization suggestion. It says my project needs to use an image loading library to let them automatically handle downsampling, caching, and memory management. Isn't Glide in my project already doing these things? Or should I be configuring something? A bit confused... It seems necessary to first understand how to truly optimize bitmaps in a project.
What Problems Cause Google Play Console to Give This Rectification Suggestion?
First, understand how this suggestion comes about. The "Bitmap Optimization" detection item in Google Play Console scans bitmap resources in the APK/AAB to see if the following situations exist:
- Large bitmaps packed directly into the installation package: The actual resolution of the image far exceeds the display requirement, causing installation package bloat and runtime memory waste.
- Not using efficient formats like WebP: Still using uncompressed formats like PNG/JPEG.
- Images not adapted for screen density: Lacking differentiation for different dpi directories (mdpi / hdpi / xhdpi / xxhdpi / xxxhdpi).
- No downsampling at runtime: Images are not sampled according to the actual display size when loaded into memory, causing memory waste.
If your project has the above situations, your application might also receive the same rectification suggestion in the Google backend as I did.
Image Loading Libraries
This is the most basic point: do not manage bitmap loading manually. Use mature image loading libraries; they automatically handle caching, downsampling, reuse, and recycling. Currently, there are two main image loading libraries for Android projects: one is Glide, the other is Coil.
- Key configurations for Coil:
ImageLoader,bitmapConfig,size - Key configurations for Glide:
DecodeFormat,DownsampleStrategy,override()
Coil Configuration (Compose Project Example)
Global ImageLoader Configuration
Using AsyncImage in Compose
SubcomposeAsyncImage in Lists (Asynchronous Loading to Avoid Jank)
Custom ImageLoader Interceptor (Cooperating with Server-Side Cropping)
Automatically append target size parameters after the URL.
Register in ImageLoader.
Glide Configuration (Traditional Android Project Example)
Global RequestOptions (Limiting Image Quality and Downsampling)
Limiting Memory Cache and BitmapPool
Glide Optimization for List Pages
Explicitly specify the target size in list items to let Glide automatically downsample.
Using Glide's RequestManager Lifecycle Management
Painter Parameter Trap: Do Not Pass Painter to Composable
Why can't you pass Painter? Because the Painter interface does not have the @Stable annotation. The Compose compiler cannot stably infer whether it has changed, easily leading to unnecessary recompositions. The root cause is that Bitmap's equals() calculation cost is very high, so the Compose compiler will not mark Painter as a stable type.
Avoiding Unnecessary Recompositions Triggered by Painter
Passing Resource ID or URL
Handling in ViewModel
Avoid Holding Bitmap or Painter in ViewModel
If You Must Hold a Bitmap, Use ImageBitmap and Pay Attention to Lifecycle
Image Downsampling
When loading images, always load an image size that exactly meets the display requirements, avoiding stuffing ultra-high-resolution images into small containers.
Using Image Libraries for Automatic Downsampling
Coil and Glide automatically downsample after determining the target size.
Coil's Size Resolution Priority
ImageRequest.Builder.size(width, height): Explicitly specifiedAsyncImage'sModifier.size(): Inferred from layout constraints- When unconstrained : Loads the original size; this behavior should be avoided as much as possible.
Glide's Size Resolution
Using override() to Explicitly Specify
Manual Downsampling (BitmapFactory)
If manual bitmap handling is necessary in the project, inSampleSize can be used for safe downsampling.
PS: inSampleSize must be a power of 2 (1, 2, 4, 8, 16...). The system will round down to the nearest power of 2.
Prioritize Server-Side Cropping
Best Practice: Request Precisely Sized Images
When requesting images from a backend API, try to include target width and height parameters to let the server return a cropped image. The benefits of doing this are:
- Reduces network transfer volume (faster downloads, less data usage)
- Reduces disk cache space usage
- Reduces memory overhead for device-side cropping
- Improves list scrolling smoothness
Implementation Method If using Coil, you can use the interceptor mentioned above. If using Glide, you can use the following method:
Backend coordination ensures the CDN or image service supports the following parameters:
| Parameter | Description | Example |
|---|---|---|
w / width |
Target width | ?w=200 |
h / height |
Target height | ?h=200 |
m / mode |
Crop mode (fill / fit / crop) | &m=crop |
q / quality |
Compression quality | &q=80 |
Avoid Unconstrained Layout Sizes
Do not use wrapContentSize or unconstrained sizes on Composables that load remote images. Doing so will cause:
Problems
When the image library cannot infer the target bounds, it falls back to loading the full original image, leading to:
- Memory usage far exceeding actual needs
- Increased loading latency
- List scrolling jank
Principle
Correct Approach
Set Explicit Sizes
Define Aspect Ratio, Let the Layout Engine Calculate Exact Pixel Target
Use Fixed Sizes in Lists
Avoid Unconstrained, Leading to Loading Original Size
Choosing the Correct Pixel Format
The memory usage difference between different pixel formats is significant:
| Format | Bits Per Pixel | Supports Transparency | Applicable Scenarios | Savings Compared to ARGB_8888 |
|---|---|---|---|---|
ARGB_8888 |
32 bit | Yes | Needs alpha channel (default) | / |
RGB_565 |
16 bit | No | Images not needing transparency | Saves half the memory |
ARGB_4444 |
16 bit | Yes | Not recommended (poor quality) | Saves half (not recommended) |
ALPHA_8 |
8 bit | Alpha only | Masks/Overlays | Saves 75% |
In non-special scenarios, RGB_565 is sufficient for over 80% of images in an app, which can immediately halve memory usage.
Configuration Method
Coil
Glide
Determining if Transparency is Needed
In a project, you can determine whether an image needs transparency based on different scenarios. For example, the following four scenarios do not need it:
But if you encounter the following scenarios, then the image must support transparency:
GPU Texture Upload Optimization
Calling ImageBitmap.prepareToDraw() can upload the texture to the GPU in advance before actual drawing, reducing the first frame rendering latency.
Most image loading libraries have this optimization built-in. Only when not using an image loading library and needing to call it manually should you do the above.
Hardware Bitmap
Android 8.0 (API 26) supports hardware bitmaps, where bitmap data exists only in GPU memory, reducing CPU memory usage. If using the Coil loading library, it automatically uses hardware bitmaps. If using Glide, Glide enables hardware bitmaps by default. If you want to disable it, you can use the following method:
Prioritize Vector Graphics Over Bitmaps
For scenarios like geometric shapes, icons, etc., always prioritize vector graphics (ShapeDrawable, VectorDrawable). The benefits of doing this are:
- A vector file is only a few hundred bytes in size, far smaller than a bitmap.
- Adaptable to any screen density.
- Memory usage does not increase with resolution.
- Supports theme colors (
tint).
So, why do we still need bitmaps? Or when is it more appropriate to use bitmaps versus vector graphics? Here are a few scenarios:
- Icons, logos: Suitable for vector graphics because the shapes are simple, files are small, and they are scalable.
- Photos, screenshots: Suitable for bitmaps because complex color gradients cannot be achieved by vector graphics.
- Animated icons: Suitable for vector graphics because of the small file size.
- Product images: Suitable for bitmaps because true color reproduction is needed.
Memory Management and Release
When Using an Image Loading Library
The image library manages the Bitmap pool, releasing Bitmaps back into the pool for reuse when they are no longer needed, retaining a memory buffer. Manual recycling is not required.
When Managing Manually
The Correct Way to Add Padding to Images
When adding padding to images, some people are accustomed to using letterboxing, directly modifying the image size to add transparent borders. However, this changes the image size and increases memory usage. The correct approach is to keep the image size unchanged and use InsetDrawable or add padding on the parent container.
Avoid Packing Large Images into APK/AAB
In some projects, a static resource image might be several hundred KB or even several MB. If loaded directly into memory, memory usage will definitely spike. Therefore, avoid introducing large images into the project. If a large image must be used, do the following things first:
- Convert to WebP format (Right-click → Convert to WebP).
- Compress the image, e.g., using TinyPNG.
- Host it on a server and download on demand.
Multi-Density Adaptation
Try to place images of different resolutions into corresponding directories to avoid loading high-resolution images on low-density devices.
Performance Monitoring and Analysis Methods
adb Command Monitoring
Android Studio Memory Profiler
- Open Profiler → Memory panel.
- Record the scenario: Enter page → Load images → Scroll → Leave page.
- Pay attention to the following metrics:
- Java Heap: Heap memory occupied by Bitmap objects.
- Native Heap: Native layer Bitmap pixel data.
- Graphics: GPU texture memory.
- Check if memory drops back after leaving the page (memory leak detection).
Coil Memory Monitoring
Finally
This article mainly talks about how to correctly handle bitmaps in a project to prevent warnings from Google. However, sometimes you might find that your project handles bitmaps well in every aspect, but you still receive rectification suggestions from Google. In this case, it's possible that a certain SDK you are using has problems in this area. Therefore, regularly check the official website updates of the third-party libraries you use. It's possible that the new version has already solved the problem, and you are still using the old version, analyzing the cause vigorously, which is really a waste of time and energy.