Android "Trying to use recycled bitmap" error? - android

I am running into a problem with bitmaps on an Android application I am working on. What is suppose to happen is that the application downloads images from a website, saves them to the device, loads them into memory as bitmaps into an arraylist, and displays them to the user. This all works fine when the application is first started. However, I have added a refresh option for the user where the images are deleted, and the process outlined above starts all over.
My problem: By using the refresh option the old images were still in memory and I would quickly get OutOfMemoryErrors. Thus, if the images are being refreshed, I had it run through the arraylist and recycle the old images. However, when the application goes to load the new images into the arraylist, it crashes with a "Trying to use recycled bitmap" error.
As far as I understand it, recycling a bitmap destroys the bitmap and frees up its memory for other objects. If I want to use the bitmap again, it has to be reinitialized. I believe that I am doing this when the new files are loaded into the arraylist, but something is still wrong. Any help is greatly appreciated as this is very frustrating. The problem code is below. Thank you!
public void fillUI(final int refresh) {
// Recycle the images to avoid memory leaks
if(refresh==1) {
for(int x=0; x<images.size(); x++)
images.get(x).recycle();
images.clear();
selImage=-1; // Reset the selected image variable
}
final ProgressDialog progressDialog = ProgressDialog.show(this, null, this.getString(R.string.loadingImages));
// Create the array with the image bitmaps in it
new Thread(new Runnable() {
public void run() {
Looper.prepare();
File[] fileList = new File("/data/data/[package name]/files/").listFiles();
if(fileList!=null) {
for(int x=0; x<fileList.length; x++) {
try {
images.add(BitmapFactory.decodeFile("/data/data/[package name]/files/" + fileList[x].getName()));
} catch (OutOfMemoryError ome) {
Log.i(LOG_FILE, "out of memory again :(");
}
}
Collections.reverse(images);
}
fillUiHandler.sendEmptyMessage(0);
}
}).start();
fillUiHandler = new Handler() {
public void handleMessage(Message msg) {
progressDialog.dismiss();
}
};
}

You don't actually need to call recycle method here. Refresh button should just clear the array, garbage collector will free the memory later. If you get OutOfMemory it means some other objects are still referencing your old images and Garbage Collector can't remove them.
I may asume that some ImageViews display your bitmaps and they keep references to that bitmaps. You can't remove old bitmaps while they're still displayed. So a possible solution is to clear ImageVIews too. After that you can clear the array and fill it with new images.
Recycle frees the memory but some ImageView is still displaying the bitmap and it can't do that after recycle, that's why you get "Trying to use recycled bitmap".
These all are just an assumptions because I can't see your complete code.

If the memory is very large, you'd better recycle the bitmap yourself.
GC can't be controled.

Related

Glide inconsistent loading in for loop

I am writing an Android app where I want to display a consistent stream using the newest Glide 4.11.0. My idea was that I display every 100 ms a new image using a for loop where I call the Glide function. When I start my function loadImageFromURL() it runs through and afterwards display all the images as fast as possible after each other, but I want to get it displayed evenly with a time gap of around 100 ms.
What am I doing wrong? Is there a problem with an internal thread of Glide?
My Code:
public void loadImageFromURL() {
Glide.get(this).setMemoryCategory(MemoryCategory.HIGH); //use more RAM than normal
final long Start = System.currentTimeMillis();
for (int i = 0; i < 5; imageNumber++) { //for loop for displaying continuously stream
if (System.currentTimeMillis() > Start + i * 100) {
Glide.with(this).asBitmap().load(ImageURL).diskCacheStrategy(DiskCacheStrategy.NONE).override(ScreenSizeX, ScreenSizeY).into(new CustomTarget<Bitmap>(){
#Override
public void onResourceReady(#NonNull Bitmap resource, #Nullable Transition<? super Bitmap> transition) {
_imageView.setImageBitmap(resource);
imageNumber++;
}
#Override
public void onLoadCleared(#Nullable Drawable placeholder) {
}
});
i++;
}
}
Glide.get(this).setMemoryCategory(MemoryCategory.NORMAL); //RAM usage back to normal
}
There are multiple flaws with your code. Glide downloads the images in a separate thread, so it's running the download code independently from your loop. Some issues:
You are manipulating imageNumber from multiple threads. This will cause random time-sensitive issues. You should not increment imageNumber in onResourceReady
The loop will just infinitely loop until the desired time is reached. This will cause heavy CPU load and can be solved by using locks, waits or similar.
My suggestion: Try loading each image after another synchronously with the solution from this answer. When an image has loaded, calculate how much time remains for your "image load break" to pass and use Thread.sleep(milliSeconds) to wait out the remaining time. Then you can update the imageView.
Make sure you run this code in a separate thread and use runOnUIThread when updating the imageView.
You can probably further optimize this by using a ConcurrentQueue where the download of the images all happen at once and a separate consumer thread takes from the queue and waits in between. This will probably have higher memory usage though because the images will be also stored in the queue.

