I am using an image of size 720X1136 as splash screen for my app targeted only for Samsung Galaxy Nexus phones. The actual file size is 513Kb(Found from Browser). When the activity calls the onCreate method and sets the content view the log mentions about a memory allocation of 13.5 MB.
This is the activity which I am loading with an image in background.
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical">
<ImageView android:src="#drawable/splashscreen"
android:id="#+id/splashscreen"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
/>
<WebView xmlns:android="http://schemas.android.com/apk/res/android"
android:visibility="invisible"
android:id="#+id/webview"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
/>
</RelativeLayout>
How can I reduce this memory allocation?
Thanks
Use #drawable/splashscreen this Image as 9-Patch image
then it will reduce memory allocation
Here are Step for sort out of your problem.
mRelativeLayout=(RelativeLayout)findViewById(R.id.relative_view);
intializeScreenSize();
mBitmap=decodeSampledBitmapFromResource(getResources(),R.drawable.splash_background,width,height);
mDrawable=new BitmapDrawable(getResources(), mBitmap);
mRelativeLayout.setBackgroundDrawable(mDrawable);
public static Bitmap decodeSampledBitmapFromResource(Resources res,
int resId, int reqWidth, int reqHeight) {
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(res, resId, options);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth,
reqHeight);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
return BitmapFactory.decodeResource(res, resId, options);
}
public void intializeScreenSize(){
DisplayMetrics displaymetrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(displaymetrics);
height= displaymetrics.heightPixels;
width= displaymetrics.widthPixels;
}
public static int calculateInSampleSize(BitmapFactory.Options options,
int reqWidth, int reqHeight) {
// Raw height and width of image
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
if (width > height) {
inSampleSize = Math.round((float) height / (float) reqHeight);
} else {
inSampleSize = Math.round((float) width / (float) reqWidth);
}
}
return inSampleSize;
}
Here are some link related to Load Bitmap in android.
SO Answer
Load Bitmap Efficiently
13mb is fine and within the limits. Whenever this splash screen is gone, the bitmap will be recycled and the memory will be repurposed to the rest of your app.
Tricks you could use to try to minimise this memory foot print is:
. use a 9 patch to stretch the borders of the image (if the design allows)
. lower the quality of the image (it will look crap)
As Alex Orlov stated: "the size on disk has nothing to do with the size of bitmap in memory. In the first case the image is compressed, bitmap, on the other hand, is just a raw set of pixels." However even considering it is stored as a raw image, 20MB is stil way too much. If we consider 4B per pixel, that would be 5Mpix image, but that one you have posted is definittely smaller.
I had similar problem: I was loading an image that has 8MB in raw format, however, 32MB were allocated for it. I later found out this was caused by dpi scaling. If you have your image in "drawable" folder, it will be automatically scaled according to current screen dpi. I have an XHDPI screen, so my image was scaled 2x horizontally and 2x vertically, that's why it took 4x more memory.
You can find more about how dpi works here: http://developer.android.com/guide/practices/screens_support.html
If you don't want to use this automatic android feature and scale images yourself, simply rename your "drawable" folder to "drawable-nodpi". All images in "drawable" folder tagged as "nodpi" will be loaded as they are.
nobody needs pixel perfect splash screen, you may easily reduce the image size to the half of the original, this will save you 3/4 of the memory footprint.
on another note, i'd recommend to avoid splash screens at all, here's a nice essay on this topic: "Splash screens are evil, don’t use them!", otherwise you may search google for "splash screens are evil" and get more arguments against using them.
Related
Following is the code from the official Android documentation.
https://developer.android.com/training/camera/photobasics
private void setPic() {
// Get the dimensions of the View
int targetW = imageView.getWidth();
int targetH = imageView.getHeight();
// Get the dimensions of the bitmap
BitmapFactory.Options bmOptions = new BitmapFactory.Options();
bmOptions.inJustDecodeBounds = true;
int photoW = bmOptions.outWidth;
int photoH = bmOptions.outHeight;
// Determine how much to scale down the image
int scaleFactor = Math.min(photoW/targetW, photoH/targetH);
// Decode the image file into a Bitmap sized to fill the View
bmOptions.inJustDecodeBounds = false;
bmOptions.inSampleSize = scaleFactor;
bmOptions.inPurgeable = true;
Bitmap bitmap = BitmapFactory.decodeFile(currentPhotoPath, bmOptions);
imageView.setImageBitmap(bitmap);
}
I am not able to understand the use of Math.min function here. So what Math.min is doing, is selecting the smallest ratio between photoW/targetW and photoH/targetH.
Now, let us suppose there is an image whose width is two times larger than that of the imageView and its height is four times larger than that of the imageView. So, the ratios come to be two and four and the Math.min function will give us two in the scaleFactor.
And what bmOptions.inSampleSize does is
https://developer.android.com/reference/android/graphics/BitmapFactory.Options#inSampleSize
If set to a value > 1, requests the decoder to subsample the original
image, returning a smaller image to save memory. The sample size is
the number of pixels in either dimension that corresponds to a single
pixel in the decoded bitmap. For example, inSampleSize == 4 returns an
image that is 1/4 the width/height of the original
According to this, our image will get half it's size whose height will still be two times bigger than that of the imageView.
Shouldn't we use Math.max or am I missing something?
In the process of setting a background image to an Android View (API 22) I am facing some problems.
My image is a 1MB .jpeg file, 4000x2672.
At first I tried via the
android:background="#drawable/your_image"
method, getting an D/skia﹕ --- allocation failed for scaled bitmap error.
Then I followed http://developer.android.com/training/displaying-bitmaps/load-bitmap.html and implemented it as follows:
BitmapDecoder.java
(..omissis..)
public class BitmapDecoder {
public static int calculateInSampleSize(
BitmapFactory.Options options, int reqWidth, int reqHeight) {
// Raw height and width of image
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
final int halfHeight = height / 2;
final int halfWidth = width / 2;
// Calculate the largest inSampleSize value that is a power of 2 and keeps both
// height and width larger than the requested height and width.
while ((halfHeight / inSampleSize) > reqHeight
&& (halfWidth / inSampleSize) > reqWidth) {
inSampleSize *= 2;
}
}
return inSampleSize;
}
public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,
int reqWidth, int reqHeight) {
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(res, resId, options);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
return BitmapFactory.decodeResource(res, resId, options);
}
}
SplasActivity.java (the main activity)
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_splash);
LinearLayout mL = (LinearLayout)findViewById(R.id.MainLayout);
Display display = getWindowManager().getDefaultDisplay();
Point size = new Point();
display.getSize(size);
int width = size.x;
int height = size.y;
if (Build.VERSION.SDK_INT >= 16) {
Drawable d = new BitmapDrawable(getResources(),
BitmapDecoder.decodeSampledBitmapFromResource(getResources(),
R.drawable.andromeda,
1080,1920));
mL.setBackground(d);
}
this.getSupportActionBar().hide();
}
I am getting WxH of the Device's screen, resizing the Drawable resource to that size, converting the resulting Bitmap to a Drawable, setting the Drawable as the View background.
But I always get the
D/skia﹕ --- allocation failed for scaled bitmap
...
java.lang.OutOfMemoryError: Failed to allocate a 684032012 byte allocation with 16777120 free bytes and 227MB until OOM
error.
I have been searching for hours now and implemented all suggested techniques with no luck.
As a side note, if I put as width and height a very small value (e.g. 400x200), it works. But, as you may imagine, that is not feasible.
Any idea on how to solve?
judging from your numbers you have placed your jpeg in res/drawable folder which is the same as res/drawable-mdpi, the remedy is to place it in res/drawable-nodpi folder
I think the problem is that you evaluate the image size in its compressed jpeg format (1MB) but when you load it and manipulate into your software it's unpacked in it's bitmap form.
As reported in the link you mentioned:
"... The lower resolution version should match the size of the UI component that displays it. An image with a higher resolution does not provide any visible benefit, but still takes up precious memory and incurs additional performance overhead due to additional on the fly scaling.
.."
You are using a 4000x2672= 10688000 pixels image, and each pixel requires a number of bytes to encode color, thus amounting the 684032012 allocated bytes.
The only suggestion I can give is to evaluate what is the bigger screen you want to show your image on, check it's definition and rescale your image so to match at most that value.
In my app i'm iterating through URLs of images, decoding and putting them in an ArrayList<Bitmap>.
They may vary greatly in size, so i'm doing a "pre-decode" with the inJustDecodeBounds = true option to calculate the necessary inSampleSize value for the actual decode.
See my method for this below, i hope it's not too hard to understand. Basically i'm aiming for a size similar to the screen size of the device.
for (Element e: posts) {
if (!e.id().equals("")) {
//preparing decode
options = new BitmapFactory.Options();
input = new URL(e.url).openStream();
options.inJustDecodeBounds = true;
BitmapFactory.decodeStream(input, null, options);
input.close();
//setting inSampleSize if necessary
int picPixels = options.outWidth * options.outHeight;
int picScreenRatio = picPixels / screenPixels;
if (picScreenRatio > 1) {
int sampleSize = picScreenRatio % 2 == 0 ? picScreenRatio : picScreenRatio + 1;
options.inSampleSize = sampleSize;
}
//actual decode
input = new URL(e.url).openStream();
options.inJustDecodeBounds = false;
Bitmap pic = BitmapFactory.decodeStream(input, null, options);
input.close();
picList.add(pic);
}
}
Code for calculating screenPixels:
Display display = getWindowManager().getDefaultDisplay();
Point screenSize = new Point();
display.getSize(screenSize);
int screenPixels = screenSize.x * screenSize.y;
I'm going through ~60 images and around 40 my app crashes with java.lang.OutOfMemoryError.
As i understand, with inJustDecodeBounds = true there's no memory allocated, and if my method is correct (i believe it is), very big images get very big inSampleSizes, so i don't know what could be the problem.
Would appreciate any advice.
No matter that you use inSampleSize you can't easily hold 60 bitmaps as large as your screen in memory.
Assuming a fullhd screen you get about 1920*1080*60*4 bytes.
Thats roughly 500 MB of data.
You need to somehow change your bitmap loading logic.
In this answer you can find out how much memory you can count with. It differs for various devices.
But always try to make you app as memory efficient as possible.
Sample size must be a power of two. From the documentation:
If set to a value > 1, requests the decoder to subsample the original
image, returning a smaller image to save memory. The sample size is
the number of pixels in either dimension that correspond to a single
pixel in the decoded bitmap. For example, inSampleSize == 4 returns an
image that is 1/4 the width/height of the original, and 1/16 the
number of pixels. Any value <= 1 is treated the same as 1. Note: the
decoder uses a final value based on powers of 2, any other value will
be rounded down to the nearest power of 2.
Here's a good trick from the Android - Volley library on how to find the best sample size for bitmaps:
int findBestSampleSize(int actualWidth, int actualHeight, int desiredWidth, int desiredHeight) {
double wr = (double) actualWidth / desiredWidth;
double hr = (double) actualHeight / desiredHeight;
double ratio = Math.min(wr, hr);
float n = 1.0f;
while ((n * 2) <= ratio) {
n *= 2;
}
return (int) n;
}
It is notoriously hard to get image manipulation right on Android. I would recommend that you use an established Image Management Library like Facebook's Fresco:
https://github.com/facebook/fresco
An example from their docs:
Fresco's Drawees show a placeholder for you until the image has loaded and automatically show to the image when it arrives. When the image goes off-screen, it automatically releases its memory.
Also:
Apps using Fresco can run even on low-end devices without having to constantly struggle to keep their image memory footprint under control.
i had a problem with load images as bitmap , therefore i use the method:
"decodeSampledBitmapFromFile"
(the implementation is included)
and i also saved all the bitmaps on the SdCard and every time that i need the Bitmap i load it from the Path which it's stored with the parameters:
decodeSampledBitmapFromFile(path,150,100);
Bitmap image_profile =decodeSampledBitmapFromFile(path,150,100);
and set the image Bitmap into the imageView that i need (every time that i need a image i load it from the sdCard.
however i still get an OutOfMemoryException after load about 20 images.
So, what is the solution for the OutOfMemoryException?
why even after load small number of images (about 20) i get an OutOfMemoryException?
what is the secret of application like facebook, instagram or youtube that they suceesseed
to load huge number of images without Exception?
i tried everything however i still get Exception.
anyOne has further suggestions what can i implement in order to avoid this exception?
thanks alot
public static Bitmap decodeSampledBitmapFromFile(String path, int reqWidth, int reqHeight)
{ // BEST QUALITY MATCH
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(path, options);
// Calculate inSampleSize
// Raw height and width of image
final int height = options.outHeight;
final int width = options.outWidth;
options.inPreferredConfig = Bitmap.Config.RGB_565;
int inSampleSize = 1;
if (height > reqHeight) {
inSampleSize = Math.round((float)height / (float)reqHeight);
}
int expectedWidth = width / inSampleSize;
if (expectedWidth > reqWidth) {
//if(Math.round((float)width / (float)reqWidth) > inSampleSize) // If bigger SampSize..
inSampleSize = Math.round((float)width / (float)reqWidth);
}
options.inSampleSize = inSampleSize;
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
return BitmapFactory.decodeFile(path, options);
}
You're probably loading the images and then they're not getting recycled or collected by the garbage collector so they're still taking up memory.
Since it sounds like you're only using one image at a time, I guess you could try to manually get rid of the references to the Bitmap that you don't need anymore and then call System.gc() to free up the memory?
If not, I would look into an LruCache.
Google has a good tutorial on it: http://developer.android.com/reference/android/util/LruCache.html
This tutorial also helped me when I first used an LruCache for my game:
http://andrewbrobinson.com/2012/03/05/image-caching-in-android/
I tried using:
//decodes image and scales it to reduce memory consumption
private Bitmap decodeFile(File f)
{
try {
//Decode image size
BitmapFactory.Options o = new BitmapFactory.Options();
o.inJustDecodeBounds = true;
BitmapFactory.decodeStream(new FileInputStream(f),null,o);
//The new size we want to scale to
final int REQUIRED_SIZE=70;
//Find the correct scale value. It should be the power of 2.
int scale=1;
while(o.outWidth/scale/2>=REQUIRED_SIZE && o.outHeight/scale/2>=REQUIRED_SIZE)
scale*=2;
//Decode with inSampleSize
BitmapFactory.Options o2 = new BitmapFactory.Options();
o2.inSampleSize=scale;
return BitmapFactory.decodeStream(new FileInputStream(f), null, o2);
} catch (FileNotFoundException e) {}
return null;
}
then
R.drawable.image = decodeFile(R.drawable.image);
How to use this method? R.drawable.image is integer, so it gives me error, how can I decode file? I add 240kb image to textview.
Well you've said yourself, R.drawable.image is an integer - but you're trying to pass it to a method accepting a File, and then assign the return value (a Bitmap) back to it.
Additionally, catching FileNotFoundException like that and just swallowing it is a really bad idea - why don't you just declare that the method might throw the exception?
It's not clear what you're really trying to do - or what R.drawable.image is really meant to achieve - but in order to use the method you've got, you clearly need a File.
Are you trying to convert a drawable to a File and then that File to a Bitmap?
This will allow you to decode a drawable right to a Bitmap:
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.image );
I think you may be looking for something like this:
Resources res = getResources();
Drawable image = res.getDrawable(R.drawable.image);
Android allows you to access the images in your drawable folders using the Resources class. They also handle the scaling for you by using different folders for different resolution images. Additionally, if you have a widget defined in xml, you can assign it a source drawable that way using the android:src attribute, in your case it would be android:src="#drawable/image". Take a look at
http://developer.android.com/guide/topics/resources/drawable-resource.html
and
http://developer.android.com/training/multiscreen/screensizes.html
for more information about how you can handle images with Android and how the OS helps you choose a properly scaled image, respectively.
Edit: One more thing, if you're just trying to set the image resource for something like an ImageView, you can use imageView.setImageResource(R.drawable.image) so that you don't have to create a Drawable or Resources object in your code. If you need to do something to the image before you set it though, there are corresponding setImageDrawable and setImageBitmap methods.
VM wont let us allocate 6291456 bytes
Your image is about 6 Megabyte uncompressed, see http://developer.android.com/training/displaying-bitmaps/index.html
And if you want to load a downscaled version of that image which might be required since you seem to be low on memory it would work with about the following code
/** decodes image from resources to max maxSize x maxSize pixels */
private Bitmap decodeFile(Context context, int resId, int maxSize) {
Resources res = context.getResources();
// Decode image size
BitmapFactory.Options o = new BitmapFactory.Options();
o.inJustDecodeBounds = true;
BitmapFactory.decodeResource(res, resId, o);
// Find the correct scale value. It should be the power of 2.
int scale = 1;
while (o.outWidth / scale / 2 >= maxSize && o.outHeight / scale / 2 >= maxSize)
scale *= 2;
// Decode with inSampleSize
o.inJustDecodeBounds = false;
o.inSampleSize = scale;
return BitmapFactory.decodeResource(res, resId, o);
}