Working with graphics is one of the most popular tasks when developing Android applications. Bitmap (raster image) is the basis for displaying pictures, processing photographs and creating user interfaces. But incorrect use of this class can lead to OutOfMemoryErrorinterface freezes or memory leaks. In this article, we will look at 5 proven ways to obtain Bitmap Android - from basic to advanced, taking into account optimization and error handling.

If you are just starting to work with graphics in Android, start with the first section. Experienced developers will find the section about memory-limited decoding and working with Glide/Picassouseful. All code examples are current for Android 14 (API 34) and compatible with earlier versions (starting with API 16).

Before diving into the code, remember a key rule: Bitmap takes 4 times more memory than the original file (due to format ARGB_8888). For example, a photo from a camera 4000ร—3000 pixels "weighs" ~48 MB in memory! This is critical for devices with limited resources.

โ•โ•โ•

1. data-i="46">The easiest way is to load an image from folders

The easiest way is to upload an image from folders res/drawable or res/raw. Suitable for static icons, backgrounds and small pictures that have been added to the project in advance.

Use the method BitmapFactory.decodeResource():

// Retrieving from drawable

Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.my_image);

// Receiving from raw (for example, for vector SVGs converted to raster)

InputStream inputStream = getResources().openRawResource(R.raw.my_raw_image);

Bitmap bitmapFromRaw = BitmapFactory.decodeStream(inputStream);

โš ๏ธ Attention: If the image is large (for example 2048ร—2048), decoding without parameters may cause OutOfMemoryError. Always specify the target size using BitmapFactory.Options:

BitmapFactory.Options options = new BitmapFactory.Options();

options.inJustDecodeBounds = true; // Read only dimensions

BitmapFactory.decodeResource(getResources(), R.drawable.my_image, options);

// Calculate the scaling factor

int imageHeight = options.outHeight;

int imageWidth = options.outWidth;

int scaleFactor = Math.min(imageWidth / 1024, imageHeight / 1024); // Max. size 1024px

options.inJustDecodeBounds = false;

options.inSampleSize = scaleFactor; // Reduce the image by 'scaleFactor' times

Bitmap scaledBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.my_image, options);

For vector images (.xml to drawable) use VectorDrawable and convert to Bitmap only if necessary:

Drawable vectorDrawable = ContextCompat.getDrawable(context, R.drawable.ic_vector);

Bitmap bitmap = Bitmap.createBitmap(vectorDrawable.getIntrinsicWidth(),

vectorDrawable.getIntrinsicHeight(),

Bitmap.Config.ARGB_8888);

Canvas canvas = new Canvas(bitmap);

vectorDrawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());

vectorDrawable.draw(canvas);

Check image size in the drawable folder (optimally < 1 MB)

Use inSampleSize for large images

For vector icons, give preference to VectorDrawable

Specify the target Bitmap.Config (ARGB_8888 or RGB_565)

-->

2. Creating a Bitmap from a file (JPEG, PNG, WebP)

A common task is loading images from the deviceโ€™s internal memory, SD card or cache. Here it is important to consider file permissions (especially for Android 10+) and handle paths correctly.

Basic example of reading a file:

String filePath = Environment.getExternalStorageDirectory() + "/Pictures/my_photo.jpg";

Bitmap bitmap = BitmapFactory.decodeFile(filePath);

But this code will crash on Android 10+ without permission READ_EXTERNAL_STORAGE. The correct approach is to use MediaStore or Storage Access Framework:

// For Android 10+ (Scoped Storage)

ContentResolver resolver = getContentResolver();

Uri imageUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;

Cursor cursor = resolver.query(imageUri, new String[]{MediaStore.Images.Media.DATA},

null, null, null);

if (cursor != null && cursor.moveToFirst()) {

String path = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DATA));

Bitmap bitmap = BitmapFactory.decodeFile(path);

cursor.close();

}

For files in the application's internal memory:

File internalFile = new File(getFilesDir(), "cached_image.png");

Bitmap bitmap = BitmapFactory.decodeFile(internalFile.getAbsolutePath());

๐Ÿ’ก

To work with large files (>5 MB) use BitmapRegionDecoder. It allows you to load only the visible part of the image (for example, for zooming), saving memory.

3. Bitmap from URL (network download)

