How to use an ImageView without de/allocating memory - android

I have an application that uses a flip-book style animation with thousands of images. APK size isn't an issue due to it being an internal only application that will never be put on any app store.
The issue I am having is my animator is a sub-classed ImageView that switches out images at 15 frames per second and each time I call setImageURI Garbage Collection then runs. So is there an alternative method to setImageURI that wont cause Garbage Collection to be run?
Edit: A little more background information.
My app has ~12 sequences and 6 of them contain 1609 Images and different events need to be called at certain frames. Another need is for the user to be able to stop on any frame to survey the situation and either preform an action or continue down the sequence. A 3D engine would have been the ideal solution but this method was brought up as the user doesn't need complete freedom and is on a "rail" the entire time.
This is the meat of the application and where the problem occurs
try {
((BitmapDrawable) getDrawable()).getBitmap().recycle();
setImageURI(imgUri);
refreshDrawableState();
} catch (Exception e) {
e.printStackTrace();
}

The Garbage Collector is running because you're exchanging one image for another which dumps the previous. It's going to happen every time you lose references to the images.
Here are a couple possible methods to consider.
Since you mentioned it's a flipbook, build and assign an AnimationDrawable to the ImageView which will act as a frame-by-frame animation (exactly what a flipbook is). This loads all the images at once and will keep them around until you lose a reference to the AnimationDrawable.
Store all the images as SoftReferences to cache the images. A SoftReference will keep the object in memory until memory is needed to be collected. This will slow the frequency of the garbage collector (assuming this is the cause). Use BitmapFractory to build the image and setImageBitmap to assign it to the ImageView. The other advantage of this is it allows you to build images on threads before you assign it to the ImageView.

Maybe it's because your Image need to much memory, and the android need call the GB to free more mem trying to avoid crashing your program

Related

Is my image caching process going to leak memory?

