What's the best way to display an animation as a live wallpaper? Right now I have a gif split into 11 pngs (one per frame) and then I just am doing
public Bitmap frame0;
ArrayList<Bitmap> frameArray = new ArrayList<Bitmap>();
frame0 = BitmapFactory.decodeResource(getResources(), R.drawable.nyancat0);
frame0 = Bitmap.createScaledBitmap(frame0, minWidth, minHeight, true);
frameArray.add(frame0);
Then I just use a For Loop to loop through the frames and draw them on a canvas
canvas.drawBitmap(frameArray.get(indexnumber), 0, 0, mPaint);
and then I just change my indexnumber++ unless it's 11, then I go back to 1.
That works, but of course, storing that many Bitmaps is very memory inefficient. This stops me from doing multiple layers or other cool effects without lagging and battery drain. Is there a better way to display an animation on the Android Live wallpaper? I tried Movie for displaying the whole GIF but that's not supported for live wallpapers.
How long does the loading of images take? If it's negligible then why not load each image in right before you display it, discarding the old one? That way you only have 1 image in memory at any one stage.
Alternatively do something akin to using a back buffer, have two spaces in memory, one for the image being displayed now, an another into which you're loading the next image. When it's time to change you make the newly loaded bitmap visible, unload the other and then load the next frame into that.
Despite what people say, you actually can have a lot of images in your Live Wallpaper. The only tricky thing is the memory limit. I had as much as 40 .pngs loaded in my application and i reloaded them once in a minute.
But when you handling that many images in your application, you have to load them in a smart way:
public BitmapResult decodeResource(int file, int scale){
//Decode image size
BitmapFactory.Options o = new BitmapFactory.Options();
o.inPurgeable = true;
o.inInputShareable = true;
o.inJustDecodeBounds = true;
BitmapFactory.decodeResource(resources, file, o);
BitmapFactory.Options o2 = new BitmapFactory.Options();
o2.inPreferredConfig = Bitmap.Config.ARGB_8888;
o2.inSampleSize=scale;
return new BitmapResult(BitmapFactory.decodeResource(resources, file, o2),o2.outWidth,o2.outHeight);
}
You see that scale variable? It should be a power of 2 and it scales your bitmap down.
In case things got wrong, clean the bitmaps and reload bitmaps with a lower quality:
void init()
{
try
{
loadFirstBitmap();
loadSecondBitmap();
}
catch(java.lang.OutOfMemoryError error)
{
/*some infinite loop breaker*/
scale *= 2;
cleanup();
init();
}
}
Also, system won't get rid of a bitmaps for you, you have to clean them yourself and then probably call the garbage collector:
bitmap1.recycle();
bitmap2.recycle();
System.gc();
Resizing your bitmaps to the size you need is also a good idea because otherwise system would probably call createScaledBitmap each time you try to draw it which would require additional memory.
I never figured what's the memory cap for such kind of apps is and is it that a memory heap limit which most often equals 24 MB, but i can tell you that my app takes up to 13 MB of memory and no one ever reported a crash on Android devices >= 2.2.
So if you follow some optimization rules, you can load as much bitmaps in your application as you need.
Related
after googling a lot I have not yet found a way to resize an image preserving quality.
I have my image - stored by camera in full resolution - in
String filePath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES) + "/my_directory/my_file_name.jpg";
Now, I need to resize it preserving aspect ratio and then save to another path.
What's the best way to do this without occurring the error "Out of memory on a xxxxxxx-byte allocation."?
I continue to retrieve this error on Samsung devices, I tried in every way, even with the library Picasso.
Thanks!
1st things 1st: depending on device and bitmap size, no matter what magic code you do, it will crash! Specially cheap Samsung phones that usually have no more than 16mb of RAM to the VM.
You can use this code How to get current memory usage in android? to check on amount of memory available and deal with it properly.
When doing those calculations, remember that bitmaps are uncompressed images, that means, even thou the JPG might be 100kb, the Bitmap might take several MB.
You'll use the code shown here https://developer.android.com/training/displaying-bitmaps/load-bitmap.html to read the bitmap boundaries, and do an approximate scale down as close as possible to the size you actually need, or enough to make the device not crash. That's why it's important to properly measure the memory.
That 1st code takes virtually no RAM as it creates from the disk, making it smaller by simply skipping pixels from the image. That's why it's approximate, it only does in power of 2 the scaling.
Then you'll use the standard API to scale down to the size you actually need https://developer.android.com/reference/android/graphics/Bitmap.html#createScaledBitmap(android.graphics.Bitmap, int, int, boolean)
so the pseudo code for it, will be:
try{
Info info = getImageInfo(File);
int power2scale = calculateScale(info, w, h);
Bitmap smaller = preScaleFromDisk(File, power2scale);
Bitmap bitmap = Bitmap.createScaledBitmap(smaller, w, h, f);
} catch(OutOfMemoryError ooe){
// call GC
// sleep to let GC run
// try again with higher power2scale
}
I've researched at least 10 similar topics on SO, however, none have reached a definitive answer for me allowing me to avoid the Out of Memory error Bitmaps are known for.
Taking into consideration the advice from these previous questions, I constructed the following method setBipmapFromPath to produce an optimally sized (both in dimensions and kilobytes) wallpaper image from a file path. This method works fine on a large RAM device like my G2, however, it crashes in an emulator with 1.5GB of RAM using a 256kb picture.
I welcome any criticism that will help me prevent the Out of Memory error. My hope is to also ensure the image can still act as a proper background image, as in, fill the screen of the device reasonably without insane stretch marks.
My methods:
public void recycleWallpaperBitmap() {
if (mBitmap != null) {
mBitmap.recycle();
mBitmap = null;
}
}
private void setBitmapFromPath() {
// Recycle the bitmap just in case.
recycleWallpaperBitmap();
String path = mProfileManager.getWallpaperPath();
if (path != null) {
WindowManager wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
Display display = wm.getDefaultDisplay();
int displayWidth = display.getWidth(); // deprecated
int displayHeight = display.getHeight(); // deprecated
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = false;
mBitmap = ThumbnailUtils.extractThumbnail(BitmapFactory.decodeFile(path, options),
displayWidth, displayHeight);
}
}
This method works fine on a large RAM device like my G2, however, it crashes in an emulator with 1.5GB of RAM using a 256kb picture.
It will fail on the G2 as well, depending on where and when you call this method. Your "256kb" picture will take up several MB of heap space, and there is no assurance that you have that amount of heap space available in a single contiguous block.
Also, I would not use a class and method designed for creating thumbnails will be suitable for creating wallpaper-sized images.
I welcome any criticism that will help me prevent the Out of Memory error.
Use inSampleSize on your BitmapFactory.Options to downsample the image to closer to the right size while it is being read in.
Then, dump your use of ThumbnailUtils and allow your ImageView to scale it the rest of the way, to avoid making yet another copy of the image.
Bonus points for using inBitmap instead of junking and re-allocating your Bitmap every time, since the screen size is not changing, and therefore your wallpaper dimensions are not changing.
These techniques and more are covered in the developer documentation.
(This is due to the limitations of the server software I will be using, if I could change it, I would).
I am receiving a sequence of 720x480 JPEG files (about 6kb in size), over a socket. I have benchmarked the network, and have found that I am capable of receiving those JPEGs smoothly, at 60FPS.
My current drawing operation is on a Nexus 10 display of 2560x1600, and here's my decoding method, once I have received the byte array from the socket:
public static void decode(byte[] tmp, Long time) {
try {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferQualityOverSpeed = false;
options.inDither = false;
Bitmap bitmap = BitmapFactory.decodeByteArray(tmp, 0, tmp.length, options);
Bitmap background = Bitmap.createScaledBitmap
(bitmap, MainActivity.screenwidth, MainActivity.screenheight, false);
background.setHasAlpha(false);
Canvas canvas = MainActivity.surface.getHolder().lockCanvas();
canvas.drawColor(Color.BLACK);
canvas.drawBitmap(background, 0, 0, new Paint());
MainActivity.surface.getHolder().unlockCanvasAndPost(canvas);
} catch (Exception e) {
e.printStackTrace();
}
}
As you can see, I am clearing the canvas from a SurfaceView, and then drawing the Bitmap to the SurfaceView. My issue is that it is very, very, slow.
Some tests based on adding System.currentTimeMillis() before and after the lock operation result in approximately a 30ms difference between getting the canvas, drawing the bitmap, and then pushing the canvas back. The displayed SurfaceView is very laggy, sometimes it jumps back and forth, and the frame rate is terrible.
Is there a referred method for drawing like this? Again, I can't modify what I'm getting from the server, but I'd like the bitmaps to be displayed at 60FPS when possible.
(I've tried setting the contents of an ImageView, and am receiving similar results). I have no other code in the SurfaceView that could impact this. I have set the holder to the RGBA_8888 format:
getHolder().setFormat(PixelFormat.RGBA_8888);
Is it possible to convert this stream of Bitmaps into a VideoView? Would that be faster?
Thanks.
Whenever you run into performance questions, use Traceview to figure out exactly where your problem lies. Using System.currentTimeMillis() is like attempting to trim a steak with a hammer.
The #1 thing her is to get the bitmap decoding off the main application thread. Do that in a background thread. Your main application thread should just be drawing the bitmaps, pulling them off of a queue populated by that background thread. Android has the main application thread set to render on a 60fps basis as of Android 4.1 (a.k.a., "Project Butter"), so as long as you can draw your Bitmap in a couple of milliseconds, and assuming that your network and decoding can keep your queue current, you should get 60fps results.
Also, always use inBitmap with BitmapFactory.Options on Android 3.0+ when you have images of consistent size, as part of your problem will be GC stealing CPU time. Work off a pool of Bitmap objects that you rotate through, so that you generate less garbage and do not fragment your heap so much.
I suspect that you are better served letting Android scale the image for you in an ImageView (or just by drawing to a View canvas) than you are in having BitmapFactory scale the image, as Android can take advantage of hardware graphics acceleration for rendering, which BitmapFactory cannot. Again, Traceview is your friend here.
With regards to:
and have found that I am capable of receiving those JPEGs smoothly, at 60FPS.
that will only be true sometimes. Mobile devices tend to be mobile. Assuming that by "6kb" you mean 6KB (six kilobytes), you are assuming a ~3Mbps (three megabits per second) connection, and that's far from certain.
With regards to:
Is it possible to convert this stream of Bitmaps into a VideoView?
VideoView is a widget that plays videos, and you do not have a video.
Push come to shove, you might need to drop down to the NDK and do this in native code, though I would hope not.
Before enabling largeHeap option, I was handling large bitmaps and it's consume almost the entire memory available for the application, and recycling it over navigation and loading new ones works round on almost the full heap available. However when some operations needs a bit more memory the application crashes. So I enabled largeHeap=true to have a bit more memory.
But doing this has a unexpected behavior, it's looks like that recycle() method of bitmaps do not work most of times, and the application that worked in 58Mb of memory (and exceeds sometimes throwing a OutOfMemoryException) now consumes memory exponentially and keeps growing (for now the test I did came to 231Mb allocated memory), the expected behavior is that the memory management keeps working and the application will not use more than 60Mb.
How can I avoid that? Or efficiently recycle bitmaps?
EDIT: Actually, I made it give a OutOfMemoryError when allocating more than 390Mb of memory on the device.
Reading GC_* logs shown that only GC_FOR_ALLOC that freed 3.8Mb sometimes, but almost never other GC runs freed something.
You should probably have a look at Displaying Bitmaps Efficiently which includes several ways to handle large Bitmaps Efficiently,
Loading Large Bitmaps Efficiently
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(getResources(), R.id.myimage, options);
int imageHeight = options.outHeight;
int imageWidth = options.outWidth;
This will give you the size of the image before downloading and on that basis you can check the size of your device and scale it using calculateInSampleSize() and decodeSampledBitmapFromResource() given in the explanation of docs.
Calculating how much we need to scale the image,
First way
if (imageHeight > reqHeight || imageWidth > reqWidth) {
if (imageWidth > imageHeight ) {
inSampleSize = Math.round((float)imageHeight / (float)reqHeight);
} else {
inSampleSize = Math.round((float)imageWidth / (float)reqWidth);
}
}
Second way
int inSampleSize = Math.min(imageWidth / reqWidth,imageHeight / reqHeight);
The you can set the inSampleSize,
options.inSampleSize = inSampleSize;
Then finally make sure you call,
options.inJustDecodeBounds = false;
else it will return Bitmap as null
Processing Bitmaps Off the UI Thread
Processing Bitmap on UI thread is never safe so its always better to do that in a background thread and update UI after the process is completed.
Caching Bitmaps
LruCache is available from API 12 but if you are interested it using below versions it is also available in Support Library too. So caching of Images should be done efficiently using that. Also you can use DiskLruCache for images where you want then to remain for longer period in extenal storage.
Clearing the Cache
Sometimes when your image size is too large even caching the image causes OutOfMemoryError so in that case its better to clear the cache when your image is out of the scope or not used for longer period so that other images can be cached.
I had created a demo example for the same, you can download from here
Your case behaves as expected. Before Honeycomb, recycle() was unconditionally freeing the memory. But on 3.0 and above, bitmaps are part of normal garbage collected memory. You have plenty of RAM on the device, you allowed the JVM to allocate more than the 58M limit, now the garbage collector is satisfied and has no incentive to reclaim memory occupied by your bitmaps.
You can verify this by running on an emulator with controlled amount of RAM, or load some memory consuming service on your device - GC will jump to work. You can use DDMS to further investigate your memory usage.
You can try some solutions for bitmap memory management: Bitmaps in Android Bitmap memory leaks http://blog.javia.org/how-to-work-around-androids-24-mb-memory-limit/, but start with the official Android bitmap tips, as explained in #Lalit Poptani's detailed answer.
Note that moving the bitmaps to OpenGL memory as textures has some performance implications (but perfect if you will render these bitmaps through OpenGL in the end). Both textures and malloc solutions require that you explicitly free the bitmap memory which you don't use anymore.
Definitely #Lalit Poptani answer is the way to do it, you should really scale your Bitmaps if they are very large. A preferable way is that this is done server-side if this is possible, since you will also reduce NetworkOperation time.
Regarding the implementation of a MemoryCache and DiskCache this again is the best way to do it, but I would still recommend to use an existing library, which does exactly that (Ignition) and you will save yourself a lot of time, and also a lot of memory leaks, since because your Heap does not get emptied after GC I can assume that you probably have some memory leaks too.
To address your dilemma, I believe this is the expected behaviour.
If you want to free up memory you can occasionally call System.gc(), but really you should for the most part let it manage the garbage collection itself.
What I recommend is that you keep a simple cache (url/filename to bitmap) of some sort which keeps track of its own memory usage by calculating the number of bytes that each Bitmap is taking up.
/**
* Estimates size of Bitmap in bytes depending on dimensions and Bitmap.Config
* #param width
* #param height
* #param config
* #return
*/
public static long estimateBitmapBytes(int width, int height, Bitmap.Config config){
long pixels=width*height;
switch(config){
case ALPHA_8: // 1 byte per pixel
return pixels;
case ARGB_4444: // 2 bytes per pixel, but depreciated
return pixels*2;
case ARGB_8888: // 4 bytes per pixel
return pixels*4;
case RGB_565: // 2 bytes per pixel
return pixels*2;
default:
return pixels;
}
}
Then you query how much memory the app is using and how much is available, maybe take half of that and try to keep the total image cache size under that, by simply removing (dereferencing) the older images from your list when your are coming up against this limit, not recycling. Let the garbage collector clean up the bitmaps when they are both derefrrenced from your cache and are not being used by any views.
/**
* Calculates and adjusts the cache size based on amount of memory available and average file size
* #return
*/
synchronized private int calculateCacheSize(){
if(this.cachedBitmaps.size()>0){
long maxMemory = this.getMaxMemory(); // Total max VM memory minus runtime memory
long maxAllocation = (long) (ImageCache.MEMORY_FRACTION*maxMemory);
long avgSize = this.bitmapCacheAllocated / this.cachedBitmaps.size();
this.bitmapCacheSize = (int) (maxAllocation/avgSize);
}
return this.bitmapCacheSize;
}
I would recommend you stay away from using recycle(), it causes a lot of intermittent exceptions (like when seemingly finalized views try to access recycled bitmaps) and in general seems buggy.
You have to be very careful with handling bitmaps on Android. Let me rephrase that: you have to watch out handling bitmaps even on a system with 4 gigs of RAM. How large are these guys and do you have a lot? You might have to chop and tile it up if it's large. Remember that you use using video RAM, which is a different animal than system RAM.
Pre-Honeycomb, the Bitmaps were allocated on the C++ layer, so that RAM usage was invisible to Java and couldn't be accessed by the garbage collector. A 3 MP uncompressed Bitmap with the RGB24 colorspace uses around 9-10 megabytes (around 2048x1512). So, larger images can easily fill up your heap. Also remember that in whatever is being used for video RAM (sometimes dedicated RAM, sometimes shared with system), the data is usually stored uncompressed.
Basically, if you are targeting pre-Honeycomb devices, you almost have to manage Bitmap object as if you were coding a C++ program. Running the bitmap recycle() onDestory() usually works if there aren't many images, but if you have a ton of images on the screen, you may have to handle them on-the-fly. Also, if you launch another activity, you may have to consider putting in logic into onPause() and onResume().
You can also cache the images using the Android file system or SQLite when they aren't in video RAM. You may be able to get away with caching it in RAM if you are using a format like .jpg or a .png with a lot of repeated data/
I am creating a viewer application that calls BitmapRegionDecoder.decodeRegion(Rect, BitmapFactory.Options). I am disposing of the bitmap previously got from decodeRegion before each call:
//this is a function I wrote that gets the rectangle I need
//from the zoom/pan state of a lower-resolution ImageView (page).
//it is bragable.
Rect areaRect = page.getBitmapRegionDecoderRect(areaBitmapRect);
BitmapFactory.Options options = new BitmapFactory.Options();
//area is the ImageView which will get the hi-res bitmap in it.
if(area.getHeight()!=0)
{
//I used Math.round because we are so desperate to save memory!
//The consequent blurring is actually not too bad.
options.inSampleSize = Math.round( ((float) areaRect.height()) / ((float) area.getHeight()) );
}
else
{
options.inSampleSize = Math.round( ((float) areaRect.height()) / ((float) page.getHeight()) );
}
if(options.inSampleSize==0) options.inSampleSize=1;
if(options.inSampleSize>16) options.inSampleSize=16;
options.inPreferredConfig = Bitmap.Config.RGB_565;
if(areaRect.left<0) areaRect.left = 0;
if(areaRect.right>areaBitmapRect.right) areaRect.right = areaBitmapRect.right;
if(areaRect.top<0) areaRect.top = 0;
if(areaRect.bottom>areaBitmapRect.bottom) areaRect.bottom = areaBitmapRect.bottom;
if(areaBitmap!=null)
{
//recycling our garbage ... or are we?
areaBitmap.recycle();
areaBitmap = null;
try
{
//dirty hack
wait(200);
}
catch(Exception x)
{
//something happened.
}
}
//the all-important call
areaBitmap = areaDecoder.decodeRegion(areaRect, options);
area.setImageBitmap(areaBitmap);
I was having problems with the fact that on very quick successive UI events, we were running out of memory. As you can see I have "solved" this with the dirty hack (the thread waits 200ms and gives Android a bit of time to catch up).
I'm not very happy with this, for obvious reasons. Firstly, is my diagnosis (that garbage collection is not finishing before we allocate new memory) correct? Secondly, I tried putting
while(!areaBitmap.isRecycled())
around a counter increment after the recycle() call and the counter stayed at zero. I see the sense in having isRecycled() do this but I need something like an isCompletelyRecycled() method instead. Does Android have anything like this? Thirdly, if I can't get anywhere with this, is there an "available memory" method I can use to tell if my call is going to push us over? I can't find one. It would be nice if Android would say MORE CORE AVAILABLE BUT NONE FOR YOU so I could maybe call my wait loop as a plan B, or eventually try something less intensive instead.
Bitmap memory is allocated in the native heap, so System.gc() will not help. The native heap has its own GC, but clearly it is is not kicking in quickly enough for you - and I am not aware of any way to force it (ie there is not a native analogue of System.gc()).
You could change the structure of your application as Nicklas A suggests to re-use your bitmap.
Or you could use the native heap monitoring mechanisms detailed in BitmapFactory OOM driving me nuts to determine when it is safe to allocate a new bitmap.
Try running System.gc() instead of the sleep.
Actually I'm unsure if this will help as bitmap seems to be mostly native code meaning that it should be released as soon as recycle is called.
To me it seems like creating a new bitmap on every UI event seems like a bad idea, couldn't you just create one bitmap and edit that one?