Downloading images from the Internet is a classic task for applications with content (social networks, news aggregators, stores). Never load Bitmap in the main thread - this will block the UI and cause ANR (Application Not Responding).

Minimal working example with AsyncTask (for simple cases):

class DownloadImageTask extends AsyncTask {

protected Bitmap doInBackground(String... urls) {

try {

URL url = new URL(urls[0]);

HttpURLConnection connection = (HttpURLConnection) url.openConnection();

connection.setDoInput(true);

connection.connect();

InputStream input = connection.getInputStream();

return BitmapFactory.decodeStream(input);

} catch (IOException e) {

e.printStackTrace();

return null;

}

}

protected void onPostExecute(Bitmap result) {

if (result != null) {

ImageView imageView = findViewById(R.id.imageView);

imageView.setImageBitmap(result);

}

}

}

// Call

new DownloadImageTask().execute("https://example.com/image.jpg");

For production applications, use the libraries:

  • ๐Ÿ“ฆ Glide โ€” Google recommended, supports caching, transformations and GIF:
  • Glide.with(context)
    

    .load("https://example.com/image.jpg")

    .placeholder(R.drawable.placeholder)

    .into(imageView);

  • ๐Ÿ–ผ๏ธ Picasso โ€” lightweight alternative:
  • Picasso.get()
    

    .load("https://example.com/image.jpg")

    .resize(800, 600)

    .centerCrop()

    .into(imageView);

  • ๐Ÿš€ Coil โ€”a modern library from the Kotlin community with support for SVG i WebP.

Glide|Picasso|Coil|Own solution|I donโ€™t use-->

โš ๏ธ Attention: When loading by HTTPS make sure that your application supports modern encryption protocols. On devices with Android 7-9 you may need to update security provider:

ProviderInstaller.installIfNeeded(context);

Working with user photos requires processing Intents and permissions. Let's look at two scenarios: take a photo and select from the gallery.

1. Shooting through the camera:

// Requesting permission (for Android 6+)

if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)

!= PackageManager.PERMISSION_GRANTED) {

ActivityCompat.requestPermissions(this,

new String[]{Manifest.permission.CAMERA}, REQUEST_CAMERA);

}

// Launching the camera

Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);

if (takePictureIntent.resolveActivity(getPackageManager()) != null) {

startActivityForResult(takePictureIntent, REQUEST_IMAGE_CAPTURE);

}

// Processing the result

@Override

protected void onActivityResult(int requestCode, int resultCode, Intent data) {

if (requestCode == REQUEST_IMAGE_CAPTURE && resultCode == RESULT_OK) {

Bundle extras = data.getExtras();

Bitmap imageBitmap = (Bitmap) extras.get("data"); // Thumbnail!

// For a full-size photo, use the Uri (see below)

}

}

2. Selection from gallery:

Intent pickPhotoIntent = new Intent(Intent.ACTION_PICK,

MediaStore.Images.Media.EXTERNAL_CONTENT_URI);

startActivityForResult(pickPhotoIntent, REQUEST_PICK_IMAGE);

// Processing (taking into account Scoped Storage on Android 10+)

@Override

protected void onActivityResult(int requestCode, int resultCode, Intent data) {

if (requestCode == REQUEST_PICK_IMAGE && resultCode == RESULT_OK) {

Uri selectedImageUri = data.getData();

try {

InputStream inputStream = getContentResolver().openInputStream(selectedImageUri);

Bitmap bitmap = BitmapFactory.decodeStream(inputStream);

} catch (FileNotFoundException e) {

e.printStackTrace();

}

}

}

For full-size photos from the camera, save the image to a temporary file:

Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);

File photoFile = createTempImageFile(); // Create a file in the cache

Uri photoUri = FileProvider.getUriForFile(this,

"com.example.android.fileprovider",

photoFile);

takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoUri);

startActivityForResult(takePictureIntent, REQUEST_IMAGE_CAPTURE);

How to process EXIF โ€‹โ€‹data (orientation) of a photo?

When reading a Bitmap from a camera file, the image can be rotated (for example, by 90ยฐ). Use ExifInterface to adjust:

ExifInterface exif = new ExifInterface(filePath);

int orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION,

ExifInterface.ORIENTATION_NORMAL);

Matrix matrix = new Matrix();

