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 drawableBitmap 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.get().load("https://example.com/image.jpg")
.resize(800, 600)
.centerCrop()
.into(imageView);
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);
4. Bitmap from camera or gallery
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:
LruCachememoryCache = 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:
- ๐
MediaStoreto work with media files. - ๐
Storage Access Frameworkto 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:
- ๐
Bitmapis tied to Java heap, not to native memory. Long-running operations can blockGC. - ๐๏ธ 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:
- Reduce resolution using
inSampleSize. - Apply JPEG compression with 70-85% quality:
bitmap.compress(Bitmap.CompressFormat.JPEG, 80, outputStream); - Convert to
WebP(supported with API 14):bitmap.compress(Bitmap.CompressFormat.WEBP, 75, outputStream); - For transparent images, use
WebPlossy (WEBP_LOSSY).
Comparison of formats (for an image 1024ร1024):
| Format | Quality | File size | Transparency |
|---|---|---|---|
| PNG | without losses | ~1.5 MB | yes |
| JPEG (90%) | lossy | ~300 KB | no |
| WebP (80%) | lossy | ~250 KB | yes |
| WebP (lossless) | lossless | ~1 MB | yes |