I am developing an application for the Galaxy S4.
One of the requirements of the application is having a SplashScreen containing an Image of 1920x1080 pixels. Its a high quality .jpeg image and the size of the Image is about 2 Megabytes.
The problem is that I am getting an OutOfMemoryError as soon as I start the Application. I am quite stunned that this already happens with an image of just 2 megabyte in size? How can I fix this problem and display the image?
Changing the dimensions or the size of the image is not an option.
SplashScreen.java
public class Splashscreen extends Activity {
private static final int SPLASH_DURATION = 2000;
private boolean isBackPressed = false;
#Override
protected void onCreate(Bundle savedInstanceState) {
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(LayoutParams.FLAG_FULLSCREEN, LayoutParams.FLAG_FULLSCREEN);
super.onCreate(savedInstanceState);
setContentView(R.layout.splashscreen);
Handler h = new Handler();
h.postDelayed(new Runnable() {
#Override
public void run() {
// check if the backbutton has been pressed within the splash_duration
if(!isBackPressed) {
Intent i = new Intent(Splashscreen.this, MainActivity.class);
i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
Splashscreen.this.startActivity(i);
overridePendingTransition(R.anim.short_fade_in, R.anim.short_fade_out);
}
finish();
}
}, SPLASH_DURATION);
}
#Override
public void onBackPressed() {
isBackPressed = true;
super.onBackPressed();
}
}
And the splashscreen.xml
<ImageView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/ivSplashScreenImage"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="fitXY"
android:src="#drawable/high_res_splashscreen_image"/>
ADDITIONAL INFORMATION:
Sometimes, (when a lot of device memory is available) the app is able to make it past the splash screen, but then, the memory consumption of the app is just insane. (around 100 megabyte). Even though I close the SplashScreen Activity and finish() it, it seems that there is a reference to the ImageView / the Image kept in memory.
How can I reduce the huge memory consumption?
When I do not display the splash screen, my app only consumes around 35MB
of memory. With the SplashScreen image, its around 100MB.
Three hints which should help you:
Use this to load your images, from Loading Large Bitmaps Android documentation:
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 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) {
// Calculate ratios of height and width to requested height and width
final int heightRatio = Math.round((float) height / (float) reqHeight);
final int widthRatio = Math.round((float) width / (float) reqWidth);
// Choose the smallest ratio as inSampleSize value, this will guarantee
// a final image with both dimensions larger than or equal to the
// requested height and width.
inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
}
return inSampleSize;
}
Make sure you have only one instance of your Bitmap in memory. After displaying it, call recycle() and set your reference to null. You can use Memory Allocation Tracker to see what is allocated. You can also read HPROF files, as suggested in comments.
By default ARGB_8888 pixel format is used, which means 4 bytes per pixel. Very good article: Bitmap quality, banding and dithering. Your image is JPEG, so it doesn't have transparency, so you are wasting 1 byte on every pixel for alpha channel. It's not very probable, but maybe with acceptable quality you can use even more economical format. Take a look at them. Maybe RGB_565 for example. It takes 2 bytes for pixel, so your image would be 50% lighter. You can enable dithering to improve the quality of RGB_565.
I had a similar problem and the solution is simple. Put your high resolution 1920x1080 px image in the drawable-nodpi directory. The system does not scale resources tagged with this qualifier regardless of the current screen's density, which leads to the OutOfMemoryError, so the problem should disappear.
Please check Android documentation: http://developer.android.com/intl/es/guide/practices/screens_support.html
Hope it helps.
Related
I have bigs problems with the decodeSampleBitmap code and the final size of my images. I have my drawables in nodpi drawables folder for not multiply the same drawable in diferents folders with diferents sizes, and i resize after aply my decodeSampleBitmap.
I have this code for load a bitmap:
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);
}
The code i can get at the documentation. Ok i think this is for load images more eficiently but i have memory problems. I try explain this problem.
1-I want load a png, 2037x1440 pixels, 12.6kb.
2-I aply my code and load a png with 2037x1440 pixels and 11,73 MB. I dont undurstand what i'm doing wrong. Why android multiply for 1000 the size of my drawables?
10-12 12:06:07.842 12405-12822/com.viridesoft.kiseichuu V/control4: width=2037 height=1440 size=11733120
Edit: if i load the drawable with BitmapFactory.decodeResource the size is the same, 11733120.
The old question should be changed. I understand i cant reduce more my image size and i continue with the same problem.
I load a lot of images for create a dinamical background and charge all the characters(my player and zombies), in total until 111 bitmaps of 25%px x 10%px of the screen size and until 30 bitmaps of the 100% screen size. I load all this bitmaps in a loader activity and save for the later use in the game. I have a good quality of images and a drawable-nodpi folder with a lot of images for the best android resolution i see. My game run fluid in some devices (bq aquaris, nexus 5, etc) but is continually cleaning the memory and run slow in others (some tablets, some mid-range devices, etc )
The new question is:
How i can load eficiently a lot of images for a fluid game experiencie?
I am integrating a city map in one of my app's activity . But due to its large dimensions(2000*2000) it is causing map to load slowly when app starts and when i zoom in/out it causes a delay of about 1-2sec and its not as responsive as i want . I reffered to this scale-imageview ,it is working fine with images of small dimensions (eg 500*500) .How can i make zoom in/out more responsive with images of large dimensions?
I think it was happen because the image is too big and need big memory allocation for render that image.
there is technique to loading large bitmaps efficiently from android developer site. here the link:
http://developer.android.com/training/displaying-bitmaps/load-bitmap.html
so, to load bitmap more efficient you have to make it loaded with your limited height and weight. You need to use this function :
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;
}
and before you set image bitmap, you have to set some options of BitmapFactory options like limiting height and width of bitmap image. In this example you will limiting max-width to 1024px and max-height to 768px. So, Here the code :
// create new BitmapFactory.Options
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(imagePath, options);
// calculate inSampleSize, 1024 is your request-width, 768 is your request-height
options.inSampleSize = calculateInSampleSize(options, 1024, 768);
// decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
Bitmap imgBitmap = BitmapFactory.decodeFile(imagePath, options);
// set image bitmap after alter bitmap height and width
imgDetail.setImageBitmap(imgBitmap);
I hope it can help you. :)
I have used Chris Banes PhotoView: https://github.com/chrisbanes/PhotoView
It works perfectly fine for me for images of size 1024x1024px
Also this has some nice features:
Out of the box zooming, using multi-touch and double-tap.
Scrolling, with smooth scrolling fling.
Works perfectly when using used in a scrolling parent (such as ViewPager).
Allows the application to be notified when the displayed Matrix has changed. Useful for when you need to update your UI based on the current zoom/scroll position.
Allows the application to be notified when the user taps on the Photo.
Usage is very simple:
// Any implementation of ImageView can be used!
mImageView = (ImageView) findViewById(R.id.iv_photo);
// Set the Drawable displayed
Drawable bitmap = getResources().getDrawable(R.drawable.wallpaper);
mImageView.setImageDrawable(bitmap);
// Attach a PhotoViewAttacher, which takes care of all of the zooming functionality.
mAttacher = new PhotoViewAttacher(mImageView);
I am trying to prevent OutOfMemoryError in my android app. I have read many post but I cannot solve it yet.
The app has activities with background so I think this is the main problem. OutOfMemoryError only occurs in some devices (maybe due to VM heap) and I need to be sure that this error won't produce a crash in any device.
I have recently read about MAT (Memory Analytics plugin), and I have executed it during the app runtime, here you can see the result:
dominator_tree
report
In this activity I have a background for each orientation (home, home_land). Both sizes are the same (190kb, jpg). When I created the HPROF file the activity was in landscape orientation and I hadn't ran the portrait orientation before. What conclusion can I extract about this result in order to get my purpose?
I can add more information if it is necessary
EDIT
I tried to use the method of this page in order to avoid OutOfMemoryError too, but I couldn't get it. This was my code:
decodeFromResource class
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
public class decodeFromResource {
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);
}
public static Drawable getDecodedDrawableFromResource(Resources res, int resId,
int reqWidth, int reqHeight){
return new BitmapDrawable(res, decodeSampledBitmapFromResource(res, resId, reqWidth, reqHeight));
}
}
onCreate method from the main activity
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.home);
resources = getResources();
DisplayMetrics metrics = resources.getDisplayMetrics();
layoutHome = (LinearLayout) findViewById(R.id.home_layout);
if (resources.getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) {
layoutHome.setBackgroundDrawable(decodeFromResource
.getDecodedDrawableFromResource(resources, R.drawable.home,
metrics.widthPixels, metrics.heightPixels));
} else {
layoutHome.setBackgroundDrawable(decodeFromResource
.getDecodedDrawableFromResource(resources,
R.drawable.home_land, metrics.heightPixels,
metrics.widthPixels));
}
I had implemented the "Loading Large Bitmaps Efficiently" method only for the background, because apart from this I have only five small buttons with very small size. Should I also need to implement the method for them? Can you see any errors?
You are probably loading your jpg files as is, which can easily lead to OutOfMemory even on strong devices.
Keep in mind that an image loaded into memory with no compression and that on most devices a single pixel is represented by 4 memory bytes. A 7 MPixel image, for example, will require mem block of 28 MByte which may bring your app real close to OutOfMemory crash.
The solution is simple: always load a scaled-down image, according to your app's needs.
To do this start by reading your image size:
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(getResources(), R.id.myimage, options);
int imageHeight = options.outHeight;
int imageWidth = options.outWidth;
String imageType = options.outMimeType;
The code above will NOT load the actual image file. it will only interrogate it
for its dimension.
Once you have the deminsion you can calculate the 'sample size' to be used for
loading the scaled image. A sample size of N will result in loading 1/(N*N) of the
orig image pixels, e.g. for sample size of 4 only 1/16 of the image pixels will be loaded.
And finally load the scaled down image:
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(res, resId, options);
options.inSampleSize = mySampleSize; //<-----------------------
options.inJustDecodeBounds = false;
bitmap = BitmapFactory.decodeResource(res, resId, options);
Even when doing a scaled-down load it is a good idea to protect your code with
a try {...} catch(OutOfmemory) clause and allow for a graceful handling of load failure.
More details here: http://developer.android.com/training/displaying-bitmaps/load-bitmap.html
Try using android:largeHeap="true" tag in your AndroidManifest.xml This should make sure that android will handle larger bitmaps.
EDIT
I have to point that this is not ultimate solution to your problem, it will not prevent OutOfMemory exception but they will be less likely to appear. Probably Gilad Haimov posted right solution to this problem
options.inPurgeable = true;
options.inInputShareable = true;
these flags allow the actual bits associated with a Bitmap object to be discarded under memory pressure and transparently re-decoded if it turns out you're still using the Bitmap.
Since bitmaps are decompressed, 190kb in storage doesn't really help, did you simply load up the bitmap with little regard of manipulating the parameters fitting memory paging requirement? About up to screen-resolution image can load straight up with no regard of memory.
I think when you used DecodeResource such as Bitmap ,you'd better use GC artificially,if you don't do that ,it maybe OOM.
Finally, after I have read many post, my last comment in #j__m answer was correct. The problem was drawable folders.
I found this: https://stackoverflow.com/a/19196749/2528167
"Option #1: Just ship the -xxhdpi drawables and let Android downsample them for you at runtime (downside: will only work on fairly recent devices, where -xxhdpi is known)."
I had all my pictures in a xxhdpi folder in order to let Android downsample them at runtime, but as CommonsWare said this only work on recent devices, so I have filled drawable-**dpi folders and now OutOfMemoryError doesn't appear.
I'm working on an android app, and in resources folder I have an image which is 8000x400px resolution. It is a .png that I'm using at my Sprite class to simulate the movement of an animal.
I display the portion of the png using drawBitmap() in my SurfaceView class.
Sprite class, SurfaceView and all the elements work perfect, but when working with that large images it doesnt display anything.
To fix this problem I would like to know.
What is the limit of maximum resolution of a Bitmap allowed in
Android?
How could I display in onDraw() a Sprite with that size?
Concerning the displaying / loading of Bitmaps:
You need to load the Bitmap properly and adjust the size of the Bitmap to your needs. In most cases, it makes no sense to load a Bitmap with higher resolution than the screen of the device supports.
Furthermore, this practice is very important to avoid OutOfMemoryErrors when working with Bitmaps that large.
As an example, a Bitmap with the size of 8000 x 4000 uses more than 100 Megabytes of RAM (in 32 Bit color), which is an enormous amount for a mobile device and much more than even high end devices are capable of handling.
This is how to load a Bitmap properly:
public abstract class BitmapResLoader {
public static Bitmap decodeBitmapFromResource(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);
}
private 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) {
// Calculate ratios of height and width to requested height and width
final int heightRatio = Math.round((float) height / (float) reqHeight);
final int widthRatio = Math.round((float) width / (float) reqWidth);
// Choose the smallest ratio as inSampleSize value, this will guarantee
// a final image with both dimensions larger than or equal to the
// requested height and width.
inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
}
return inSampleSize;
}
}
Example usage in code:
Bitmap b = BitmapResLoader.decodeBitmapFromResource(getResources(),
R.drawable.mybitmap, 500, 500);
Taken from the Google Android Developer guidelines here:
http://developer.android.com/training/displaying-bitmaps/load-bitmap.html
Concerning the maximum Bitmap size:
The maximum Bitmap size limit depends on the unterlying OpenGL implementation. When using OpenGL, this can be tested via (source: Android : Maximum allowed width & height of bitmap):
int[] maxSize = new int[1];
gl.glGetIntegerv(GL10.GL_MAX_TEXTURE_SIZE, maxSize, 0);
e.g. for the Galaxy S2, it is 2048x2048.
This question already has answers here:
Android: BitmapFactory.decodeStream() out of memory with a 400KB file with 2MB free heap
(8 answers)
Closed 9 years ago.
I'm having an OutOfMemoryError in my VSD220 (It's a 22" Android based All in one)
for (ImageView img : listImages) {
System.gc();
Bitmap myBitmap = BitmapFactory.decodeFile(path);
img.setImageBitmap(myBitmap);
img.setOnClickListener(this);
}
I really don't know what to do, because this image is below the maximum resolution. The image size is something about (1000x1000), and the display it's 1920x1080.
Any help?
(That foreach cycle is for about 20 elements, it gots broken after 6, or 7 loops..)
Thanks a lot.
Ezequiel.
You should take a look at the training docs for Managing Bitmap Memory. Depending on your OS version, you could use different techniques to allow you to manage more Bitmaps, but you'll probably have to change your code anyway.
In particular, you're probably going to have to use an amended version of the code in "Load a Scaled Down Version into Memory", but I at least have found this section to be particularly useful:
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) {
// Calculate ratios of height and width to requested height and width
final int heightRatio = Math.round((float) height / (float) reqHeight);
final int widthRatio = Math.round((float) width / (float) reqWidth);
// Choose the smallest ratio as inSampleSize value, this will guarantee
// a final image with both dimensions larger than or equal to the
// requested height and width.
inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
}
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);
}
This method makes it easy to load a bitmap of arbitrarily large size
into an ImageView that displays a 100x100 pixel thumbnail, as shown in
the following example code:
mImageView.setImageBitmap(
decodeSampledBitmapFromResource(getResources(), R.id.myimage, 100, 100));
Are you really sure you want to load the same Bitmap 20 times? Don't you want to load it once and set it inside the loop.
Still, loading a 1000x1000 pixel image is not guaranteed to work, regardless of screen resolution. Remember that a 1000x1000 pixel image takes up 1000x1000x4 bytes =~4MB (if you load it as ARGB_8888). If your heap memory is fragmented/too small you may not have enough space to load the bitmap. You may want to look into the BitmapFactory.Options class and experiment with inPreferredConfig and inSampleSize
I would suggest that you either use the suggestion by DigCamara and decide on a size and load a downsampled image of nearly that size (I say nearly because you won't get the exact size using that technique) or that you try to load the full size image and then recursively increase the sample size (by factors of two for best result) until you either reach a max sample size or the image is loaded:
/**
* Load a bitmap from a stream using a specific pixel configuration. If the image is too
* large (ie causes an OutOfMemoryError situation) the method will iteratively try to
* increase sample size up to a defined maximum sample size. The sample size will be doubled
* each try since this it is recommended that the sample size should be a factor of two
*/
public Bitmap getAsBitmap(InputStream in, BitmapFactory.Config config, int maxDownsampling) {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 1;
options.inPreferredConfig = config;
Bitmap bitmap = null;
// repeatedly try to the load the bitmap until successful or until max downsampling has been reached
while(bitmap == null && options.inSampleSize <= maxDownsampling) {
try {
bitmap = BitmapFactory.decodeStream(in, null, options);
if(bitmap == null) {
// not sure if there's a point in continuing, might be better to exit early
options.inSampleSize *= 2;
}
}
catch(Exception e) {
// exit early if we catch an exception, for instance an IOException
break;
}
catch(OutOfMemoryError error) {
// double the sample size, thus reducing the memory needed by 50%
options.inSampleSize *= 2;
}
}
return bitmap;
}