I have some CustomButton, and at some point, a background is drawn from a base64String into a BitmapDrawable, which is applied to the CustomButton. Within the total lifespan of the application, this can happen multiple times. This caused a OutOfMemoryError: bitmap size exceeds VM budget, and after some digging on StackOverflow, this happened because the Bitmaps were not recycled. So I tried to handle this with some code that is executed each time another background was applied (this code comes from the CustomButton class):
public void recycleBackground() {
BitmapDrawable bd = null;
Drawable bg = getBackground();
if (bg != null) {
bg.setCallback(null);
if (bg instanceof BitmapDrawable) {
bd = (BitmapDrawable) bg;
if (!bd.getBitmap().isRecycled()) bd.getBitmap().recycle();
}
bd = null; //just precautionous
bg = null; //also just a precaution
}
Yet, the memory is slowly (because background images are not all that large) but surely flooded. What am I missing or doing wrong? I combined the above code from some different questions/answers from SO. I can find a lot on recycling Bitmaps, not so much on the recycling of Drawables. Maybe that's what I'm doing wrong?
Related
I have searched for hours on the subject of memory leaks and I cant seem to solve my problem. After exactly 9 rotations my app crashes with an OutOfMemory error. I have both Bitmaps and Drawables with Bitmaps in my app. I have tried putting in code that removes callbacks to drawables, I have tried setting all Bitmaps to null as well as manually calling the garbage collector. I had all of this code in the onSaveInstanceState() method since I assume that is called whenever the screen changes and before destruction of the view. None of these solutions worked.
I got some results with the Bitmaps turned to null, however that only added about another 9 screen rotations before another memory leak. Where is my leak? What am I doing wrong? I was under the impression that when a screen is rotated, everything is destroyed and recreated, that must obviously be false.
I dont want to post my code because A. there is a lot of it and B. its close to finishing and so becomes a company secret per se. If there is something you must absolutely see to solve this then I will post it. I think I just need a really good explanation of what is actually going on when the screen is rotated and how to correctly handle bitmaps and drawables. Note: This error does not pop up if I leave the app and then go back in, it only happens when the view is resized upon screen rotation.
I have about 7 bitmaps and 7 drawables, all created at launch and resized when the screen rotates. DOing that several times stops the app.
Some simplified code:
How I set up a Bitmap to a drawable. This one sets to a ClipDrawable as well:
//Full Bar
colorbarMap = BitmapFactory.decodeResource(res, R.drawable.colorbar);
colorbarDraw = new BitmapDrawable(res, colorbarMap);
colorbarDraw.setBounds(barX, barY, barX2, barY2);
colorbarClip = new ClipDrawable(colorbarDraw, Gravity.BOTTOM, ClipDrawable.VERTICAL);
colorbarClip.setBounds(barX, barY, barX2, barY2);
colorbarClip.setLevel(currentLevel);
//Empty Bar
colorbaremptyMap = BitmapFactory.decodeResource(res, R.drawable.colorempty);
colorbaremptyDraw = new BitmapDrawable(res, colorbaremptyMap);
colorbaremptyDraw.setBounds(barX, barY, barX2, barY2);
The above code runs once at the start of the view initializing based on this code:
private void init(){
//System
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.HONEYCOMB) {
setLayerType(View.LAYER_TYPE_SOFTWARE, null);
}
res = getResources();
options = new BitmapFactory.Options();
viewTreeObserver = getViewTreeObserver();
if (viewTreeObserver.isAlive()) {
viewTreeObserver.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
getViewTreeObserver().removeOnGlobalLayoutListener(this);
view_width = getWidth();
view_height = getHeight();
afterLayout();
}
});
}
}
The first code snippet runs under the method: afterLayout()
Nothing is done with the bitmaps after this. Each bitmap is initialized with an x and y location based on the view width and height. That location is edited using a rectangle to set its bounds for example a moving object.
If the Bitmap that to be displayed is small enough than use this or you don't need a high res image
bitmapOption = new BitmapFactory.Options();
bitmapOption.inScaled = true;
bitmapOption.inSampleSize = 2;
imageBitmap = BitmapFactory.decodeFile(imgFile.getAbsolutePath(), bitmapOption);
this will reduce the amount of size of bitmap see the bald guys explanation
optimizing bitmap
put your bitmap loading code in try finally block and use this at the end
try {
...
}finally {
if (imageBitmap != null) {
//recycle the bitmap to improve performance and avoid OO Exception
imageBitmap.recycle();
imageBitmap = null;
}
}
This will recycler the bitmap space taken by the image whenever GC is called
I have a AnimationDrawable that i initialise before starting the animation and recycle it right as it finishes.. the problem is that when i want to start the animation again, i reinitialise everything but still gives an exception
java.lang.RuntimeException: Canvas: trying to use a recycled bitmap android.graphics.Bitmap#2bbad018
here's my code
public ImageView radarImageView;
public AnimationDrawable animationDrawable;
public void animationStart() {
// animationStop();
radarImageView = (ImageView) findViewById(R.id.radarIV);
radarImageView.setBackgroundResource(R.drawable.sensor_animation);
animationDrawable = (AnimationDrawable) radarImageView.getBackground();
animationDrawable.start();
}
public void animationStop() {
animationDrawable.stop();
for (int i = 0; i < animationDrawable.getNumberOfFrames(); ++i){
Drawable frame = animationDrawable.getFrame(i);
if (frame instanceof BitmapDrawable) {
((BitmapDrawable)frame).getBitmap().recycle();
}
frame.setCallback(null);
}
animationDrawable.setCallback(null);
// animationDrawable = null;
// radarImageView.setBackgroundResource(0);
}
Why does it not reinitialise the whole thing again?
The problem is because when you Bitmap.recycle() is called on the bitmap you cannot reuse it.
Also as you are calling setBackgroundResources(R.drawable.sensor_animation) you are referring to the same object which its bitmaps were recycled previously.
As a solution, you either have to create a new drawable every time or do not recycle that instance.
If you are worries about the memory usage try to use smaller bitmaps. Also using the correct sizes for different screen densities would help.
It appears that, when I call setImageDrawable(null) for an ImageView, the bitmap is not being released. What can I do to force it to free the bitmap memory?
I have an activity that needs to display a large number of drawables, but not at the same time. I have extended ImageView as shown here. The XML layout file declares instances of this, with the "src" attribute set to "#null". In replacement, I have a custom attribute to hold the drawable resource id.
public class ImageViewHolder extends ImageView
{
int srcId = 0;
//-----------------------------------------------------------------------------
public ImageViewHolder (Context context, AttributeSet attrs)
{
super (context, attrs);
TypedArray a = context.obtainStyledAttributes (attrs, R.styleable.ImageViewHolder, 0, 0);
srcId = a.getResourceId (R.styleable.ImageViewHolder_src, 0);
a.recycle();
}
//-----------------------------------------------------------------------------
public void showDrawable (boolean makeVisible)
{
if (makeVisible)
{
// Drawable d = getResources().getDrawable (srcId);
// setImageDrawable (d);
setImageResource (srcId);
}
else // hide & free memory
{
Bitmap bitmap = ((BitmapDrawable)getDrawable()).getBitmap();
setImageResource(0);
bitmap.recycle();
// setImageDrawable (null);
}
}
}
I've tried using SetImagerResource() instead but get the same results. I also tried calling System.gc() after clearing the drawable and that only deferred the point where my device ran out of memory.
Any ideas?
Instead of using
setImageResource (srcId);
use
Resources r = getResources();
Bitmap b = BitmapFactory.decodeResource (r, srcId);
setImageBitmap (b);
This bypasses the Resources() caching and just recreates the bitmap each time you need it. After testing this code, I'm still eventually getting an out-of-memory error but it's taking much longer to get there. I'm going to assume it's being caused by something else. For now, that's enough time spent on this problem :)
i have problem in my app. When switching in landspace mode, the setContentView() method is called to show a piano keyboard. The piano keyboard class extends Surfaceview for better performance showing pressed keys. This SurfaceView class is added as child to my landscape layout:
RelativeLayout rootLayout = (RelativeLayout) findViewById(R.id.rootLayout);
RelativeLayout.LayoutParams relativeLayoutParams = new RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
relativeLayoutParams.addRule(RelativeLayout.BELOW, R.id.relativeLayoutLowerBar);
rootLayout.addView(keyboardSurfaceView, relativeLayoutParams);
In my keyboardSurfaceView class im loading bitmaps in an Bitmap array with the BitmapFactory.decodeResource() method and holding the bitmaps till the app is destroyed. This works great. When im swiching in portrait mode, i change layout with setContentView() and remove the keyboardSurfaceView :
if(keyboardSurfaceView != null && keyboardSurfaceView.getParent() != null)
((ViewGroup) keyboardSurfaceView.getParent()).removeView(keyboardSurfaceView);
The Bitmaps are loaded once. First time when switched in landscape mode. I encountered an out of memory error, when swichtin up to 10-20 times from protrait in landscape an so forth. When i update the heap in ddms view, i can see, that every time, when i switch from protrait view in landscape view, the heap size is growing up to ca. 20mb and then the app crashes. I dont know why this is happening all the time. The bitmaps are just loaded once and not every time.
I also tried bitmap.recycle(); bitmap = nullwithout success. Also tried to cache bitmaps with the LRUCache class as described in Google best practices. I also searched stackoverflow for a proper solution for my problem. Can still not fix this. I handle portrait/ landscape changes by myself (have onConfigurationChanged() method overriden). Putting all images in drawable-xhdpi helped a little. The heap size is growing as before, when changing orientation, but is still growing. Any help would be appreciated...
Exception:
02-13 22:44:09.419: E/dalvikvm-heap(935): 11448-byte external allocation too large for this process.
02-13 22:44:09.419: E/dalvikvm(935): Out of memory: Heap Size=16391KB, Allocated=13895KB, Bitmap Size=16394KB, Limit=32768KB
02-13 22:44:09.419: E/dalvikvm(935): Trim info: Footprint=16391KB, Allowed Footprint=16391KB, Trimmed=432KB
02-13 22:44:09.419: E/GraphicsJNI(935): VM won't let us allocate 11448 bytes
Here's Android document of how to handle bitmap efficiently
http://developer.android.com/training/displaying-bitmaps/index.html
ok first time when surfaceCreated is called in my surfaceView class, i call this method, and this method is called only once and not every time i show the surfaceView on screen:
bitmapKeyboard = BitmapFactory.decodeResource(context.getResources(), R.drawable.keyboard);
bitmapGlowImages[0] = BitmapFactory.decodeResource(context.getResources(), R.drawable.keycdown);
bitmapGlowImages[1] = BitmapFactory.decodeResource(context.getResources(), R.drawable.blackkeydown);
bitmapGlowImages[2] = BitmapFactory.decodeResource(context.getResources(), R.drawable.keyddown);
bitmapGlowImages[3] = BitmapFactory.decodeResource(context.getResources(), R.drawable.blackkeydown);
bitmapGlowImages[4] = BitmapFactory.decodeResource(context.getResources(), R.drawable.keyedown);
bitmapGlowImages[5] = BitmapFactory.decodeResource(context.getResources(), R.drawable.keycdown);
bitmapGlowImages[6] = BitmapFactory.decodeResource(context.getResources(), R.drawable.blackkeydown);
bitmapGlowImages[7] = BitmapFactory.decodeResource(context.getResources(), R.drawable.keyddown);
bitmapGlowImages[8] = BitmapFactory.decodeResource(context.getResources(), R.drawable.blackkeydown);
bitmapGlowImages[9] = BitmapFactory.decodeResource(context.getResources(), R.drawable.keyddown);
bitmapGlowImages[10] = BitmapFactory.decodeResource(context.getResources(), R.drawable.blackkeydown);
bitmapGlowImages[11] = BitmapFactory.decodeResource(context.getResources(), R.drawable.keyedown);
in this class in also run a seperate thread to draw pressed keys: with canvas.drawBitmap(bitmap, null, destRect, null). Finally, when onDestroy is called, my method recycleBitmaps() is called
public void recycleBitmaps() {
if(bitmapKeyboard != null) {
bitmapKeyboard.recycle();
bitmapKeyboard = null;
for(int i = 0; i < bitmapGlowImages.length; i++) {
bitmapGlowImages[i].recycle();
bitmapGlowImages[i] = null;
}
System.gc();
}
}
That's it. And every time in landscape, i add my surfaceView to my parent View in my landscape layout and in portrait i remove it again, and load my portrait layout. I make this in onConfigurationChanged(), because i handle orientation changes by myself, so the app is not destroyed and created every time when you change the device orientation.
my live wallpaper doesn't slide smoothly most of the time on home screen change.
there is not a complicate coding for the position of background. it just use the xpixles that located on the onoffsetschanged method to adjust the x position of the wallpaper.
sometime it slide well same as in the sun rise live wallpaper app. but most of the other time it slide.. dunno how to describe it but its like a static sharp move. is there any idea about that guys.
best regards.
private void drawS() {
final SurfaceHolder holder = getSurfaceHolder();
canvas = null;
try {
canvas = holder.lockCanvas();
if (canvas != null) {
background = BitmapFactory.decodeResource(getResources(),
R.drawable.background);
canvas.drawBitmap(background, mXPixels, mYPixels, null);
}
}
finally {
if (canvas != null) {
holder.unlockCanvasAndPost(canvas);
}
}
handler.removeCallbacks(runnableS);
if (visible) {
handler.postDelayed(runnableS, 25);
}
}
try to avoid resource creation in draw routine; remove this from drawS() function:
background = BitmapFactory.decodeResource(getResources(),
R.drawable.background);
and put it wherever you initialize your stuff and just use reference to background Bitmap later on.
If you take a look at logcat you'll see that there's a lot or resource shuffling and memory management going on (allocating memory for your resource, decoding bitmap image in it etc) every time you call your drawS(); ie every time you draw your background...
Cheers,
Ivica
I encountered the same problem and solved by following Ivica suggestion.
I moved the bitmap creation line inside the surface creation sub:
public void onSurfaceCreated(SurfaceHolder holder) {
super.onSurfaceCreated(holder);
mybackground = BitmapFactory.decodeResource(getResources(), R.drawable.mybg);
}
In this way the sliding is very fluent