I have an object that overwrites the Application object. In it, I have a member variable which is a LongSparseArray where the key is some identifier of type long and the value is an object with 2 member variables: a Bitmap and a long which is used as a timestamp.
This is my global image cache. Occasionally, a function is ran that looks at the timestamps and ages things that are over an hour old.
By "age" I mean that it removes that entire entry from the LongSparseArray.
Here is my question:
Suppose I have an Activity with a ListView. Each row in the ListView has an ImageView that is populated with an image from the cache.
Bitmap image = ((MyApp)getApplicationContext()).getImage(id);
holder.imgImage.setImageBitmap(image);
Now, suppose the user clicks some button which takes them to a new Activity. While on this new Activity, the image previously assigned to a row in the ListView in the previous Activity ages.
So, to recap, that Bitmap key/value entry now no longer exists in the global LongSparseArray.
Is that Bitmap really able to be reclaimed by Java? Isn't it still being referred to by the ImageView in the ListView of the previous Activity? Assuming, of course, that Android hasn't reclaimed the memory used by that Activity.
The reason I'm asking about this is my previous aging function would also call .Recycle() on the Bitmap. In this scenario, when the user hit the back button and returned to the previous Activity which was using that Bitmap, the application would crash, presumably because that Bitmap was not only missing from the cache, but also from memory. So I just removed the .Recycle() call.
By the way, once the Bitmap is removed from the cache, and an object with that id shows up on screen again, the application will download the Bitmap again and place it in the cache. If the previous one stayed in memory, you could see how this would present a problem.
Also, does anyone have any ideas for a more effective solution?
What would happen if I set myImageView.setDrawingCacheEnabled(false);?
There are 2 Activities which use this image caching. One is a search screen that displays a list of items (and their images) after the user performs a search. The other is a list of those items the user has then selected to keep.
Issue: Once recycle() method is called on a bitmap, the bitmap should never be used again. If an attempt is made to draw the bitmap, then an exception will be thrown. From docs:
You should use recycle() only when you are sure that the bitmap is no
longer being used. If you call recycle() and later attempt to draw the
bitmap, you will get the error: "Canvas: trying to use a recycled
bitmap".
In this specific case, you have recycled the bitmap, but the ListView item's ImageView has a strong reference to the bitmap. When you return to the Activity, the ListView item attempts to draw the bitmap, hence the exception is thrown.
Bitmap memory management: Prior to Android 2.3.3, the backing pixel data of a bitmap was stored in native memory and bitmap itself in Dalvik memory. Hence to release the native memory, recycle method has to be called.
Here is Bitmap.recycle function definition:
public void recycle() {
if (!mRecycled) {
if (nativeRecycle(mNativeBitmap)) {
// return value indicates whether native pixel object was actually recycled.
// false indicates that it is still in use at the native level and these
// objects should not be collected now. They will be collected later when the
// Bitmap itself is collected.
mBuffer = null;
mNinePatchChunk = null;
}
mRecycled = true;
}
}
Post Android 3.0, the backing pixel data is also stored in Dalvik memory. When the bitmap is no longer required, we need to ensure we don't hold any strong reference to the bitmap, so that it is garbage collected.
Solution: If you are still supporting Android 2.3.3 and lower version, you still need to use recycle to release the bitmap.
You can use reference counting to track whether the bitmap is currently being referenced by the ListView item, so that even it is aged, you don't call recycle on the bitmap.
ListView adapater's getView method is the place where the bitmap is assigned to the ImageView. Here you increment the reference count. You can attach setRecyclerListener to the ListView to know whenever the listview item is put into recycle bin. This is the place you would decrement the reference count of the bitmap. The aging function need to recycle the bitmap only if the reference count is zero.
You can also consider using LruCache for caching, as mentioned in docs.
setDrawingCacheEnabled: By calling this method with true param, the next call to getDrawingCache will draw the view to a bitmap. The bitmap version of view can be rendered on to the screen. Since it is just a bitmap, we cannot interact with it as done with an actual view. Couple of use cases are:
When ListView is being scrolled, the bitmap of the displayed items view is captured and rendered. So that the views being scrolled don't undergo measure and layout pass.
View hierarchy feature in DDMS.
Is that Bitmap really able to be reclaimed by Java? Isn't it still
being referred to by the ImageView in the ListView of the previous
Activity? Assuming, of course, that Android hasn't reclaimed the
memory used by that Activity.
The Bitmap is stilled used in the ListView (a strong reference) so dalvik can't reclaim its memory.
Apparently you can't call recycle on the Bitmap or bad things will happen(app crash, e.g.).
What would happen if I set myImageView.setDrawingCacheEnabled(false);?
If you disable drawing cache, every time your view needs to be redrawn, the onDraw method will be called.I'm not very familiar with ImageView , you can go and read its source for a deep understanding.
(Note: the usage of drawing cache is different when hardware accerleration is enabled/disabled, here I just assume you're using software rendering).
For the solution, you can try the following:
when the Bitmap cache become stale, you remove it from the cache array(and then you app will try to get a new one, I think).
In ListView.getView, you can check whether currently used Bitmap ages. It should be easy because you know the timestamp when you call setImageBitmap the first time and the latest timestamp. If they are not same, you call setImageBitmap again using the new Bitmap and the old one will be reclaimed.
Wish this helps.
Regarding, "Also, does anyone have any ideas for a more effective solution?"
The Picasso library would help solve the problems you are facing http://square.github.io/picasso/
Picasso is "A powerful image downloading and caching library for Android"
"Many common pitfalls of image loading on Android are handled automatically by Picasso:
Handling ImageView recycling and download cancelation in an adapter.
Automatic memory and disk caching."

Reference counting android Bitmaps with caches

On Android pre-honeycomb, Bitmaps have freaky memory issues because their data isn't stored in the VM. As a result it isn't tracked or removed by the GC. Instead it is removed when Bitmap.recycle() is called (and that is also done automatically in the Bitmap's finalizer).
This leads to some problems when doing image caching. When a bitmap is due to be evicted, I can't simply call recycle() on it, because I have no idea if anyone else is using it.
My first thought was to do System.gc() just before I load each bitmap. That way, hopefully orphaned Bitmaps will be finalized and the native memory freed. But it doesn't work. Edit: Actually it does sort of work. I had my System.gc() in the wrong place, after moving it, and halving my cache size (to what seems like a ridiculously small 2 MB of uncompressed bitmap data), my app no longer seems to crash (so far)!
My next thought was to implement manual reference counting, by subclassing Bitmap and calling ReferenceCountedBitmap.decrementCount() in all my activities' onDestroy() methods. But I can't because Bitmap is final.
I am now planning a BitmapManager which keeps WeakReference's to the bitmaps, and has methods like:
public void using(Bitmap bm);
public void free(Bitmap bm);
which count the references.
Does anyone have any experience or advice handling this? Before you suggest it, I can't ignore 80% of the market.
Well, I solved this with a bitmap manager, where I save the referencing views. In a map-like structure bitmap -> list of views.
Before I call recycle() on a bitmap, I first set all the references from the views to null (otherwise it will throw bitmap recycled exception).
Manual garbage collection, as you say, doesn't work for bitmaps pre-honeycomb, since they are allocated in the native heap and even with System.gc() you can't make assumptions when this memory will be released.

Best practice for storing images to use on short term

So, I'm using custom number tiles for images. The images are stored as resources. I've been tracing a memory leak, and have cause to believe the method I am storing these images for use is suspect. Currently, I'm doing this:
private void loadImageList()
{
Log.d(TAG,"Reloading List "+imageList);
if (imageList==null || imageList.size()<10)
{
imageList=new ArrayList<Bitmap>(10);
imageList.add(0,BitmapFactory.decodeResource(getResources(), R.drawable.ant_number0));
imageList.add(1,BitmapFactory.decodeResource(getResources(), R.drawable.ant_number1));
imageList.add(2,BitmapFactory.decodeResource(getResources(), R.drawable.ant_number2));
imageList.add(3,BitmapFactory.decodeResource(getResources(), R.drawable.ant_number3));
imageList.add(4,BitmapFactory.decodeResource(getResources(), R.drawable.ant_number4));
imageList.add(5,BitmapFactory.decodeResource(getResources(), R.drawable.ant_number5));
imageList.add(6,BitmapFactory.decodeResource(getResources(), R.drawable.ant_number6));
imageList.add(7,BitmapFactory.decodeResource(getResources(), R.drawable.ant_number7));
imageList.add(8,BitmapFactory.decodeResource(getResources(), R.drawable.ant_number8));
imageList.add(9,BitmapFactory.decodeResource(getResources(), R.drawable.ant_number9));
}
}
public void onStart()
{
super.onStart();
loadImageList();
}
What will happen is if I open and close this application repeatedly, then the system won't have enough memory to add an image to the ImageList. I'm setting the image of the button like this (But with an object ImageButton, of course). I should add that the application is threaded, and this call resides in a runOnUiThread(Runnable)
ImageButton.setImageBitmap(imageList.get(current_var));
I've tried deleting the images in the onStop() command, but it will sometimes cause a crash when the thread tries to allocate the image stored in memory to the button, due to the threaded nature of the beast.
So, is there a better way that I can load these images that won't cause a memory leak?
I realized something after posting this. I could just make the imageList static. These images aren't going to change from one view to the next. The Bitmaps don't contain a view, so the memory won't lock up. I'm already checking to see if the values exist before I'm using them. And this function is used so frequently that holding on to static memory won't hurt other aspects of the program. Sometimes it just helps to put it into words..

Asynchronous download of Bitmaps in an Adapter, with emphasis on Bitmap.recycle()

Could someone tell me how to make a good mechanism for async. download of images for use in a ListView/GridView?
There are many suggestions, but each only considers a small subset of the typical requirements.
Below I've listed some reasonable factors (requirements or things to take into account) that I, and my collegues, are unable to satisfy at once.
I am not asking for code (though it would be welcome), just an approach that manages the Bitmaps as described.
No duplication of downloaders or Bitmaps
Canceling downloads/assigning of images that would no longer be needed, or are likely to be automatically removed (SoftReference, etc)
Note: an adapter can have multiple Views for the same ID (calls to getView(0) are very frequent)
Note: there is no guarantee that a view will not be lost instead of recycled (consider List/GridView resizing or filtering by text)
A separation of views and data/logic (as much as possible)
Not starting a separate Thread for each download (visible slowdown of UI). Use a queue/stack (BlockingQueue?) and thread pool, or somesuch.... but need to end that if the Activity is stopped.
Purging Bitmaps sufficiently distant from the current position in the list/grid, preferably only when memory is needed
Calling recycle() on every Bitmap that is to be discarded.
Note: External memory may not be available (at all or all the time), and, if used, should be cleared (of only the images downloaded here) asap (consider Activity destruction/recreation by Android)
Note: Data can be changed: entries removed (multi-selection & delete) and added (in a background Thread). Already downloaded Bitmaps should be kept, as long as the entries they're linked to still exist.
setTextFilterEnabled(true) (if based on ArrayAdapter's mechanism, will affect array indexes)
Usable in ExpandableList (affects the order the thumbnails are shown in)
(optional) when a Bitmap is downloaded, refresh ONLY the relevant ImageView (the list items may be very complex)
Please do not post answers for individual points. My problem is that that the more we focus on some aspects, the fuzzier others become, Heisenberg-like.
Each adds a dimension of difficulty, especially Bitmap.recycle, which needs to be called during operation and on Activity destruction (note that onDestroy, even onStop might not be called).
This also precludes relying on SoftReferences.
It is necessary, or I get OutOfMemoryError even after any number of gc, sleep (20s, even), yield and huge array allocations in a try-catch (to force a controlled OutOfMemory) after nulling a Bitmap.
I am resampling the Bitmaps already.
Check this example. As Its is used by Google and I am also using the same logic to avoid OutOfMemory Error.
http://developer.android.com/resources/samples/XmlAdapters/index.html
Basically this ImageDownlaoder is your answer ( As It cover most of your requirements) some you can also implement in that.
http://developer.android.com/resources/samples/XmlAdapters/src/com/example/android/xmladapters/ImageDownloader.html
In the end, I chose to disregard the recycling bug entirely. it just adds a layer of impossible difficulty on top of a manageable process.
Without that burden (just making adapters, etc stop showing images), I made a manager using Map<String, SoftReference<Bitmap>> to store the downloaded Bitmaps under URLs.
Also, 2-4 AsyncTasks (making use of both doInBackground and onProgressUpdate; stopped by adding special jobs that throw InterruptedException) taking jobs from a LinkedBlockingDeque<WeakReference<DownloadingJob>> supported by a WeakHashMap<Object, Set<DownloadingJob>>.The deque (LinkedBlockingDeque code copied for use on earlier API) is a queue where jobs can leave if they're no longer needed. The map has job creators as keys, so, if an Adapter demands downloads and then is removed, it is removed from the map, and, as a consequence, all its jobs disappear from the queue.
A job will, if the image is already present, return synchronously. it can also contain a Bundle of data that can identify which position in an AdapterView it concerns.
Caching is also done on an SD card, if available, under URLEncoded names. (cleaned partially, starting with oldest, on app start, and/or using deleteOnExit()
requests include "If-Modified-Since" if we have a cached version, to check for updates.
The same thing can also be used for XML parsing, and most other data acquisition.
If I ever clean that class up, I'll post the code.

outofmemoryexception when using large number of images with a sensor listener

I am working on an app having a lot of image and using a sensor listener that I register at the beginning of the activity with the sensor manager to detect shake events and change images based on shake direction.
I read the event values in onSensorChanged(SensorEvent event) in order to use it in my app:
public void onSensorChanged(SensorEvent event){
accX = event.values[0];
accY = event.values[1];
accY = event.values[2];
}
I unregister the sensor listener on pause of the activity and also call system.gc for garbage collection.
Everything works well till I am on a single activity.
When I finish current activity and open another activity (again having a a sensor listener), app starts giving out of memory exception after a while:
"OutOfMemoryException: bitmap size
exceeds VM budget"
Note that heap size of the app is never more than 4 MB.
One more observation is that the app never crashes if sensor listener is not used which makes me thinks that possibly there are memory leaks when sensor events are generated and when a new activity is loaded, due to lesser memory available, the app gives OutOfMemoryException when bitmaps are used in the activity.
So seemingly two possible reasons:
images are not recycled properly
http://code.google.com/p/android/issues/detail?id=8488
every time there is a SensorEvent, somewhere there are memory leaks which decreases the available memory for an app
Android: reading accelerometer without memory allocation?
I'm also using the solution described in comment 51 here:
http://code.google.com/p/android/issues/detail?id=8488
to recycle everything but it still gives memory issues.
If you can tell me the best approach to use the images in a large size app, how to recycle them and confirm if there are really any memory leaks while using the sensor listener, or any suggestions what I should look for in the app, it will be really helpful.
For the images to get recycled properly, if possible, try using Bitmap.recycle() method. This is becasue the image is actually kept as a reference (in pre-Honeycomb) and the actual data is allocated using native memory.
In Honeycomb the memory for images is allocated on the heap so it is easilly GCed. Additionally, you may set largeHeap="true" (again post-Honeycomb).
FYI, you may use the combination of SoftReference and HashMap to achieve better memory control on Bitmap. By using SoftReference, memory will be released by not referencing the object anymore when memory is not enough.
A Common Example:
HashMap<String, SoftReference<Bitmap>> cache;
...
Bitmap bitmapToRead = cache.get(key).get();
if (bitmapToRead == null){
cache.put(key, new SoftReference<Bitmap>(bitmap)); // bitmap from Storage for example
/* your operation */
}else{
/* your operation on bitmapToRead */
}
But to be honest, I have experienced OutOfMemoryError even used SoftReference<Bitmap>. What I have done to overcome this error is to recycle() the bitmap manually.

Categories

Resources