Clear background after start new activity

I had a problem with lack of memory thus I decided to clean the background before starting a new activity.
It works, but I have the bad side-effect that I have a black screen before a new activity is started.
This is my code:
I set it here:
#Override
protected void onResume() {
super.onResume();
bg.setBackgroundDrawable(new BitmapDrawable(decodeSampledBitmapFromResource(getResources(), R.drawable.bg, sizeWigth, sizeHeight)));
}
I clear it here:
#Override
protected void onPause() {
super.onPause();
bg.setBackgroundDrawable(null);
System.gc();
}
Have you any idea to fix it?
When you loading large image make sure you follow following rules
Load the image based on your Screen DPI. if your screen is small their no reason load a huge image into memory.
Bitmap Sampling. read this for Sub-sampling a large image
Monitor your HeapSize and make you have enough space after loading the image to do other processing. Check this link
load image using java references. Make sure to reload the image when it is null. This will help you to avoid out of memory issue.
System.gc();
is a hints to garbage collector and and their are no guarantee that garbage collector will collect garbage after execution of this line.
Putting this image on XML layout will not help you with memory issue.

Bitmap / LayerDrawable leak on multiple FAST screen rotations

I read a lot of posts and comments on the Internet about Bitmap and Memory Leaks on screen rotation.
Actually, the issue i am experiencing is quite particular...
I use Eclipse DDMS and I am watching at the heap memory occupied by 1 byte arrays, which are related to my bitmaps placed in four ImageView, each one of them in a page of a ViewFlipper.
The images are loaded from internal memory and are 216-220KB big, and I use a layer drawable in order to draw another transparent image on top of them.
Keeping an eye on the heap memory occupied by the bitmaps, if I change the orientation of the device, the memory increases of some MB. Causing 2-3 GC, in a few moments the amount of memory decreases to the initial value.
Slowly repeating the process (change orientation + 2-3 Force Garbage Collection), the amount of memory increases but then goes back at the same value.
This makes me think I am doing it right with Bitmap management in my application.
But if I start rotating repeatedly and quickly the device, I see the amount of memory increasing continuously.... and subsequent GC will only be able to reduce the heap occupation by a little amount of memory in comparison to its rapid growth during fast rotation.
Is Android unable to Garbage collecting so fast?
Why am I able to keep memory usage stable if I slowly rotate and not if I repeatedly rotate
faster without forcing GC for a while?
Android 4.1.2 / Samsung Galaxy S3.
The code below is called whenever a png is read from internal storage or downloaded by
an AsyncTask from the internet.
Disabling AsyncTasks (thus excluding pending tasks through subsequent rotations) does not
change the scene, it is enough to load the two "layers" from the internal storage at
startup to quickly fill the heap while rotating the device.
public void setBitmap(Bitmap bitmap)
{
try{
Drawable layers[] = new Drawable[2];
Bitmap fvgBackgroundBmp = BitmapFactory.decodeResource(getResources(), R.drawable.fvg_background2);
layers[0] = new BitmapDrawable(getResources(), fvgBackgroundBmp);
layers[1] = new BitmapDrawable(getResources(), bitmap);
LayerDrawable layerDrawable = new LayerDrawable(layers);
setImageDrawable(layerDrawable);
mLayerDrawableAvailable = true;
}
catch(Exception outOfMemory)
{
Log.e("setBitmap got exception", outOfMemory.getLocalizedMessage());
}
}
The method unbindDrawables() called from the Activity's onDestroy() does not help:
public void unbindDrawables()
{
if(mLayerDrawableAvailable)
{
LayerDrawable lDrawable = (LayerDrawable) getDrawable();
if(lDrawable != null)
{
lDrawable.getDrawable(1).setCallback(null);
lDrawable.getDrawable(0).setCallback(null);
lDrawable.setCallback(null);
}
}
}
onDestroy is not guaranteed to be called when memory pressure is high. This might explain it. Try calling unbindDrawables from onStop()
http://developer.android.com/training/basics/activity-lifecycle/stopping.html#Stop
I would try to get back Bitmap from Your layers drawable and recycle it.
public void unbindDrawables()
{
if(mLayerDrawableAvailable)
{
LayerDrawable lDrawable = (LayerDrawable) getDrawable();
if(lDrawable != null)
{
((BitmapDrawable)lDrawable.getDrawable(1)).getBitmap().recycle();
((BitmapDrawable)lDrawable.getDrawable(0)).getBitmap().recycle();
lDrawable.setCallback(null);
}
}
}
Try to call unbindDrawables() in body of onDestroy() event in Your activity.

