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.
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
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 :)
Recently, I started using Google ImageWorker class for loading bitmaps on a background thread. This class handles everything, including putting the bitmaps in the ImageView. Google talks about this class (and it's helper classes for Image manipulation and caching) here: http://developer.android.com/training/displaying-bitmaps/process-bitmap.html
The ImageView(s) in my case are parts of list items in a ListView. Here's the interesting part of getView on my ArrayAdapter:
#Override
public View getView(final int position, View convertView, ViewGroup parent) {
View view = convertView == null ? inflater.inflate(R.layout.nodelist_item, null) : convertView;
NodeHolder holder = getHolder(view);
Node node = mList.get(position);
holder.txtTitle.setText(node.Title);
//Setting the rest of the different fields ...
//And finally loading the image
if(node.hasArt())
worker.loadImage(node.ImageID, holder.imgIcon);
}
The entire getView method can be seen here: http://pastebin.com/CJbtVfij
The worker object is a very simple implementation of the ImageWorker:
public class NodeArtWorker extends ImageWorker {
protected int width;
protected int height;
public NodeArtWorker(Context context) {
super(context);
addImageCache(context);
//Code for getting the approximate width and height of the ImageView, in order to scale to save memory.
}
#Override
protected Bitmap processBitmap(Object data) {
Bitmap b = getBitmap(data);
if(b == null) return null;
return Utils.resizeBitmap(b, width, height);
}
protected Bitmap getBitmap(Object data) {
//Downloads the bitmap from a server, and returns it.
}
}
This works very well, and the performance is much better now than before. However, if I change the ImageID on some of the items in the list, and call adapter.notifyDataSetChanged() to rebuild the view (and thereby start loading new bitmaps), I get a RuntimeException:
0 java.lang.RuntimeException: Canvas: trying to use a recycled bitmap android.graphics.Bitmap#41adf448
1 at android.graphics.Canvas.throwIfRecycled(Canvas.java:1058)
2 at android.graphics.Canvas.drawBitmap(Canvas.java:1159)
3 at android.graphics.drawable.BitmapDrawable.draw(BitmapDrawable.java:440)
4 at android.widget.ImageView.onDraw(ImageView.java:1025)
5 at android.view.View.draw(View.java:14126)
The full stacktrace can be seen in the following pastebin entry, but is probably uninteresting, as it is the typical Android view hierarchy redraw stacktrace: http://pastebin.com/DsWcidqw
I get that the Bitmaps are being recycled, but I don't understand exactly where and what can be done about it. As my code comes directly from Google, and is, as far as I understand, the recommended way of doing this, I am very confused.
On Android, Bitmaps can be recycled to be later re-used (much faster than re-creating a Bitmap).
The Bitmap#recycle()method will flag the Bitmap as recycled.
So if you try to set such a recycled bitmap to an ImageView or draw it on a Canvas, you will end up with this exception:
java.lang.RuntimeException: Canvas: trying to use a recycled bitmap
The official demo that you linked on your question is dealing with recycled Bitmaps.
It uses a dedicated method hasValidBitmap() and checks the Bitmap#isRecycled() value:
private synchronized boolean hasValidBitmap() {
Bitmap bitmap = getBitmap();
return bitmap != null && !bitmap.isRecycled();
}
So you need to do the same. When you're searching on your cache for a Bitmap, before applying it to an ImageView, check if it's recycled or not. If it's not you can directly set it, otherwise, you need to update the Bitmap.
To create a new Bitmap from a recycled Bitmap, you can use the Bitmap::createBitmap(Bitmap) method.
You can find more details on this page.
i think this is not the ImageWorker class bug, this is
com.mobeta.android.dslv.DragSortListView.dispatchDraw(DragSortListView.java:797)
issue. so in order to see my answer is correct or not just remove that library and see if it works or not. after that i think the best solutions are:
1- not use that library.
2- report issue on github.
3- solve it by your own and debugge it.
4- or you can set null for image holder bitmap
holder.imgIcon.setBitmapDrawble(null);
and then call worker.loadImage.
There is an ImageView and a GLSurfaceView setup with 'match_parent' parameters on the screen. The ImageView gets populated quickly and shows up on the screen. Then, the GLSurfaceView gets populated on top of it with modifications to the image. So far so good.
However, these views also live inside of a ViewPager with images to the left and right of them. Before introducing the OpenGL Surface View on top of the ImageView, the views would transition out to the left and right as expected. But after introducing the GLSurfaceView on top of it, as soon as the user starts to swipe their fingers to move to the next image, the SurfaceView becomes transparent causing the image to disappear. It even forces the image below it to disappear in the ImageView. Showing the background of the parent view.
I am unclear on how to approach this issue. It would be nice if the texture slid out to the side just like the ImageView or even be transparent but leave the ImageView behind it visible would be fine.
Even GLSurfaceView.RENDERMODE_CONTINUOSLY doesn't keep the texture around during the transition.
It would seem that the reason that I cannot get the effect that I am trying to achieve is because the GLSurfaceView is always placed behind other views causing transparency on the foreground views. Thus, the GLSurfaceView would have priority over being drawn. In order to achieve the effect that I would want, I probably need to be sure to use View.setVisibility( ) for invisible when swiping and set it back to visible after the ViewPager settles. That is the current conclusion that I have come across after seeing a couple other links as a by-product of different research.
z-order for GLSurfaceViews
scrolling in GLSurfaceViews
It doesn't seem as though the GLSurfaceView class really performs as similar to a view as I had been originally expecting. So a little extra care will need to be taken in order to progress further to keep the ImageView visible transitioning between images in the ViewPager.
Within a viewPager I managed to accomplish something along the lines of the following code. This is still in a rough state of course since it was in the middle of testing, but it accomplishes what I am looking for. Which is for a texture to start loading when we begin to settle on an image, and then disappear revealing the original image below it once the user begins to leave the image. And this seems to work without tearing or flickers which is how I would expect for it to behave.
#Override
public void onPageScrollStateChanged(int state)
{
switch(state)
{
case ViewPager.SCROLL_STATE_DRAGGING:
Log.i("photoViewer:","dragging ViewPager");
if(photoSurfaceView_ != null && photoViewFrameLayout != null)
{
photoSurfaceView_.setAlpha(0);
photoSurfaceView_.invalidate();
}
break;
case ViewPager.SCROLL_STATE_IDLE:
photoSurfaceView_.setVisibility(View.VISIBLE);
photoSurfaceView_.setAlpha(1);
Log.i("photoViewer:","idle ViewPager");
break;
case ViewPager.SCROLL_STATE_SETTLING:
int childCount = photoViewFrameLayout.getChildCount();
if(childCount >= 2)
{
photoViewFrameLayout.removeViews(1, childCount-1);
}
new Thread(new Runnable() {
#Override
public void run()
{
Looper.prepare();
final Bitmap openGlBitmap = BitmapFactoryUtils.resizeAsNecessaryForOpenGLtexture(photoPaths[photoViewPager_.getCurrentItem()], new BitmapFactory.Options());
Rect bounds = BitmapFactoryUtils.calculateBoundsForBitmap(activityContext, openGlBitmap);
int scaledHeight = bounds.height();
int scaledWidth = bounds.width();
photoSurfaceView_ = new PhotoViewSurfaceView(activityContext, openGlBitmap);
photoSurfaceView_.setLayoutParams(new LayoutParams(scaledWidth, scaledHeight, Gravity.CENTER));
photoSurfaceView_.setVisibility(View.VISIBLE);
photoSurfaceView_.setAlpha(0);
runOnUiThread(new Runnable() {
#Override
public void run()
{
photoViewFrameLayout.addView(photoSurfaceView_);
}
});
}
}).start();
Log.i("photoViewer:","settling ViewPager");
break;
}
Edit: As requested I wanted to provide some additional input as to how to resize the texture for an OpenGL SurfaceView that will use a Bitmap for input.
The following is a quick snippet of some of the code available for the first step in the process in checking against whether or not the texture size of the current Bitmap will fit within a single texture. If you want to tile the texture to keep the image larger, then this gets more complicated and this was not what was needed for my resolution.
The texture size can be retrieved with the following code:
private void assignTextureSize()
{
/* The maximum texture size can be found within the OpenGL context and then shared amongst other applications. */
int[] maxTextureSize = new int[1];
GLES20.glGetIntegerv(GLES20.GL_MAX_TEXTURE_SIZE, maxTextureSize, 0);
maxTextureSize = maxTextureSize[0];
}
but only from within the Renderer class and for my case I waited until within the onDrawFrame of the Texture to do the assignment; before a Bitmap has been assigned to it.
Then, the goal of the Bitmap being loaded for me was to simply load what I could without running out of memory. For this, I simply modified the Options of the Bitmap that I was about to load and try and load it within a try catch block. If it ran out of memory, I would try it again, but with a smaller footprint.
try
{
final int sizeFactors[] = { 1, 2, 4, 8, 16, 32 };
if (new File(bitmapFilePath).exists())
{
BitmapFactory.Options options = new BitmapFactory.Options();
for (int i = 0; i < sizeFactors.length; ++i)
{
try
{
options.inSampleSize = sizeFactors[i];
Bitmap bmp = BitmapFactory.decodeFile(bitmapFilePath, options);
if(bmp.getHeight() > maximumTextureSize ||
bmp.getWidth() > maximumTextureSize)
{
continue;
}
/*
* #category Check against EXIF data if the image needs to be rotated or not for viewing.
*/
Matrix matrix = new Matrix ();
ExifInterface exif = new ExifInterface(bitmapFilePath);
int orientation = exif.getAttributeInt (ExifInterface.TAG_ORIENTATION, 1);
switch(orientation)
{
case ExifInterface.ORIENTATION_NORMAL:
break;
case ExifInterface.ORIENTATION_ROTATE_90:
matrix.postRotate (90);
break;
case ExifInterface.ORIENTATION_ROTATE_180:
matrix.postRotate (180);
break;
case ExifInterface.ORIENTATION_ROTATE_270:
matrix.postRotate (270);
break;
default:
}
bmp = Bitmap.createBitmap (bmp, 0, 0, bmp.getWidth (), bmp.getHeight (), matrix, true);
return bmp;
} catch (OutOfMemoryError outOfMemory)
{
//Do nothing, it will return when one that doesn't run out of memory is loaded. Or you could do additional checking to bail if needed, etc.
}
}
throw new Exception("Not enough memory for loading image");
}
} catch (Exception exception)
{
Log.e("CustomLogTag", "Exception for loading the image that was selected for extraction.");
}
There are many ways to accomplish this, but this is one approach to take and I wanted to add clarity and additional information to this previous post as requested since it did not contain as much information on my accepted answer as I would have like either.
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?