switch (orientation) {

case ExifInterface.ORIENTATION_ROTATE_90:

matrix.postRotate(90);

break;

// ... other cases

}

Bitmap rotatedBitmap = Bitmap.createBitmap(bitmap, 0, 0,

bitmap.getWidth(), bitmap.getHeight(),

matrix, true);

5. Memory optimization when working with Bitmap

Bitmap takes up 4 times more memory than the original JPEG/PNG file (due to format ARGB_8888). On devices with 2GB RAM, this may crash the app. Here are the key optimization techniques:

1. Use the correct one Bitmap.Config:

  • ๐ŸŽจ ARGB_8888 (4 bytes/pixel) - for images with transparency.
  • ๐Ÿ–ผ๏ธ RGB_565 (2 bytes/pixel) - for opaque images (saves 50% memory).
  • ๐Ÿ“Š ALPHA_8 (1 byte/pixel) - for masks and alpha channels.
BitmapFactory.Options options = new BitmapFactory.Options();

options.inPreferredConfig = Bitmap.Config.RGB_565; // Save memory

Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.my_image, options);

2. Reduce the size using inSampleSize:

How to calculate the scaling factor:

BitmapFactory.Options options = new BitmapFactory.Options();

options.inJustDecodeBounds = true;

BitmapFactory.decodeResource(getResources(), R.id.my_image, options);

// Calculate inSampleSize (target size - 512px)

int scale = 1;

while (options.outWidth / scale > 512 || options.outHeight / scale > 512) {

scale *= 2;

}

options.inJustDecodeBounds = false;

options.inSampleSize = scale;

Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.id.my_image, options);

3. Reusing Bitmap:

If you need to update the image frequently (for example, in a camera or game), use Bitmap.eraseColor() instead of creating a new object:

// Instead: bitmap = Bitmap.createBitmap(...);

bitmap.eraseColor(Color.TRANSPARENT); // Clear old data

Canvas canvas = new Canvas(bitmap);

// Draw new content on canvas

4. Caching with LruCache:

Store frequently used Bitmaps in memory with a size limit:

LruCache memoryCache = new LruCache(maxMemory / 8) {

@Override

protected int sizeOf(String key, Bitmap bitmap) {

return bitmap.getByteCount(); // Size in bytes

}

};

// Add to cache

memoryCache.put("image_key", bitmap);

// Retrieving from cache

Bitmap cachedBitmap = memoryCache.get("image_key");

Optimization method Memory saving Suitable for
RGB_565 instead of ARGB_8888 50% Opaque images
inSampleSize=2 75% Any large images
BitmapRegionDecoder up to 90% Viewing part of a large image
LruCache depends on the cache size Frequently used pictures (avatars, icons)
๐Ÿ’ก

Always check the Bitmap size after downloading using bitmap.getByteCount(). If it exceeds 10% of available memory (Runtime.getRuntime().maxMemory()), reduce the resolution or use BitmapRegionDecoder.

6. Error handling and best practices

Working with Bitmap is fraught with errors - from OutOfMemoryError to FileNotFoundException. Here's how to avoid them:

1. Check for null:

Always handle cases where the Bitmap did not load:

Bitmap bitmap = BitmapFactory.decodeFile(filePath);

if (bitmap == null) {

// Show stub or reload

imageView.setImageResource(R.drawable.placeholder_error);

}

2. Processing OutOfMemoryError:

If the image is too large, reduce its size recursively:

try {

Bitmap bitmap = decodeSampledBitmapFromResource(res, resId, reqWidth, reqHeight);

} catch (OutOfMemoryError e) {

// Reduce the target size by 2 times and repeat

bitmap = decodeSampledBitmapFromResource(res, resId, reqWidth/2, reqHeight/2);

}

3. Freeing memory:

Call recycle() only if you are sure that the Bitmap is no longer needed and it is not bound to View:

if (bitmap != null && !bitmap.isRecycled()) {

bitmap.recycle();

bitmap = null;

}

4. Permissions for Android 10+:

Starting from Android 10 (API 29), access to files is limited. Scoped Storage. Use:

  • ๐Ÿ“ MediaStore to work with media files.
  • ๐Ÿ”— Storage Access Framework to select files by the user.
  • ๐Ÿ“‚ getExternalFilesDir() for internal application files.

5. Testing on different devices:

Bitmap behaves differently on devices with:

  • ๐Ÿ“ฑ Small amount of RAM (< 2 GB).
  • ๐Ÿ–ฅ๏ธ Large screens (tablets, foldable devices).
  • ๐Ÿค– Old versions of Android (< API 21).
๐Ÿ’ก

To debug memory leaks, use Android Profiler in Android Studio. Enable tracking Bitmap in the memory settings (Settings โ†’ Memory โ†’ Track Bitmap allocations).

FAQ: Frequently asked questions about working with Bitmap

How to convert Bitmap to byte[] (and vice versa)?

To save a Bitmap to a database or send it over the network, use ByteArrayOutputStream:

// Bitmap โ†’ byte[]

ByteArrayOutputStream stream = new ByteArrayOutputStream();

bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream);

byte[] byteArray = stream.toByteArray();

// byte[] โ†’ Bitmap

Bitmap bitmap = BitmapFactory.decodeByteArray(byteArray, 0, byteArray.length);

For JPEG, specify the compression quality (0โ€“100):

bitmap.compress(Bitmap.CompressFormat.JPEG, 85, stream);
Why is the Bitmap rotated 90 degrees after loading from the camera?

This is due to the EXIF metadatathat the camera saves. adjustments: ExifInterface for adjustment:

ExifInterface exif = new ExifInterface(filePath);

int rotation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION,

ExifInterface.ORIENTATION_NORMAL);

Matrix matrix = new Matrix();

switch (rotation) {

case ExifInterface.ORIENTATION_ROTATE_90:

matrix.postRotate(90);

break;

case ExifInterface.ORIENTATION_ROTATE_180:

matrix.postRotate(180);

break;

// ... other cases

}

Bitmap rotatedBitmap = Bitmap.createBitmap(bitmap, 0, 0,

bitmap.getWidth(), bitmap.getHeight(),

matrix, true);

How to crop a Bitmap to a circle or rounded rectangle?

Use BitmapShader i Canvas:

public Bitmap getRoundedBitmap(Bitmap bitmap, int radius) {

Bitmap output = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(),

Bitmap.Config.ARGB_8888);

Canvas canvas = new Canvas(output);

Paint paint = new Paint();

Rect rect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight());

RectF rectF = new RectF(rect);

paint.setAntiAlias(true);

canvas.drawRoundRect(rectF, radius, radius, paint);

paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));

canvas.drawBitmap(bitmap, rect, rect, paint);

return output;

}

For a circle, specify radius = Math.min(width, height) / 2.

Is it possible to use Bitmap in the Worker/Background service?

Yes, but keep in mind:

  • ๐Ÿ”„ Bitmap is tied to Java heap, not to native memory. Long-running operations can block GC.
  • ๐Ÿ—‘๏ธ In Worker (WorkManager), avoid storing large Bitmaps in memory - save them to temporary files.
  • โš ๏ธ B Foreground Service limit the Bitmap size to 1-2 MB so as not to cause ANR.

Example of secure upload to Worker:

public class BitmapWorker extends Worker {

public BitmapWorker(Context context, WorkerParameters params) {

super(context, params);

}

@Override

public Result doWork() {

try {

File outputFile = new File(getApplicationContext().getCacheDir(), "temp.jpg");

// Load Bitmap, save to file

Bitmap bitmap = ...;

try (FileOutputStream fos = new FileOutputStream(outputFile)) {

bitmap.compress(Bitmap.CompressFormat.JPEG, 90, fos);

}

bitmap.recycle(); // Free up memory

return Result.success();

} catch (Exception e) {

return Result.failure();

}

}

}

How to reduce the weight of Bitmap without losing quality?

Use a combination of methods:

  1. Reduce resolution using inSampleSize.
  2. Apply JPEG compression with 70-85% quality:
    bitmap.compress(Bitmap.CompressFormat.JPEG, 80, outputStream);
  3. Convert to WebP (supported with API 14):
    bitmap.compress(Bitmap.CompressFormat.WEBP, 75, outputStream);
  4. For transparent images, use WebP lossy (WEBP_LOSSY).

Comparison of formats (for an image 1024ร—1024):

FormatQualityFile sizeTransparency
PNGwithout losses~1.5 MByes
JPEG (90%)lossy~300 KBno
WebP (80%)lossy~250 KByes
WebP (lossless)lossless~1 MByes