Android Bitmap Cache

I'm working on implementing a cache for a lot of bitmap tiles I have. What I've done so far:
Successfully implemented a LRU Cache system, but the tiles still load slowly when they must be loaded from the app's resources. The cache currently has about an 85% hit rate.
Whenever I must load the bitmap from resources, it is still slow like I said. With this in mind, I am now loading the Bitmaps from an Async task. Before this, everything would load without error, but it was fairly slow. Now, it's faster since it's not working on the main thread, but I inevitably run into an OOM error. Here's the code for my Async task:
public class loadBitmap extends AsyncTask<Void,Void,Void>
{
Bitmap bit;
#Override
protected Void doInBackground(Void... params)
{
Options opts = new Options();
bit = BitmapFactory.decodeResource(reso, drawable, opts);
return null;
}
#Override
protected void onPostExecute(Void result)
{
// TODO Auto-generated method stub
drawLoadedBit(bit);
super.onPostExecute(result);
}
}
Any ideas on how I can implement this so I don't get the Out of Memory error? Since this is called in the draw method, I'm thinking that the multiple calls to it are causing it. Thanks for any advice.
Refer to this link
He gives a good tutorial on using regenative bitmaps. Further, to decouple the bitmap from the view [once the view is disposed], you can #Override View#onRemovedFromWindow() to recycle the bitmap. Going even further, if you still have this issue, you can create a BitmapPool in which you allocate your bitmaps. You can implement an algorithm go calculate the sizes of the bitmaps and release older bitmaps that would push you over an arbitrary memory amount (bitamp memory is about width*height*4 + object size which should be nominal)
When the Bitmap is loaded, keep it around in a class variable. Then next time a draw is requested, check to see if the class variable is non-null before loading it from resources again.

java.lang.OutOfMemoryError: bitmap size exceeds VM budget - Android

