I'm curious if anyone has information on whether or not this is an actual optimization or unnecessary bloat.
I have some screens that are pushed and popped from the stack via user interaction and all of them have the same background image.
Instead of loading the image on each screen, I have implemented a static method which loads the image from disk the first time it's accessed, then keeps the bitmap in a static variable for future use.
Is there some way to profile this or is anyone aware of a downside to this?
public final class App {
private static Bitmap _bgBitmap = null;
/*
* Get a standard background for screens
* Method caches background in memory for less disk access
*/
public static Bitmap getScreenBackground(){
if (_bgBitmap == null){
try {
_bgBitmap = Bitmap.getBitmapResource("ScreenBG.jpg");
}
catch(Exception e){}
}
return _bgBitmap;
}
}
I suppose the only reason of having a Bitmap as a static field somewhere is to speed up creating another screen that also uses the same bitmap. IMHO this is a nice approach, however the answer to your question may differ depending on how exactly you use the bitmap:
Do you draw it directly on Graphics instance in some paint()?
Do you resize it before drawing?
Do you create a Background instance from the bitmap? In this case you'll need to investigate whether the Background instance creates a copy of bitmap for its internal usage (in this case RAM consupmtion may be doubled (2 bitmaps), so it would be nice to share across screens the Background instance rather than the bitmap).
Another point - it sounds like there maybe a case when there is no screen instances that use the bitmap. If yes, then you could detect such case in order to nullify the _bgBitmap so if OS decides to free some RAM it could GC the bitmap instance. However if app workflow implies such screen to be created soon, then maybe it is cheaper to leave the bitmap alive.
Also, how large is the bitmap? If it is relatively small, then you can just don't bother yourself with further optimization (your current lazy loading is good enough). You can count size in bytes consumed in RAM by knowing its with and height: int size = 4 * width * height. You can also log/popup the time taken to load the bitmap from resources. If it is relatively small, then maybe don't even use the current lazy loading? Note the timings should be taken on real devices only, since BB simulators are in times faster than devices.
Related
I would like to know if there are any optimizations that could be used to improve the speed when using a large quantity of bitmaps drawn on a screen.
I use a canvas which I load all my resources at the initialization and use createBitmap when I need to update the bitmap. I use ~10-15kb files with my Galaxy Note 3 and notice a lag (xxhdpi) when I reach around 20 bitmaps which gets nearly unusable around 35+.
I am using createbitmap constantly because the bitmaps use frame animation and matrix to rotate.
So far the only thing i've tried that i've noticed a difference is inBitmap which gives about 5-10% increase in the GC_FOR_ALLOC freed.
Anyone care to chime in on a good answer for what is better? I've heard flash AIR is a good choice to go with using cacheAsBitmapMatrix, but I would like a different option (just personal pref).
EDIT:
(rectf bounds = bitmap bounds)
matrix.setRotate(rotation, rectf.centerX(), rectf.centerY());
ship1 = Bitmap.createBitmap(ship1, 0, 0, ship1.getWidth(), ship1.getHeight(), matrix, true);
I think I understand my problem, I should be calling
canvas.drawBitmap(ship1, matrix, paint);
But in my onDraw method I am using
canvas.drawBitmap(ship1, srcRectf, dstRectf, paint); //srcRectf = null
I use dstRectf to move my bitmap around, but I suppose this can be replaced with setTranslate. I'll try it out, thanks Mehmet!
Bitmap stores the pixel data in the native heap*, not in the java heap, and takes care of managing it by itself. That would mean GC shouldn't give you any serious headaches.
The problem is probably constantly using createBitmap(), which is usually a really costly operation. This will make a disk IO call at worst, or a relatively big memory allocation at best. You would like to use it as little as possible, i.e. only when initially reading them from the disk.
Instead I advise you to use a Matrix in conjunction with a Canvas. Just change your Matrix and with each step repaint your Bitmaps with it.
EDIT:
*Only correct for Android 2.3.3 <-> Android 3.0
I'm making a game in Java and enjoying the speedup that VolatileImage offers. I'd like to include an effect in my game that makes objects linger and fade in their old positions after moving away, much like the motion blur imitation described in this video. The method involved in that video involves changing the screen-clearing (which occurs every frame) from opaque to partially transparent. This method is perfect except that it won't work for VolatileImages, since their memory contents could be lost at any time.
Using VolatileImage.getSnapshot() solves this problem, since you can save the image contents between frames in case they're lost. But this means a new image is created each frame, and if we assume 1920x1080 resolution, that's about 8 MB of memory allocation per frame. I'm hoping for a way to get around this, either by somehow writing the snapshot to pre-allocated memory or by achieving this effect in some other way. Any suggestions?
I solved my problem by creating an accelerated BufferedImage like so:
private static BufferedImage renderImg = device.getDefaultConfiguration().
createCompatibleImage(
device.getDisplayMode().getWidth(),
device.getDisplayMode().getHeight());
static {
renderImg.setAccelerationPriority(1);
}
I'm trying to load a background image for a game as well as some smaller images, placing them on a Canvas, and letting people interact with the smaller overlayed images (like move, rotate)
In order to maintain aspect ratio (e.g. 5:3) I tried loading in the images as a bitmap and resizing them myself. The idea was to do cropping/letter-boxing for the background according to the canvas's width/height, and maintain the correct ratio of size for the smaller images.
Bitmap originalBitmap = BitmapFactory.decodeResource(getResources(), resourceImg);
Bitmap resizedBitmap = Bitmap.createBitmap(bitmapOrg2, 0, 0, width, height, matrix, true);
In order to cater for tablets/phones i have a background PNG background image at 1600x1000 and 200kb.
However I am now struggling with out of memory issues due to the bitmap being 1600x1000x4byte=6.4 mb of ram and more when it tries to resize.
(I am using the emulator at the moment when these issues occur)
I decided to change it to use canvas.setBackgroundResource
SceneCanvas sceneCanvas = (SceneCanvas) findViewById(R.id.the_canvas);
sceneCanvas.setBackgroundResource(R.drawable.my_scene_1600x900);
This works well, except it fills the screen and does not maintain aspect ratio.
Is there a way to set the background maintaining aspect ratio? Or have I just gone down the wrong route completely and should use ImageViews and render to the canvas somehow to avoid OutOfMemory issues
Given that Java code is only allowed a heap size of around 20MB or so, you’re always going to have trouble with large bitmaps (unless you resort to native code in C/C++/etc).
One option is to use a BitmapFactory to load your image, and in the Options you can specify an inSampleSize to downsample the image as it’s being read in. This should avoid chewing up memory by trying to load the entire original image. There is even an inJustDecodeBounds option, so your code can check the dimensions of the image, instead of having them hard-wired into the code.
It seems that the memory limit on Android is somewhere between 16 - 24 MB memory (depending on device). This is regardless of whether the device has a lot more memory. Also, the memory used by Bitmaps is included in the limit, resulting in lang.OutOfMemoryError: bitmap size exceeds VM budget. After some searching, there are 3 options I could find:
Allocate memory from native code using the NDK (native development kit) and JNI
For images one can also use OpenGL textures, the texture memory is not counted towards the limit.
take advantage of certain bitmap options to reduce size; http://developer.android.com/reference/android/graphics/BitmapFactory.Options.html
To see how much memory your app has allocated one can use, android.os.Debug.getNativeHeapAllocatedSize().
I realize that there are many similar questions, but most involve scaling down the bitmap, or explicit calls to bitmap.recycle() and System.gc() which doesn't guarantee anything (and in my case failed to prevent the error).
Edit: I have also tried using isPurgable = true when creating my bitmap.
Edit: Also, I have only tested this with Froyo (2.2.2) on Motorola Droid.
Here is the scenario: I am loading one bitmap (width: 1500, height: 2400). This takes up roughly 14 MB. The rest of the app is minuscule with regard to memory consumption (easily less than 2 MB).
I am using the bitmap with a transformation matrix to pan and zoom around on a surface view. On first load, this works perfectly. However, when I exit the app and relaunch it, I get the dreaded OutOfMemoryError. On third launch it works, on fourth it crashes ... and so on.
I don't need to save state, and so I tried calling finish() in onPause() (as well as the recycle() and gc() methods mentioned above). Finish() seems to stop all threads, but does not clear the memory.
I should mention that I am also using a technique which I found in a comment from this question.
Also Please check this
So, my image is loaded from the web, as an immutable bitmap. Its bytes are then saved to sdcard (very slow) just to be reloaded back to a mutable bitmap. If jumping through all these hoops is laughable, please educate me...
For my case, clearing all memory allocated for the app would be acceptable (if it doesn't generate crash messages). Is there anyway to just totally clear the memory allocated to my app so that each restart is as clean as the first launch?
Is there any solution involving tiling? Surely I am missing something.. since the image file itself (a png) is only a few kilobytes, and I have viewed larger images in the stock gallery app without this problem.
Edit: I have determined the cause of the problem based on insight gleaned from #Jason Lebrun's answer. It turns out that the canvas I had used to draw on this bitmap held a reference too it, so that canvas needed to be set to null for it to be properly garbage collected. Hope this helps someone with a similar issue.
Are you experiencing this problem on Gingberbread, or a different version? Gingerbread has a lot of problems with apps that use a lot of Bitmap memory, so knowing the OS version can help with determining the nature of the problem.
In your case, it's hard to say exactly what might be the cause. However, with a 14MB bitmap, even a 2nd instance is likely to use up your available heap and cause a crash. On Gingerbread, it's pretty easy to end up with Bitmap memory sticking around for longer than it should, due to the concurrent GC + the fact that Bitmap memory is allocated in a native array.
When you exit the app, it's probably not being unloaded from memory. That would explain your crash pattern: the first time you run the app, the large bitmap is loaded. The 2nd time you launch it, it's actually just restarting an Activity for a process already in memory, and for some reason, the memory for the Bitmap is still hanging around taking up room. So the app crashes, and a new process is started, with a fresh heap. If recycling is not helping, you might still have a reference to the previous Bitmap sticking around. If you're always using the same Bitmap, reusing a static reference to it might help, although I'm not sure.
Are you sure that the Bitmap is not leaked via a leaked context, long running background code, or something similar?
Other answers here have good advice, which is to try tiling the Bitmap after you fetch it, and only loading tiles as necessary. If you don't need to support older versions, you can use BitmapRegionDecoder (http://developer.android.com/reference/android/graphics/BitmapRegionDecoder.html) to do this, but only on devices that support API level 10 or higher.
To expand on #jtietema's answer, have you tried loading/rendering only the part of your bitmap that would be visible after applying your transformation matrix? You could do this by using a bounds-only bitmap for the whole image and transforming that, and using the resulting rectangle as an input to your acquiring-bitmap-from-sd-card.
You can use something like the following to decrease the sample size. I use this method in one of my apps to display images from the assets directory. But you can play around with the sample size, I've used values of 1 for images that aren't to big (94kb) and 4 for larger images (1.9mb)
protected void loadImageIntoBitmap( String imageAssetFile, Bitmap bitmap, int sampleSize, ImageView imgvw ) throws IOException {
InputStream is = getContext().getAssets().open( imageAssetFile );
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = sampleSize;
bitmap = BitmapFactory.decodeStream( is,null,options );
imgvw.setImageBitmap( bitmap );
is.close();
}
What are you doing with the bitmap? The resolution is way higher than that of your android device, so if you are viewing the whole bitmap than scaling it down would do.
If you are zooming in you could create just a subset of the bitmap and just load the part that is visible.
well whenever you are saving the image to SD card you can use a lower quality of the image like:
myBitmap.compress(Bitmap.CompressFormat.PNG, 85, fileOutputStream);
and/or also use the bitmapFactory options to get a smaller image to save memory:
BitmapFactory.Options factoryOptions= new BitmapFactory.Options();
factoryOptions.inSampleSize = samplesize;
note that calling recicle() and gc() doesn't mean that the resources will be freed immediatly.
As the docs say:
from myBitmap.recycle():
Free the native object associated with this bitmap, and clear the reference to the pixel data. This will not free the pixel data synchronously; it simply allows it to be garbage collected if there are no other references. The bitmap is marked as "dead", meaning it will throw an exception if getPixels() or setPixels() is called, and will draw nothing. This operation cannot be reversed, so it should only be called if you are sure there are no further uses for the bitmap. This is an advanced call, and normally need not be called, since the normal GC process will free up this memory when there are no more references to this bitmap.
and from System.gc():
Indicates to the VM that it would be a good time to run the garbage
collector. Note that this is a hint only. There is no guarantee that
the garbage collector will actually be run.
therefore your app might be still using the resources and then whenever you re open the app the system is still using the resources plus the new ones that you are generating, and that might be the reason of why you are getting the out of memory error.
So in short handling a large image in memory is not a good idea.
This method takes in the image path and gives you a drawable without crashing
For best possible quality of image,always call this method with argument imageSizeDivide's value =1
public Drawable recurseCompressAndGetImage(String image_path,
int imageSizeDivide) {
try {
Log.w("", "imageSizeDivide = " + imageSizeDivide);
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = imageSizeDivide;// controls the quality of image
// Bitmap
Bitmap srcBmp = BitmapFactory
.decodeFile(image_path.trim(), options);
//next if-else block converts the image into a squire image.Remove this block if u wish
if (srcBmp.getWidth() >= srcBmp.getHeight()) {
dstBmp = Bitmap.createBitmap(srcBmp, srcBmp.getWidth() / 2
- srcBmp.getHeight() / 2, 0, srcBmp.getHeight(),
srcBmp.getHeight());
} else {
dstBmp = Bitmap.createBitmap(srcBmp, 0, srcBmp.getHeight() / 2
- srcBmp.getWidth() / 2, srcBmp.getWidth(),
srcBmp.getWidth());
}
dstBmp = Bitmap.createScaledBitmap(dstBmp, 400, 400, true);
return new BitmapDrawable(mContext.getResources(), dstBmp);
} catch (OutOfMemoryError e) {
//reduce quality and try again
return recurseCompressAndGetImage(image_path, imageSizeDivide * 2);
}
}
I am developing a game on android.Like tower defense.
I am using surface view.I am using some image as bitmap.(Spritesheets, tilesets, buttons, backgrounds,efects vs.)
Now images are nearly 5-6 mb.And i get this error when i run my game:
Bitmap size exceeds VM budget
19464192-byte external allocation too large for this process.
I call images like that
BitmapFactory.decodeResource(res, id)
and i put it to array.
I can't scale images.I am using all of them.
I tried that
options.inPurgeable=true;
and it work but the image is loading very slowly.I load a spritesheet with that and when it is loading, i get very very low fps.
What can I do?
I've had this problem too; there's really no solution other than to reduce the number/size of bitmaps that you have loaded at once. Some older Android devices only allocate 16MB to the heap for your whole application, and bitmaps are stored in memory uncompressed once you load them, so it's not hard to exceed 16MB with large backgrounds, etc. (An 854x480, 32-bit bitmap is about 1.6MB uncompressed.)
In my game I was able to get around it by only loading bitmaps that I was going to use in the current level (e.g. I have a single Bitmap object for the background that gets reloaded from resources each time it changes, rather than maintaining multiple Bitmaps in memory. I just maintain an int that tracks which resource I have loaded currently.)
Your sprite sheet is huge, so I think you're right that you'll need to reduce the size of your animations. Alternatively, loading from resources is decently fast, so you might be able to get away with doing something like only loading the animation strip for the character's current direction, and have him pause slightly when he turns while you replace it with the new animation strip. That might get complicated though.
Also, I highly recommend testing your app on the emulator with a VM heap set to 16mb, to make sure you've fixed the problem for all devices. (The emulator usually defaults to 24mb, so it's easy for that to go untested and generate some 1-star reviews after release.)
I am not a game dev however I would like to think I know Android enough.
Loading images of the size is almost certain to throw errors. Why are the images that file size?
There is an example at http://p-xr.com/android-tutorial-how-to-paint-animate-loop-and-remove-a-sprite/. If you notice he has an explosion sprite of only ~200Kb. Even a more detailed image would not take much more file space.
OK some suggestions:
Are you loading all your spritesheets onto a single sheet or is
each spritesheet in a seperate file? If they are all on one I would
split them up.
Lower the resolution of the images, an Android device is portable
and some only have a low resolution screen. For example the HTC
Wildfire has a resolution of 240x320 (LDPI device) and is quite a
common device. You have not stated the image dimensions so we can't be sure if this is practical.
Finally; I am not a game programmer but I found this tutorial (part of the same series) quite enlightening - http://p-xr.com/android-tutorial-2d-canvas-graphics/. I wonder if you are applying a pattern that is not appropriate for Android, however without code I cannot say.
Right something a little off topic but worth noting...
People under estimate the power of the View. While there is a certain amount of logic to using a SurfaceView, the standard View will do quite a lot on its own. A SurfaceView more often than not requires an underlying thread to run (that you will have to setup yourself) in order to make it work. A View however calls onDraw(), which can be utilized in a variety of ways including the postinvalidate() method (see What does postInvalidate() do?).
In any case it might be worth checking out this tutorial http://mindtherobot.com/blog/272/android-custom-ui-making-a-vintage-thermometer/. Personally, it was an excellent example of a custom View and what you can do with them. I rewrote a few sections and made a pocket watch app.