I'm trying to lower the memory usage of my app, and I noticed most of it is caused by the activities' backgrounds: in XHDPI, 720*1280*4 = 3.6Mbytes each, and Android doesn't seem to release them as soon as they're not in use anymore.
Is there any way to tell Android to load certain resource images as RGB 565 instead of RGB 8888 in order to save some memory on those larger images without alpha channel ?
I know this can be done programmatically, but I was wondering if this can also be done with images and backgrounds used in the activities' layouts or with setBackgroundResource().
You can do it like this:
protected void onCreate(Bundle savedInstanceState)
{
setContentView(R.layout.high_scores);
BitmapFactory.Options opts = new BitmapFactory.Options();
opts.inPreferredConfig = Config.RGB_565;
BitmapDrawable highScoresBg = BitmapFactory.decodeResource(getResources(), R.drawable.achievements, opts);
findViewById(R.id.high_scores_root).setBackgroundDrawable(highScoresBg);
Pixels of your image will be stored on 2 bytes, instead of 4 when using ARGB_8888. 50% saved memory, but of course less quality.
This article tells us that on Android < 2.3 the images are in RGB_565 by default. However, if the image has alpha channel (PNG for instance), it will be loaded in ARGB_8888. On Android >= 2.3 all the images are loaded in ARGB_8888 by default.
There's currently no way to flag a resource to be RGB_565.
The best you could do, would be to provide a subsequent data file which maps your image files to desired in-memory formats. At load time, you can check each resource ID against the file to determine if you'd like to set the BitmapFactory options to 565 or 8888.
A similar process is used by most applications that pull down image data. For example, some JPG images which have a small resolution, are assumed to be thumbnails, and loaded into memory using the 565 path. Where full-screen JPGs are allowed to load into memory as 8888.
Basically, you'll always need some level of logic in your app to set the BitmapOptions accordingly.
Related
I have an 8MB png and when I try to load it into an Android ImageView I get an OutOfMemoryError that says it tried to allocate 32MB of memory and failed.
I'm working on changing the code to downsample the image to avoid using too much memory to avoid most of these problems, so I'm not looking for answers about downsampling. I'm trying to understand why the memory needs of the image are higher than the file size would imply.
Why is Android trying to allocate 4x the memory when loading the png?
I've set my options to tell it not to scale for pixel density:
BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferredConfig = Bitmap.Config.ARGB_8888;
options.inScaled = false;
It is 2848 x 4209 pixels
At the 4 bytes/pixel rate for ARGB_8888, that will be 47,948,928 bytes as a Bitmap.
The on-disk size of images represents a red herring. The major file formats (particularly PNG and JPEG) are compressed as files. That does not matter. What matters is the resolution and bit depth of the desired decoded image.
Also note that your image is bigger than the display resolution of most Android devices. Depending on your use case, you may wish to consider widgets like this one that can load and display portions of the image at a time.
I’m building an image-intensive social app where images are sent from the server to the device. When the device has smaller screen resolutions, I need to resize the bitmaps, on device, to match their intended display sizes.
The problem is that using createScaledBitmap causes me to run into a lot of out-of-memory errors after resizing a horde of thumbnail images.
What’s the most memory efficient way to resize bitmaps on Android?
This answer is summarised from Loading large bitmaps Efficiently
which explains how to use inSampleSize to load a down-scaled bitmap
version.
In particular Pre-scaling bitmaps explains the details of various
methods, how to combine them, and which are the most memory efficient.
There are three dominant ways to resize a bitmap on Android which have different memory properties:
createScaledBitmap API
This API will take in an existing bitmap, and create a NEW bitmap with the exact dimensions you’ve selected.
On the plus side, you can get exactly the image size you’re looking for (regardless of how it looks). But the downside, is that this API requires an existing bitmap in order to work. Meaning the image would have to be loaded, decoded, and a bitmap created, before being able to create a new, smaller version. This is ideal in terms of getting your exact dimensions, but horrible in terms of additional memory overhead. As such, this is kind-of a deal breaker for most app developers who tend to be memory conscious
inSampleSize flag
BitmapFactory.Options has a property noted as inSampleSize that will resize your image while decoding it, to avoid the need to decode to a temporary bitmap. This integer value used here will load an image at a 1/x reduced size. For example, setting inSampleSize to 2 returns an image that’s half the size, and Setting it to 4 returns an image that’s 1/ 4th the size. Basically image sizes will always be some power-of-two smaller than your source size.
From a memory perspective, using inSampleSize is a really fast operation. Effectively, it will only decode every Xth pixel of your image into your resulting bitmap. There’s two main issues with inSampleSize though:
It doesn’t give you exact resolutions. It only decreases the size of your bitmap by some power of 2.
It doesn’t produce the best quality resize. Most resizing filters produce good looking images by reading blocks of pixels, and then weighting them to produce the resized pixel in question. inSampleSize avoids all this by just reading every few pixels. The result is quite performant, and low memory, but quality suffers.
If you're only dealing with shrinking your image by some pow2 size, and filtering isn't an issue, then you can't find a more memory efficient (or performance efficient) method than inSampleSize.
inScaled, inDensity, inTargetDensity flags
If you need to scale an image to a dimension that’s not equal to a power of two, then you’ll need the inScaled, inDensity and inTargetDensity flags of BitmapOptions. When inScaled flag has been set, the system will derive the scaling value to apply to your bitmap by dividing the inTargetDensity by the inDensity values.
mBitmapOptions.inScaled = true;
mBitmapOptions.inDensity = srcWidth;
mBitmapOptions.inTargetDensity = dstWidth;
// will load & resize the image to be 1/inSampleSize dimensions
mCurrentBitmap = BitmapFactory.decodeResources(getResources(),
mImageIDs, mBitmapOptions);
Using this method will re-size your image, and also apply a ‘resizing filter’ to it, that is, the end result will look better because some additional math has been taken into account during the resizing step. But be warned: that extra filter step, takes extra processing time, and can quickly add up for big images, resulting in slow resizes, and extra memory allocations for the filter itself.
It’s generally not a good idea to apply this technique to an image that’s significantly larger than your desired size, due to the extra filtering overhead.
Magic Combination
From a memory and performance perspective, you can combine these options for the best results. (setting the inSampleSize, inScaled, inDensity and inTargetDensity flags)
inSampleSize will first be applied to the image, getting it to the next power-of-two LARGER than your target size. Then, inDensity & inTargetDensity are used to scale the result to exact dimensions that you want, applying a filter operation to clean up the image.
Combining these two is a much faster operation, since the inSampleSize step will reduce the number of pixels that the resulting Density-based step will need to apply it’s resizing filter on.
mBitmapOptions.inScaled = true;
mBitmapOptions.inSampleSize = 4;
mBitmapOptions.inDensity = srcWidth;
mBitmapOptions.inTargetDensity = dstWidth * mBitmapOptions.inSampleSize;
// will load & resize the image to be 1/inSampleSize dimensions
mCurrentBitmap = BitmapFactory.decodeFile(fileName, mBitmapOptions);
If you're needing to fit an image to specific dimensions, and some nicer filtering, then this technique is the best bridge to getting the right size, but done in a fast, low-memory footprint operation.
Getting image dimensions
Getting the image size without decoding the whole image
In order to resize your bitmap, you’ll need to know the incoming dimensions. You can use the inJustDecodeBounds flag to help you get the dimensions of the image, w/o needing to actually decode the pixel data.
// Decode just the boundaries
mBitmapOptions.inJustDecodeBounds = true;
BitmapFactory.decodeFile(fileName, mBitmapOptions);
srcWidth = mBitmapOptions.outWidth;
srcHeight = mBitmapOptions.outHeight;
//now go resize the image to the size you want
You can use this flag to decode the size first, and then calculate the proper values for scaling to your target resolution.
As nice (and accurate) as this answer is, it's also very complicated. Rather than re-invent the wheel, consider libraries like Glide, Picasso, UIL, Ion, or any number of others that implement this complex and error prone logic for you.
Colt himself even recommends taking a look at Glide and Picasso in the Pre-scaling Bitmaps Performance Patterns Video.
By using libraries, you can get every bit of efficiency mentioned in Colt's answer, but with vastly simpler APIs that work consistently across every version of Android.
I have an application that displays lots of images, and images can be of varying size up to full screen dimensions of device. The images are all downloaded, and I use imagemagick to reduce colors of image to compress the file size, while keeping dimensions the same, to reduce download time. The reduced color space is fine for my application.
The problem is that when I load the image into a Bitmap in android the file size is much larger because of androids Bitmap config of ARGB_8888, I do need the ALPHA channel. Since ARGB_4444 is deprecated and had performance issues I am not using that. I am wondering if there is any other way to reduce the memory footprint of the loaded Bitmap while keeping the dimensions the same as the original?
---- Update ---
After discussions here and lots of other searching/reading it does not appear that there is a straight forward way do this. I could use ARGB_4444 which stores each pixel as 2 bytes, rather than 4 bytes per pixel. The problem there is that ARGB_4444 is deprecated, and I try not to use deprecated parts of the API for obvious reasons. Android recommends use of ARGB_8888 which is the default, since I need alpha I have to use that. Since there are many applications that do not need such a complex color space it would be nice to see ARGB_4444 or something similar become part of the supported API.
If you don't need alfa (transparency) you can use:
Bitmap.Config.RGB_565
which uses 2 bytes per pixel, instead of 4, reducing size by half.
You should also look at BitmapFactory.Options:
inSampleSize
inScaled
These values correctly set for your requirements, may have a very positive effect on Bitmap memory requirements.
Regards.
Read a similar answer I posted here
Basically, bitmap recycle is required for Pre honeycomb devices to reduce memory usage
I would be very grateful if someone could confirm that I have solved the below problem correctly or if there is an alternative solution?
I have an app that loads a large image (e.g. 800*1720 pixels) into memory and displays it in a scrollview. The image is a floor plans for a museum and I wanted a simple map like experience of scrolling and zooming. The image loaded fine on older devices, but caused an out of memory error on a Samsung Galaxy S3.
Looking at the LogCat messages it turned out that in creaing the bitmap 22MB was being allocated for the bitmap instead of 800*1720*4 = 5.5MB. Essentially 4x as much memory was being allocated as required by other devices and pushing the memory usage over the 50MB heap size.
The recommended solution to this problem is to use the BitmapFactory.Options.inSampleSize option to reduce the resolution of the image loaded and have it require less memory. However, this reduces the quality of the image, and I actually want to display it at it's full size in the original quality as works fine on older devices.
After much head scratching I concluded that the issue was that the pixel density on the S3's WXGA screens is 2.0 and thus for each pixel in the image, the bitmap was actually allocating 4 pixels. With a bit of trial and error I discovered I could prevent this happening by setting options.inScaled = false;
http://developer.android.com/reference/android/graphics/BitmapFactory.Options.html#inScaled
On the way, I also realised that I could cut my memory usage in half to 2.7MB by using a lower fidelity colour depth of 2 pixels instead of 4 pixels by setting options.inPreferredConfig = Bitmap.Config.RGB_565;. For my floorpans this didn't effect the visible image quality.
The final code was thus:
String uri = "drawable/floorplan";
int imageResource = getResources().getIdentifier(uri, null, getPackageName());
BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferredConfig = Bitmap.Config.RGB_565;
options.inScaled = false;
Bitmap bm = BitmapFactory.decodeResource(getResources(), imageResource, options);
IVfloorplan.setImageBitmap(bm);
When displaying this bitmap you need to scale it back up. To work out the scaling you can obtain the pixel density from:
float density = getResources().getDisplayMetrics().density;
I reduced memory usage for the bitmap from 22MB to 2.7MB, which in a 50MB heap is significant.
The screen of the S3 has a really high res, so it's quite understandable. Also, if the image is in the drawable folder, it could be getting upscaled automatically. There might be ways to turn that off. Even if your image size doesn't chance, the OS has to allocate a buffer to accommodate the image, also possibly one to accommodate showing it on the screen.
An alternative is using tiling, which is used in games like SNES games. This is also how they handled lots of graphics without running out of RAM. Evidence shows that this is how Google Maps has a map of Planet Earth. You basically chop up the large image into tiles and show the tiles on the screen as you are panned to them (of course, maybe 1 extra tile on each side).
Even though this is post-Honeycomb, where Google put in code to better manage Bitmap allocations, be careful with Bitmap allocation. It's more like a C program than a Java program, and it's a good idea to manage it like one. It's very easy to run out of heap space when using Bitmap objects in Android
What's the best image file format for Android in terms of memory? PNG is recommended for iOS as xCode does some magic with it.. Is it the same for Android?
I'm currently developing a big app with multiple animations going on (sliding in screens, fading etc etc). All works well so far! However I have noticed the view animation where the view contains an ImageView with a (quite large) PNG as the source is a bit laggy.
Obviously I can make the PNG smaller, but is there anything extra I can do to reduce the amount of memory the ImageView takes up/makes the animation smooth? I know PNG has a much larger file size than JPEG, but I can't see this being a problem, the JPEG or PNG (I assume) is eventually stored as an array of colours, so they would both take up the same memory. PNG is probably better for loading due to less cycles uncompressing. Again I only assume, my knowledge of image file formats is null.
Alternatively is there anything else causing the lag? Is the bitmap scaled to fit the view each onDraw() during the animation so should I scale the bitmap in code before giving it to the ImageView?
Thanks,
The formats supported by Android are: PNG, JPG and GIF (also 9.png).
The recomendated is PNG as said in dev guide
All of them are stored in memory as a Bitmap, so the most important thing is the color deph, like this:
BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferredConfig = Bitmap.Config.RGB_565;
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.icon, options);
More info: stackoverflow
and add (after honeycomb):
<application
android:largeHeap="true"
...
to your manifest file :=)
thanks to my dear friend :)