I developed an application that uses lots of images on Android.
The app runs once, fills the information on the screen (Layouts, Listviews, Textviews, ImageViews, etc) and user reads the information.
There is no animation, no special effects or anything that can fill the memory.
Sometimes the drawables can change. Some are android resources and some are files saved in a folder in the SDCARD.
Then the user quits (the onDestroy method is executed and app stays in memory by the VM ) and then at some point the user enters again.
Each time the user enters to the app, I can see the memory growing more and more until user gets the java.lang.OutOfMemoryError.
So what is the best/correct way to handle many images?
Should I put them in static methods so they are not loaded all the time?
Do I have to clean the layout or the images used in the layout in a special way?
One of the most common errors that I found developing Android Apps is the “java.lang.OutOfMemoryError: Bitmap Size Exceeds VM Budget” error. I found this error frequently on activities using lots of bitmaps after changing orientation: the Activity is destroyed, created again and the layouts are “inflated” from the XML consuming the VM memory available for bitmaps.
Bitmaps on the previous activity layout are not properly de-allocated by the garbage collector because they have crossed references to their activity. After many experiments I found a quite good solution for this problem.
First, set the “id” attribute on the parent view of your XML layout:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:id="#+id/RootView"
>
...
Then, on the onDestroy() method of your Activity, call the unbindDrawables() method passing a reference to the parent View and then do a System.gc().
#Override
protected void onDestroy() {
super.onDestroy();
unbindDrawables(findViewById(R.id.RootView));
System.gc();
}
private void unbindDrawables(View view) {
if (view.getBackground() != null) {
view.getBackground().setCallback(null);
}
if (view instanceof ViewGroup) {
for (int i = 0; i < ((ViewGroup) view).getChildCount(); i++) {
unbindDrawables(((ViewGroup) view).getChildAt(i));
}
((ViewGroup) view).removeAllViews();
}
}
This unbindDrawables() method explores the view tree recursively and:
Removes callbacks on all the background drawables
Removes children on every viewgroup
It sounds like you have a memory leak. The problem isn't handling many images, it's that your images aren't getting deallocated when your activity is destroyed.
It's difficult to say why this is without looking at your code. However, this article has some tips that might help:
http://android-developers.blogspot.de/2009/01/avoiding-memory-leaks.html
In particular, using static variables is likely to make things worse, not better. You might need to add code that removes callbacks when your application redraws -- but again, there's not enough information here to say for sure.
To avoid this problem you can use native method Bitmap.recycle() before null-ing Bitmap object (or setting another value). Example:
public final void setMyBitmap(Bitmap bitmap) {
if (this.myBitmap != null) {
this.myBitmap.recycle();
}
this.myBitmap = bitmap;
}
And next you can change myBitmap w/o calling System.gc() like:
setMyBitmap(null);
setMyBitmap(anotherBitmap);
I've ran into this exact problem. The heap is pretty small so these images can get out of control rather quickly in regards to memory. One way is to give the garbage collector a hint to collect memory on a bitmap by calling its recycle method.
Also, the onDestroy method is not guaranteed to get called. You may want to move this logic/clean up into the onPause activity. Check out the Activity Lifecycle diagram/table on this page for more info.
This explanation might help:
http://code.google.com/p/android/issues/detail?id=8488#c80
"Fast Tips:
1) NEVER call System.gc() yourself. This has been propagated as a fix here, and it doesn't work. Do not do it. If you noticed in my explanation, before getting an OutOfMemoryError, the JVM already runs a garbage collection so there is no reason to do one again (its slowing your program down). Doing one at the end of your activity is just covering up the problem. It may causes the bitmap to be put on the finalizer queue faster, but there is no reason you couldn't have simply called recycle on each bitmap instead.
2) Always call recycle() on bitmaps you don't need anymore. At the very least, in the onDestroy of your activity go through and recycle all the bitmaps you were using. Also, if you want the bitmap instances to be collected from the dalvik heap faster, it doesn't hurt to clear any references to the bitmap.
3) Calling recycle() and then System.gc() still might not remove the bitmap from the Dalvik heap. DO NOT BE CONCERNED about this. recycle() did its job and freed the native memory, it will just take some time to go through the steps I outlined earlier to actually remove the bitmap from the Dalvik heap. This is NOT a big deal because the large chunk of native memory is already free!
4) Always assume there is a bug in the framework last. Dalvik is doing exactly what its supposed to do. It may not be what you expect or what you want, but its how it works. "
I had the exact same problem. After a few testing I found that this error is appearing for large image scaling. I reduced the image scaling and the problem disappeared.
P.S. At first I tried to reduce the image size without scaling the image down. That did not stop the error.
Following points really helped me a lot. There might be other points too, but these are very crucial:
Use application context(instead of activity.this) where ever possible.
Stop and release your threads in onPause() method of activity
Release your views / callbacks in onDestroy() method of activity
I suggest a convenient way to solve this problem.
Just assign the attribute "android:configChanges" value as followed in the Mainfest.xml for your errored activity.
like this:
<activity android:name=".main.MainActivity"
android:label="mainActivity"
android:configChanges="orientation|keyboardHidden|navigation">
</activity>
the first solution I gave out had really reduced the frequency of OOM error to a low level. But, it did not solve the problem totally. And then I will give out the 2nd solution:
As the OOM detailed, I have used too much runtime memory. So, I reduce the picture size in ~/res/drawable of my project. Such as an overqualified picture which has a resolution of 128X128, could be resized to 64x64 which would also be suitable for my application. And after I did so with a pile of pictures, the OOM error doesn't occur again.
I too am frustrated by the outofmemory bug. And yes, I too found that this error pops up a lot when scaling images. At first I tried creating image sizes for all densities, but I found this substantially increased the size of my app. So I'm now just using one image for all densities and scaling my images.
My application would throw an outofmemory error whenever the user went from one activity to another. Setting my drawables to null and calling System.gc() didn't work, neither did recycling my bitmapDrawables with getBitMap().recycle(). Android would continue to throw the outofmemory error with the first approach, and it would throw a canvas error message whenever it tried using a recycled bitmap with the second approach.
I took an even third approach. I set all views to null and the background to black. I do this cleanup in my onStop() method. This is the method that gets called as soon as the activity is no longer visible. If you do it in the onPause() method, users will see a black background. Not ideal. As for doing it in the onDestroy() method, there is no guarantee that it will get called.
To prevent a black screen from occurring if the user presses the back button on the device, I reload the activity in the onRestart() method by calling the startActivity(getIntent()) and then finish() methods.
Note: it's not really necessary to change the background to black.
The BitmapFactory.decode* methods, discussed in the Load Large Bitmaps Efficiently lesson, should not be executed on the main UI thread if the source data is read from disk or a network location (or really any source other than memory). The time this data takes to load is unpredictable and depends on a variety of factors (speed of reading from disk or network, size of image, power of CPU, etc.). If one of these tasks blocks the UI thread, the system flags your application as non-responsive and the user has the option of closing it (see Designing for Responsiveness for more information).
Well I've tried everything I found on the internet and none of them worked. Calling System.gc() only drags down the speed of app. Recycling bitmaps in onDestroy didn't work for me too.
The only thing that works now is to have a static list of all the bitmap so that the bitmaps survive after a restart. And just use the saved bitmaps instead of creating new ones every time the activity if restarted.
In my case the code looks like this:
private static BitmapDrawable currentBGDrawable;
if (new File(uriString).exists()) {
if (!uriString.equals(currentBGUri)) {
freeBackground();
bg = BitmapFactory.decodeFile(uriString);
currentBGUri = uriString;
bgDrawable = new BitmapDrawable(bg);
currentBGDrawable = bgDrawable;
} else {
bgDrawable = currentBGDrawable;
}
}
I had the same problem just with switching the background images with reasonable sizes. I got better results with setting the ImageView to null before putting in a new picture.
ImageView ivBg = (ImageView) findViewById(R.id.main_backgroundImage);
ivBg.setImageDrawable(null);
ivBg.setImageDrawable(getResources().getDrawable(R.drawable.new_picture));
FWIW, here's a lightweight bitmap-cache I coded and have used for a few months. It's not all-the-bells-and-whistles, so read the code before you use it.
/**
* Lightweight cache for Bitmap objects.
*
* There is no thread-safety built into this class.
*
* Note: you may wish to create bitmaps using the application-context, rather than the activity-context.
* I believe the activity-context has a reference to the Activity object.
* So for as long as the bitmap exists, it will have an indirect link to the activity,
* and prevent the garbaage collector from disposing the activity object, leading to memory leaks.
*/
public class BitmapCache {
private Hashtable<String,ArrayList<Bitmap>> hashtable = new Hashtable<String, ArrayList<Bitmap>>();
private StringBuilder sb = new StringBuilder();
public BitmapCache() {
}
/**
* A Bitmap with the given width and height will be returned.
* It is removed from the cache.
*
* An attempt is made to return the correct config, but for unusual configs (as at 30may13) this might not happen.
*
* Note that thread-safety is the caller's responsibility.
*/
public Bitmap get(int width, int height, Bitmap.Config config) {
String key = getKey(width, height, config);
ArrayList<Bitmap> list = getList(key);
int listSize = list.size();
if (listSize>0) {
return list.remove(listSize-1);
} else {
try {
return Bitmap.createBitmap(width, height, config);
} catch (RuntimeException e) {
// TODO: Test appendHockeyApp() works.
App.appendHockeyApp("BitmapCache has "+hashtable.size()+":"+listSize+" request "+width+"x"+height);
throw e ;
}
}
}
/**
* Puts a Bitmap object into the cache.
*
* Note that thread-safety is the caller's responsibility.
*/
public void put(Bitmap bitmap) {
if (bitmap==null) return ;
String key = getKey(bitmap);
ArrayList<Bitmap> list = getList(key);
list.add(bitmap);
}
private ArrayList<Bitmap> getList(String key) {
ArrayList<Bitmap> list = hashtable.get(key);
if (list==null) {
list = new ArrayList<Bitmap>();
hashtable.put(key, list);
}
return list;
}
private String getKey(Bitmap bitmap) {
int width = bitmap.getWidth();
int height = bitmap.getHeight();
Config config = bitmap.getConfig();
return getKey(width, height, config);
}
private String getKey(int width, int height, Config config) {
sb.setLength(0);
sb.append(width);
sb.append("x");
sb.append(height);
sb.append(" ");
switch (config) {
case ALPHA_8:
sb.append("ALPHA_8");
break;
case ARGB_4444:
sb.append("ARGB_4444");
break;
case ARGB_8888:
sb.append("ARGB_8888");
break;
case RGB_565:
sb.append("RGB_565");
break;
default:
sb.append("unknown");
break;
}
return sb.toString();
}
}

Categories

Resources