Android - A different Bitmap wallpaper for every home screen - OutOfMemory - java

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.

Related

What is the efficient way to load and cache multiple Bitmaps?

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.

My application crashes when i load huge amount of bitmaps in arraylist

i am new to android programming i am facing problem in loading bitmaps i have almost 92 bitmaps which i need to load. but the application crashes after 9 or 10 bitmaps.. kindly help me
here is the error
Throwing OutOfMemoryError "Failed to allocate a 22050012 byte allocation with 1805056 free bytes and 1762KB until OOM"
09-05 19:49:36.188 2615-2615/com.example.zeeshanahmedawan.yourworriestheirsolutions D/skia: --- allocation failed for scaled bitmap
mPages = new ArrayList<Bitmap>();
mPages.add(BitmapFactory.decodeResource(getResources(), R.drawable.p1));
mPages.add(BitmapFactory.decodeResource(getResources(), R.drawable.p2));
mPages.add(BitmapFactory.decodeResource(getResources(), R.drawable.p3));
mPages.add(BitmapFactory.decodeResource(getResources(), R.drawable.p4));
mPages.add(BitmapFactory.decodeResource(getResources(), R.drawable.p5));
mPages.add(BitmapFactory.decodeResource(getResources(), R.drawable.p6));
mPages.add(BitmapFactory.decodeResource(getResources(), R.drawable.p7));
mPages.add(BitmapFactory.decodeResource(getResources(), R.drawable.p8));
mPages.add(BitmapFactory.decodeResource(getResources(), R.drawable.p9));
If all of your images are similarly sized, you are attempting to allocate ~2028601104 bytes = ~1981055KB = ~1934MB. You have 64MB or less on most Android devices that your app can use.
The image that you failed on is 22050012 bytes in size, when decoded and perhaps scaled. That is equivalent to 2347 x 2347 pixels. Few if any Android devices have screens of that resolution.
So, you need to do several things:
Stop trying to load all of the bitmaps up front. Load them as needed.
Reduce the resolution of the bitmaps.
If you put the bitmaps in any directory other than res/drawable-nodpi/, move them to res/drawable-nodpi/. For example, if you put them in res/drawable/, that is a synonym for res/drawable-mdpi/, indicating that the images are designed for mdpi devices (~160dpi). Android will automatically scale those images up for higher-density devices. If you have images that are large, you cannot afford for Android to do that sort of scaling. res/drawable-nodpi/ tells Android that these images are not tied to a particular density and so should not be scaled this way.
You might also want to reconsider this entire plan. Working with lots of bitmaps — let alone large ones — is complicated even for experienced Android app developers. Memory management is hard. If you are new to Android, perhaps you should consider a project that will be simpler to implement.
You are out of memory.
Your bitmaps are too large to store in the array.
If you need just to view the photos, you can use RecyclerView. It keeps in memory only some photos which are on the screen now, and releases photos which have been scrolled out of the screen.
For other purposes you can save all photos to SD-card.
For both cases 1 & 2 use special library (Glide, Picasso,...). Libraries are easy-to-use, they can automatically cache downloaded images on SD-card and have many other benefits. Search for "glide with recyclerview" (for example)

Why does my 160kb app background become 49 MB at run time?

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.

how can I clear a bitmap memory in real time?

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.

Is there a memory limit available for widgets?

I have a widget with ViewFlipper that flips between X number of images. I aim for 10 images to be flipped, and I can do this if I load very small images. My widget is size 4x2 and I want to display images with good quality, but I can't achieve this. Everything loads fine, no exceptions, but the widget never displays them. If I load very small image sizes (100x100 px), it starts flipping them. If I load larger image size (300x300), it won't start flipping the images until I reduce the number of images (flips) to 4.
This suggests a memory limitation to me, but I would expect an exception to be thrown somewhere after I do appWidgetManager.updateWidget(widgetId, remoteViewFlipper).
Going through the logs, I don't see anything nearly related to this.
I think it depends on concrete implementation of launcher - widget stuff is hosted and processsed there. You updtes are send as parcelables, so there soule be data size limit as well:
http://groups.google.com/group/android-developers/browse_thread/thread/26ce74534024f41a?pli=1

Categories

Resources