Working with images can quickly introduce performance issues if you aren't
careful. You can quite easily run into an OutOfMemoryError
when working
with large bitmaps. Follow these best practices to ensure your app performs at
its best.
Only load the size of the bitmap you need
Most smartphones have high resolution cameras that produce large image files. If you're showing an image on screen, you must either reduce the resolution of the image or only load the image up to the size of your image container. The constant loading of larger than needed images can exhaust GPU caches, leading to less performant UI rendering.
To manage image sizes:
- Scale down your image files to be as small as possible (without affecting output image).
- Consider converting your images to WEBP format instead of JPEG or PNGs.
- Supply smaller images for different screen resolutions (see Tip #3),
- Use an image loading library, which scales down your image to fit the size of your view on screen. This can help improve the loading performance of your screen.
Use vectors over bitmaps where possible
When representing something visually on screen, you need to decide if it can be represented as a vector or not. Prefer vector images over bitmaps, as they don’t pixelate when you scale them to different sizes. However, not everything can be represented as a vector - images taken with a camera can’t be converted into a vector.
Supply alternative resources for different screen sizes
If you are shipping images with your app, consider supplying different sized assets for different device resolutions. This can help reduce the download size of your app on devices, and improve performance as it’ll load up a lower resolution image on a lower resolution device. For more information on providing alternative bitmaps for different device sizes, check out the alternative bitmap documentation.
When using ImageBitmap
, call prepareToDraw
before drawing
When using ImageBitmap
, to start the process of uploading the texture to the
GPU, call ImageBitmap#prepareToDraw()
before actually drawing it. This
helps the GPU prepare the texture and improve the performance of showing a
visual on screen. Most image loading libraries already do this optimization, but
if you are working with the ImageBitmap
class yourself, it is something to
keep in mind.
Prefer passing a Int
DrawableRes
or URL as parameters into your composable instead of Painter
Due to the complexities of dealing with images (for example, writing an equals
function for Bitmaps
would be computationally expensive), the Painter
API is
explicitly not marked as a Stable class. Unstable classes can
lead to unnecessary recompositions because the compiler cannot easily infer if
the data has changed.
Therefore, it is preferable to pass a URL or drawable resource ID as parameters
to your composable, instead of passing a Painter
as a parameter.
// Prefer this:
@Composable
fun MyImage(url: String) {
}
// Over this:
@Composable
fun MyImage(painter: Painter) {
}
Don’t store a bitmap in memory longer than you need it
The more bitmaps you load into memory, the more likely it is that you could run
out of memory on the device. For instance, if loading a large list of Image
composables on screen, use LazyColumn
or LazyRow
to ensure that memory is
freed up when scrolling a large list.
Don’t package large images with your AAB/APK file
One of the top causes for large app download size is due to graphics that are packaged inside the AAB or APK file. Use the APK analyzer tool to ensure that you aren't packaging larger than required image files. Reduce the sizes or consider placing the images on a server and only downloading them when required.
Recommended for you
- Note: link text is displayed when JavaScript is off
- ImageBitmap vs ImageVector {:#bitmap-vs-vector}
- Save UI state in Compose
- Jetpack Compose Phases