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
Related
I'm using androidplot to display a fixed-sized graph, which I've setted up a BitmapShader to match each Range interval and I need to set this shader to be always displayed on the graph, since it's beginning.
My problem is:
I can't intialize the graph with this shader (I've tested it using as base the DemoApp and the shader is working properly). Every time I try to get the GridRect using the method getGridRect() it returns null, no matter where I call this function during the activity creation. I can only set the shadder after the activity is created, but only with a button or something like it.
I searched through the entire source code but could not find where this measure occurs during the activity's lifecycle.
Unfortunately, I was unable the proper way of getting the gridrect mesured during it's creatiion, but I've found a way around.
I was able to define the graph height by getting the XYPlot view-height and subtract the graph top and bottom padding.
A snippet from my code:
#Override
protected void onCreate(Bundle savedInstanceState) {
...
mPlot.getGraphWidget().setPaddingBottom(20);
mPlot.getGraphWidget().setPaddingTop(20);
//Added a ViewTreeObserver to be sure when the Plot was measured.
mPlot.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
mPlot.getViewTreeObserver().removeOnGlobalLayoutListener(this);
//Avoid the factory trying to scale bitmap according to Display density
Options options = new BitmapFactory.Options();
options.inScaled = false;
Bitmap bm = Bitmap.createScaledBitmap(BitmapFactory.decodeResource(getResources(), R.drawable.chartbackground, options), 1, (int) mPlot.getHeight() - 40, false);
BitmapShader backgroundShader = new BitmapShader(bm, Shader.TileMode.REPEAT, Shader.TileMode.REPEAT);
Matrix shaderMatrix = new Matrix();
//This matrix references always stayed the same even if the plot changed its size.
shaderMatrix.setTranslate(49.6875f, 30.75f);
backgroundShader.setLocalMatrix(shaderMatrix);
mPlot.getGraphWidget().getGridBackgroundPaint().setShader(backgroundShader);
//Setting the background to be a little bit translucid.
mPlot.getGraphWidget().getGridBackgroundPaint().setAlpha(90);
}
});
...
}
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 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.
I'm looking for ideas on how to optimize the following code which is the body of a for loop:
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 4;
options.inJustDecodeBounds = false;
ImageButton view = new ImageButton(this);
view.setBackgroundColor(0);
view.setOnClickListener(new OnClickListener(){
#Override
public void onClick(View v) {
finalThis.onBorderClicked(v);
}
});
view.setTag(i); //i is the loop counter
Bitmap big = BitmapFactory.decodeResource(getResources(), borders[i], options);
Bitmap smaller = Bitmap.createScaledBitmap(big, borderDimension, borderDimension, false);
view.setImageBitmap(smaller);
view.setLayoutParams(new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.FILL_PARENT));
((LinearLayout.LayoutParams)view.getLayoutParams()).setMargins(0, 0, -5, 0);
group.addView(view);
big.recycle();
Basically I've got images that are 600x600 and I want to get them to size of around 50dp. So naturally, I first set the sample size to 4 (600 / 4 = 150), so that I don't occupy unnecessary memory and then further resize the image down to the specified size. I then use it to load it onto an ImageButton, and also I recycle the bigger, now useless Bitmap.
This is a dynamic UI creation code, the ImageButtons are added to a HorizontalScrollView. Thing is, in one case, I've got to add more than hundred ImageButtons to the UI and this code is awfully slow.
The question is: how can I make it work faster? It is currently executed on the MainActivity, could multithreading (perhaps AsyncTask) help in this instance? And is there any other way that would possibly be faster for getting the Bitmaps to the desired size? If there is no way to make this process significantly faster, then is there a way to progressively add the ImageButtons one by one (but in the same order) so that the UI is not frozen when all of this happens?
Taking your code off the main thread will not make the application process the images any faster, but it will keep the UI responsive to the user while that process is taking place and is absolutely a necessary step when you have this much data to load at once. However, keep in mind that you cannot manipulate the view hierarchy from any thread other than the main thread, so you cannot just place this whole operation inside of an AsyncTask.doInBackground(). You will have to split out the lines that take the most time (i.e. your decodeResource() and createScaledBitmap() calls), but the code to instantiate and add views to the parent will need to happen in onPostExecute() or publishProgress() (depending on how you implement the task logic).
Thing is, in one case, I've got to add more than hundred ImageButtons to the UI and this code is awfully slow.
Another important thing to keep in mind is that having upwards of 100 image-based views in your hierarchy at any one time is still a large chunk wasted memory. The best way to speed up the process is not to add all the views at once, but only add them as they are needed (i.e. when the user scrolls over) and then remove/recycle views that are no longer on screen. This is how AdapterView implementations work for this very reason, use only the memory you need and your app will be both faster and more efficient.
I realize there is no HorizontalListView in the framework, but there are 3rd party implementations that provide this functionality for you, or you can look at the source for one of the framework classes to build your own from.
You don't need to scale the image by yourself. You can do this by the asking the ImageButton to scale for you, which is much faster.
ImageButton view = new ImageButton(this);
view.setBackgroundColor(0);
view.setOnClickListener(new OnClickListener(){
#Override
public void onClick(View v) {
finalThis.onBorderClicked(v);
}
});
view.setTag(i); //i is the loop counter
view.setAdjustViewBounds(true);
view.setMaxHeight(borderDimension);
view.setMaxWidth(borderDimension);
view.setImageResource(border[i]);
view.setLayoutParams(new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
((LinearLayout.LayoutParams)view.getLayoutParams()).setMargins(0, 0, -5, 0);
group.addView(view);
I have changed the layout params to use WRAP_CONTENT for height, because I do not know how does your image look like. You can play around with the layout a bit, but this generally should do the trick.
I found a good solution at http://developer.android.com/training/displaying-bitmaps/index.html
It has following code, which dynamically set Bitmap to ImageView.
class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
private final WeakReference<ImageView> imageViewReference;
private int data = 0;
public BitmapWorkerTask(ImageView imageView) {
// Use a WeakReference to ensure the ImageView can be garbage collected
imageViewReference = new WeakReference<ImageView>(imageView);
}
// Decode image in background.
#Override
protected Bitmap doInBackground(Integer... params) {
data = params[0];
return decodeSampledBitmapFromResource(getResources(), data, 100, 100));
}
// Once complete, see if ImageView is still around and set bitmap.
#Override
protected void onPostExecute(Bitmap bitmap) {
if (imageViewReference != null && bitmap != null) {
final ImageView imageView = imageViewReference.get();
if (imageView != null) {
imageView.setImageBitmap(bitmap);
}
}
}
}
You can call this by using,
public void loadBitmap(int resId, ImageView imageView) {
BitmapWorkerTask task = new BitmapWorkerTask(imageView);
task.execute(resId);
}
Here, just change ImageView to ImageButton.
Edit:
public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,
int reqWidth, int reqHeight) {
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(res, resId, options);
// Calculate inSampleSize
options.inSampleSize = 4;
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
return BitmapFactory.decodeResource(res, resId, options);
}
You can find more about handling Bitmaps Efficiently here
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?