I am developing an app which instantiates a bunch of bitmap objects (e.g. buttons, which have cache bitmaps, so they don't have to get rendered again and again)
Now, I realised that when I run and start the app repeatedly on my huawei mobile device, I get an OutOfMemoryException at a point where the app tries to allocate some memory for the bitmaps.
So I guess it's the bitmaps which make trouble. I do know that there is a bitmap.recycle() method though.
Now my question: what is best practice to clean up memory?
Why isn't there some View method like View::onDestroy() which can be implemented for cleanup purpose?
EDIT: example
my "CirclyButton" (extends Button) class always draws a cached bitmap onDraw:
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawBitmap(this.getDefaultBitmap(), 0, 0, paint);
}
private Bitmap getDefaultBitmap(){
if(mBitmapDefault == null){
mBitmapDefault = Bitmap.createBitmap(8*radius, 8*radius, Config.ARGB_8888);
Canvas canvas = new Canvas(mBitmapDefault);
this.drawDefault(canvas);
return mBitmapDefault;
}
return mBitmapDefault;
}
So I guess this allocated data should be recycled somewhere...?
Views don't have an onDestroy method because views usually don't get destroyed, activities do. A view won't just be destroyed if nothing happens to its activity (Unless you inflate a different layout... That's not the case, right?), and if something happens to its activity, you do have a callback getting called.
If there is a recycle() method, make sure you call it. And remove all reference to memory taking objects in the onDestroy, i.e:
#Override
public void onDestroy() {
object1 = null;
object2 = null;
//...
}
So the GC can do its job. I had the same problem with the AdView of AdMob, although they did have a destroy method it didn't really help. But deleting my references of the view fixed the problem.
Provide more information about where are you using your bitmaps, i have some serious experience of working with images and saving memory.
For example in my app i have a list of some data, which display some bitmap in each row. I store my list in a fragment(for fragment support i use compatibility library), and i recycled my bitmaps on this fragment onDestroy method.
Later i decided to optimize my list, so i added scroll listener to my list and started recycling bitmaps, when they are scrolled off the screen.
Related
I am building a gallery using a ViewPager. Every time a photo is downloaded I immediately cache it in mMemoryCache which is
mMemoryCache = new LruCache<Integer, Bitmap>(cacheSize) {
#Override
protected int sizeOf(Integer key, Bitmap bitmap) {
// The cache size will be measured in bytes rather than number
// of items.
return (bitmap.getRowBytes() * bitmap.getHeight());
}
};
As you can see sizeOf() returns the number if bytes used, which makes sense. My problem is that my ViewPager's adapter needs to know how many pages I have, which would be the number of objects in my cache.
Any ideas on how I can do it? thanks!
I think you should rethink your approach. Usually, when you use some kind of paging (i mean data paging, not the ViewPager), you first add a certain fixed number of items to your Adapter. You can either start downloading the required resources right away, or wait until you really need them. Check in Adapter.onCreateView() if the resource is already downloaded and cached. If yes, get it from there, if not, start an asynchronous download and add the image to the view as soon as you got it. Show a placeholder in the meantime.
When you reach the last item in your adapter, add another batch to it and everything starts over again.
If you really want to know what items are in your LRUCache, override put() and entryRemoved(). There you know what items get added or evicted from the cache. I would advise against this method though.
I have method that returns Drawable, and if its Bitmap object is recycled then it reloads.
public Drawable getLogo() {
if(logo == null || Util.isRecycled(logo)) //Util.isRecycled checks - is Drawable's bitmap recycled, if it had so
logo = CacheController.getInstance().getLogo(this);
return logo;
}
But looks like right after calling bitmap.recycle() it's bitmap.isRecycled() still returns false. Am I right, that bitmap recycle process goes asynchonously, or it's just bug in my code? If so, how can I make sure, that Bitmap is not recycled right now?
If you are trying to implement some caching mechanism for bitmaps, you shouldn't have to check if it's recycled. Simply retrieve it from the cache and the cache will create it again if it doesn't have a reference to it in it's data structure. See this tutorial for how to cache bitmaps http://developer.android.com/training/displaying-bitmaps/cache-bitmap.html
two days ago i noticed something. I have a spinner over a map activity. In the OnCreate() method of the activity i populate the spinner with data. After that i start the heap analyzer in DDMS i begin to open/close the spinner. I noticed the VM allocate memory when i open the spinner items, but when i close it, the VM do no free this memory. I've tried to start the GC, but the memory is still allocated. i did this 20 times one by one and the allocated memory increased from 3.5MB to 7MB. What is wrong? I found an issue in google groups, but they haven't answered yet.
Spinner memory leak
I rewrite all my code in the spinner adapter, but the issue still remains.
I read some advices in this topic
Avoid memory leaks
There is something i did not get:
When a Drawable is attached to a view, the view is set as a callback on the drawable. In the code snippet above, this means the drawable has a reference to the TextView which itself has a reference to the activity (the Context) which in turns has references to pretty much anything (depending on your code.)
What does it mean? If i have a textview and set it a drawable object (i noticed the drawable is static), the textview object has a reference to the drawable object and the drawable object has a reference to the view too? If this is true, they become undestroyable by the GC because they both have references to each other? What is this back-reference (callbacks) dependencе between the objects?
Sorry I can't help you on your Spinner problem but I can have a try on the second part:
Romain Guy post on android developer blog explain two important things.
First:
When you create a View (TextView, ImageView...) you must not create it with the activity Context
// DO NOT DO THIS
TextView label = new TextView(this);
Otherwise the View get a reference to your activity and will never be deallocated.
Instead, when you create a View programatically, you have to use the application context:
TextView label = new TextView(getApplicationContext());
Second:
When you link a Drawable to an View, it keeps a callback on your activity via the Context. If you leave it, it will leak memory when your activity is destroy.
The thing to do to avoid that is to "set stored drawables' callbacks to null when the activity is destroyed" so for example whith an ImageView:
protected void onDestroy() {
imageView.getDrawable().setCallback(null);
super.onDestroy();
}
You have to do the same for the background drawable...
Hope it helps.
Before making this question, I have searched and read these ones:
Lazy load of images in ListView
Android - Issue with lazy loading images into a ListView
My problem is I have a ListView, where:
Each row contains an ImageView, whose
content is to be loaded from the
internet
Each row's view is recycled as in
ApiDemo's List14
What I want ultimately:
Load images lazily, only when the user
scrolls to them
Load images on different thread(s) to
maintain responsiveness
My current approach:
In the adapter's getView() method, apart from
setting up other child views, I launch a
new thread that loads the Bitmap from
the internet. When that loading thread
finishes, it returns the Bitmap to be set on the ImageView (I do this using AsyncTask or Handler).
Because I recycle ImageViews, it may
be the case that I first want to set
a view with Bitmap#1, then later want
to set it to Bitmap#2 when the user
scrolls down. Bitmap#1 may happen to
take longer than Bitmap#2 to load, so
it may end up overwriting Bitmap#2 on
the view. I solve this by maintaining
a WeakHashMap that remembers the last Bitmap I want
to set for that view.
Below is somewhat a pseudocode for my current approach. I've ommitted other details like caching, just to keep the thing clear.
public class ImageLoader {
// keeps track of the last Bitmap we want to set for this ImageView
private static final WeakHashMap<ImageView, AsyncTask> assignments
= new WeakHashMap<ImageView, AsyncTask>();
/** Asynchronously sets an ImageView to some Bitmap loaded from the internet */
public static void setImageAsync(final ImageView imageView, final String imageUrl) {
// cancel whatever previous task
AsyncTask oldTask = assignments.get(imageView);
if (oldTask != null) {
oldTask.cancel(true);
}
// prepare to launch a new task to load this new image
AsyncTask<String, Integer, Bitmap> newTask = new AsyncTask<String, Integer, Bitmap>() {
protected void onPreExecute() {
// set ImageView to some "loading..." image
}
protected Bitmap doInBackground(String... urls) {
return loadFromInternet(imageUrl);
}
protected void onPostExecute(Bitmap bitmap) {
// set Bitmap if successfully loaded, or an "error" image
if (bitmap != null) {
imageView.setImageBitmap(bitmap);
} else {
imageView.setImageResource(R.drawable.error);
}
}
};
newTask.execute();
// mark this as the latest Bitmap we want to set for this ImageView
assignments.put(imageView, newTask);
}
/** returns (Bitmap on success | null on error) */
private Bitmap loadFromInternet(String imageUrl) {}
}
Problem I still have: what if the Activity gets destroyed while some images are still loading?
Is there any risk when the loading
thread calls back to the ImageView
later, when the Activity is already
destroyed?
Moreover, AsyncTask has some global
thread-pool underneath, so if lengthy
tasks are not canceled when they're
not needed anymore, I may end up
wasting time loading things users
don't see. My current design of
keeping this thing globally is too
ugly, and may eventually cause some
leaks that are beyond my
understanding. Instead of making
ImageLoader a singleton like this,
I'm thinking of actually creating
separate ImageLoader objects for
different Activities, then when an
Activity gets destroyed, all its
AsyncTask will be canceled. Is this
too awkward?
Anyway, I wonder if there is a safe and standard way of doing this in Android. In addition, I don't know iPhone but is there a similar problem there and do they have a standard way to do this kind of task?
Many thanks.
I solve this by maintaining a
WeakHashMap that remembers the last
Bitmap I want to set for that view.
I took the approach attaching the the URL of the desired image onto the ImageView via setTag(). Then, when I have the image downloaded, I double-check the ImageView URL -- if it is different than the one I just downloaded, I don't update the ImageView, because it got recycled. I just cache it.
Is there any risk when the loading
thread calls back to the ImageView
later, when the Activity is already
destroyed?
I am not aware of any risk, other than a bit of wasted CPU time and bandwidth (and, hence, battery).
Instead of making ImageLoader a
singleton like this, I'm thinking of
actually creating separate ImageLoader
objects for different Activities, then
when an Activity gets destroyed, all
its AsyncTask will be canceled. Is
this too awkward?
Canceling an AsyncTask is not terribly easy, if it is already running. I'd just let it run to completion.
Ideally, avoid singletons. Either use a Service, or pass your ImageLoader to the next instance of your activity via onRetainNonConfigurationInstance() (e.g., isFinishing() is false in onDestroy(), so this is a rotation).
I have a complex custom view - photo collage.
What is observed is whenever any UI interaction happens, the view is redrawn.
How can I avoid complete redrawing (for example, use a cached UI) of the view specially when I click the "back" button to go back to previous activity because that also causes redrawing of the view.
While exploring the API and web, I found a method - getDrawingCache() - but don't know how to use it effectively.
How do I use it effectively?
I've had other issues with Custom Views that I outline here.
I found a better way than using getDrawingCache.
In the method onDraw, apart from drawing in the natural canvas, I also draw on an memory-only canvas.
Bitmap cacheBmp = Bitmap.Create(....);
Canvas cacheCanvas = new Canvas(cacheBmp);
void onDraw(Canvas c)
{
if(updateDueToInteraction)
{
c.drawXXX(...);
cacheCanvas.drawXXX(...);
} else
{
c.drawBitmap(cacheBmp, 0, 0);
}
}
First of all you will have to use the setDrawingCacheEnabled(true) method, so that you're View is cache-enabled. Then, you can use the getDrawingCache(boolean) method which returns a Bitmap representing the View. Then, you can draw that bitmap manually.
If you don't enable caching by calling the setDrawingCacheEnabled(true) method, you will have to call buildDrawingCache() before (and call destroyDrawingCache() when you're done).
Bye!