In the "drawable" folder I have four background images of 1024 x 768 or something.
However, it turns out to be that I have to use Bitmap.createScaledBitmap() and my Android device is 2560 x 1504. Which means a Bitmap with the size of 2560 x 1504 is required to fill the whole screen with a background, it is really memory-consuming because it is a Bitmap of impressive size.
For some reason, I need to cache 4 bitmaps like this so my program can instantly switch to a new background when I press a button. Which means I am facing huge memory problems now. Is there any way to optimize it?
P.S: I am using ImageView class to display images.
The images files in the "drawable" folder are not BMP files.
Related
I decided to investigate my app's memory usage and I looked at Android Studio's Memory Monitor and my memory usage was around 68 MB. It looks too high to me.
I opened memory allocator and started tracking from the beginning of the application. I saw that there is a 49 MB allocation of a NonMovableArray, which is a Bitmap.
I debugged the application and found out that it was the background I was using. The lines below are from PhoneWindow.java file, that's where Android assigns the background to the screen I believe. The background object had a size of 49 MB and 2625x4669 resolution.
I have no overdraw in my app, and I have a single background that is applied to entire theme.
I have a background drawable in drawable folder in JPG format with 750x1,334 resolution.
PhoneWindow.java
if (mBackgroundResource != 0) {
background = getContext().getDrawable(mBackgroundResource);
} else {
background = mBackgroundDrawable;
}
I am testing this on a Motorola Nexus 6 device which has 560 density with a resolution of 1440 x 2560.
There are two points I don't understand.
If the device has a resolution of 1440x2560, why would my background get converted to 2625x4669?
Even if this conversion is the best scenario for the app, how come a 160 KB file would end up being 49 MB?
If you guys can explain this to me that would be great. Thanks!
If the device has a resolution of 1440x2560, why would my background get converted to 2625x4669?
You put the image in res/drawable/. This is not a good choice, as that is a synonym for res/drawable-mdpi/. Hence, Android is resampling your image, thinking that you were aiming for -mdpi devices (~160dpi), so the image is about the same physical size on the Nexus 6 (3.5x the density).
Whether res/drawable-nodpi/ or res/drawable-anydpi/ is the right choice depends a bit on your alternative versions of this resource, though either will probably work.
how come a 160 KB file would end up being 49 MB?
The image takes up 160KB on disk. The memory footprint is the decoded image. That will be the image resolution (post-resampling) times 4 bytes/pixel for ARGB_8888 images. 2625x4669x4 ~= 49MB.
The default and highest quality configuration for a Bitmap is ARGB_8888, which will use 4 bytes per pixel. Since your image is being scaled to 2625*4669, that's:
2625*4669*4 bytes = 49024500 bytes
http://developer.android.com/reference/android/graphics/Bitmap.Config.html
I'm not sure about the resolution differences, but the difference between the used memory and the size of the file is because an image in the JPG format (as many other formats) is compressed (see the wiki for how). When you then load it into memory, it is no longer compressed, as it's used to draw every single pixel on the screen.
I have basic Android application created with Android Studio with single image loaded in ImageView like this:
ImageView iv = (ImageView)findViewById(R.id.imageView);
iv.setScaleType(ImageView.ScaleType.CENTER_CROP);
iv.setImageResource(R.drawable.g01);
Image size is 1280x853 pix, nothing huge. When I store the image in drawable folder runtime memory consumed by app is 24.35 MB vs 11.85 MB when image is stored in drawable-nodpi folder. Tested on xhdpi device with resolution of 1280x720 pix.
In my real life app, difference is even greater 75 MB vs 25 MB.
Why the difference? I thought that drawable and drawable-nodpi folders are basically the same and serve the same purpose?
Why the difference? I thought that drawable and drawable-nodpi folders
are basically the same and serve the same purpose?
No they are not. drawable scales its content per the device's density, drawable-nodpi doesn't. In the first case you will get a bitmap density time width/height the original one
I'd like to implement something like a slideshow on AndEngine. I need to show a big images(2000x800px). I'd like to have near 10 images but only 1 image can be displayed on the screen in one moment of time.
The main issue is memory management. I don't want to load all images in memory at once. I think the best idea is to load one image after the other(when the image is needed).
How to correctly load images in this way ? Also, how to correctly unload previous image from memory ?
I create 14 Bitmap objects like this:
bmp[0]=BitmapFactory.decodeResource(getResources(), R.drawable.a0000);
bmp[1]=BitmapFactory.decodeResource(getResources(), R.drawable.a0001);
bmp[2]=BitmapFactory.decodeResource(getResources(), R.drawable.a0002);
bmp[3]=BitmapFactory.decodeResource(getResources(), R.drawable.a0003);
bmp[4]=BitmapFactory.decodeResource(getResources(), R.drawable.a0004);
bmp[5]=BitmapFactory.decodeResource(getResources(), R.drawable.a0005);
bmp[6]=BitmapFactory.decodeResource(getResources(), R.drawable.a0006);
bmp[7]=BitmapFactory.decodeResource(getResources(), R.drawable.a0007);
bmp[8]=BitmapFactory.decodeResource(getResources(), R.drawable.a0008);
bmp[9]=BitmapFactory.decodeResource(getResources(), R.drawable.a0009);
bmp[10]=BitmapFactory.decodeResource(getResources(), R.drawable.a0010);
bmp[11]=BitmapFactory.decodeResource(getResources(), R.drawable.a0011);
bmp[12]=BitmapFactory.decodeResource(getResources(), R.drawable.a0012);
bmp[13]=BitmapFactory.decodeResource(getResources(), R.drawable.a0013);
those Bitmaps are used to make an animation of the background, the resources are 14 jpj images of 320x480 pixels. so I created a custom view and then call the bitmaps at the onDraw method. Then I test the app on my phone that have the same resolution of my images, it run smoothly, then I try to use the app on a tablet with a larger resolution(like 1280x720), but my background don't fit the entire screen, so I investigate how fill the entire screen by make a new bitmap with different resolution based on the device resolution and the result was this:
newWidth= context.getResources().getDisplayMetrics().widthPixels;
newHeight= context.getResources().getDisplayMetrics().heightPixels;
int contador=0;
do{
bmp[contador]= Bitmap.createScaledBitmap(bmp[contador], newWidth, newHeight,true);
contador++;
}while(contador<13);
Then I simply put these line on my onDraw method.
canvas.drawBitmap(bmp[i], 0, 0, null);
i++;
If I test the app on my phone it runs fine but when I test it on my tablet throws me the "bitmap size exceeds VM budget" error... So I investigate a little bit more and I had the following solution: Create the original 14 bmp Bitmap objects and make a second array of Bitmaps called bmp2 and then only assign the bitmap when I need it and then clear it like this on my onDraw method:
bmp2[i]= Bitmap.createScaledBitmap(bmp[i], newWidth, newHeight,true);
canvas.drawBitmap(bmp2[i], 0, 0, null);
bmp2[i].recycle();
bmp2[i]=null;
It work on my tablet and my phone but with a really bad performance I assume is because I assign the image in real time (on the onDraw method) but if I use the recycle method and don't assign again the bmp2 there will be not a new bmp2...
So finally my real question is: How can I clear some memory after I show my image and then when I need it again I can use it. Thanks!
Other have mentioned how scaling up small bitmaps to fill a large screen will not give high-quality results. But anyway, to answer your specific question, this is how you can draw your low-res bitmaps scaled up to fill the screen without memory errors.
You need to use one of the alternative Canvas.drawBitmap() methods which can scale your bitmap to the correct size while drawing.
For example:
public void drawBitmap (Bitmap bitmap, Rect src, RectF dst, Paint paint)
From the Javadoc:
Draw the specified bitmap, scaling/translating automatically to fill
the destination rectangle.
Set the dst rectangle to be the screen size you calculated earlier.
Then, you do not need to create separate scaled bitmaps in memory, so your problem goes away.
I am afraid there is no safe way of doing a fullscreen animation using scaled bitmaps.
Why?
Let's do some counting. Each your initial bitmap is 320x480, yes? That's 320*480*4*14=8601600, which is roughly 8Mb of memory. When you scale to 1280x720, you add up 1280*720*4*14=51609600 bytes, which is 49 megabytes. 49 + 8 = 57 megabytes. Given that the newest tablets have maximum of 64 megabytes allocated for your app VM, no surprise it won't fit. And even if it fits with your tablet, what would happen if a Nexus 10 owner would buy your app? N10 has 2560Ă—1600 resolution (not to mention that scaled bitmap would look pathetic).
Allocating single bitmap for single frame will hardly let you have a smooth animation, I'm afraid. You could try keeping a window of 5 bitmaps at once and recycling others, but still I don't thik it would be enough. Besides that would consume ton of CPU work for constant bitmap scaling and will destroy your battery.
If you really want an animated background, you should probably look at either OpenGL, or TextureView.
Most of the times the GC would do the job.
You should have different bitmap resolutions handled by Android by folder (xhdpi, hdpi, etc.).
You should also check BitmapFactory.Options in order to have lower quality version Bitmaps generated by your BitmapFactory signatures.
Edit
You might also want to consider using WeakReferences to your Bitmaps.
You should design new bitmaps for your Tablets and Other Higher Screens and put them in the xhdpi folder under the res directory. This is the most efficient option. Let The Android OS itself pull the best fitting Resource for you.
See this for more detailed help
http://developer.android.com/training/multiscreen/index.html
This is what the documentation says
Although the system performs scaling and resizing to make your
application work on different screens, you should make the effort to
optimize your application for different screen sizes and densities. In
doing so, you maximize the user experience for all devices and your
users believe that your application was actually designed for their
devices—rather than simply stretched to fit the screen on their
devices.
I am trying to set a different wallpaper for every home screen, but I get OutOfMemory issues. I have 5 Bitmaps which I am trying to overlay on a wallpaper Bitmap which is 5 times the display width. Using the code below I get OOM. The problem seems to be the first line of code which creates the large wallpaper Bitmap. My question is whether there is a way to do this (i.e. some way that takes up less memory, or someway to allocate more memory??). Thanks!
Bitmap wallpaper = Bitmap.createBitmap(displayWidth*5,displayHeight, Config.ARGB_8888);
Canvas canvas = new Canvas(wallpaper);
Uri data = getIntent().getData();
Bitmap bmp = getBitmap(data, imagePosition, displayWidth, displayHeight);
canvas.drawBitmap(bmp, 0, 0,null);
WallpaperManager wallpaperManager = (WallpaperManager) SetterActivity.this.getSystemService(Context.WALLPAPER_SERVICE);
wallpaperManager.setBitmap(wallpaper);
wallpaperManager.suggestDesiredDimensions(bmp.getWidth()*2, bmp.getHeight());
If you can't get around having 5 screens worth of images loaded at a time, you could try only having one loaded at a time, and switch based on which home screen is currently being viewed.
That is, you have 2 bitmaps in memory, one is current and one is next. Load the current bitmap on first view. Use onOffsetsChanged to determine when a scroll is happening, and at that point load bitmap next based on which home screen will be showing next. You'll need to do that interpolation from screen A to B on your own. when the scroll is finished, recycle current and save next as current.
I'm sure there are some gotchas in this implementation that I'm not thinking of right now, but you'll only ever have 2 bitmaps allocated with this method. Good luck!
Bitmap data is allocated in the Native heap (see BitmapFactory OOM driving me nuts for details). How much is available depends on the platform (eg API level 2.2 has 24M Native heap total), but there is no way to grow it beyond that. And how much of the native heap space is allocated depends on what your and other applications are doing with bitmaps/graphics.
Your first line of code / wallpaper bitmap is only 480 * 800 * 32 / 8 = 1.536 Mbytes, which is well within the maximum. So it appears likely that much of the heap is already allocated by the time you get to that line?
Note that the native heap is garbage collected but infrequently - and data is not recovered if the application does not explicitly free it. So if you were running application without recycling your bitmaps (in onDestroy) you might well eat up the Native heap after a few runs.