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
Related
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.
How Do You Force A NinePatchDrawable to release the BitMap bytes it parsed out of 'res'?
As an Android developer I face pressure to control memory utilization in my games.
I work to control memory utilization by releasing resources as quickly as I can after they are no longer used. To this end, I maintain a list of all loaded resources and purge / release them from memory as soon as I am done with them.
My application uses a number of different types of graphical resources
BitMap
BitMapDrawable
Drawable
NinePatchDrawable
How do I release these objects right now?
BitMap : I use the "recycle()" method
BitMapDrawable : I use the "getBitMap().recycle()" method
Drawable : I set these to null (not working)
NinePatchDrawable : I set these to null (not working)
What have you tried?
You cannot "getBitmap()" a NinePatchDrawable
You cannot convert a NinePatchDrawable to a BitMapDrawable (even if they are both Bitmap based Drawables)
There seems to be a way to parse the PNG yourself, feeding the bytes into NinePathDrawable yourself -- this might get me to a point where I can just "recycle()" the underlying BitMap myself, but that seems like I'm reinventing the wheel (http://code.google.com/p/android/issues/detail?id=13542)
My Current Rules:
Never use #drawable/ in XML
Never android:background in XML
Never android:src in XML
To be able to use: #drawable/, android:background, android:src in xml, you can always use custom classes.
So instead of
<ImageView android:src="#drawable/bg" />
you can use:
<com.myPackage.CustomImageView android:src="#drawable/bg" />
Than in the CustomImageView in the constructor you can get the references to your xml attributes:
private Drawable bg2;
private Drawable bg1;
public void CustomImageView(Context context, Attrs attrs)
{
super(context, attrs);
// Use this to get references to your xml attributes
int resourceId = attrs.getAttributeResourceValue("android", "src", 0);
bg1 = getResources().getDrawable(resourceId);
// Or for the 'background' attribute
resourceid = attrs.getAttributeResourceValue("android", "background", 0);
bg2 = getResources().getDrawable(resourceId);
// Now you can recycle() your 'bg' whenever you're done
}
This way, you can extract references from your xml. And recycle() them when you think it's appropriate
I too am frustrated by the outofmemory bug. My application was throwing 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. The onDestroy() method might not get called. Also, if the cleanup is done in the onPause() method, users will get a black screen before moving onto the next screen.
To prevent a black screen from occurring if the user presses the back button on the device, I then 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.
I implemented the lazy-loading images in my ListView.
I use a AsyncTask to download the image from the internet and bind it to the ImageView in the UIThread.
It's working except that when I scroll the ListView vary fast, the downloaded images sometimes are binded into the wrong items in the list.
I guess the problem is from the reuse of convertView in the BaseAdapter.
Any ideas to solve it?
Many thanks.
EDIT:
I post the answer as following:
public void setBitmap(int position, Bitmap image) {
View itemView = mListView.getChildAt(position - mListView.getFirstVisiblePosition());
if (itemView != null) {
ImageView itemImageView = (ImageView) itemView.findViewById(R.id.item_imageview);
itemImageView.setImageBitmap(image);
}
}
There are two problems that will arise during lazy loading of images in a ListView.
The old images are still shown until the new ones are loaded. This is easy just set the ImageView to an image is loading view or set it to invisible before starting the image download.
The second problem is harder to solve. Imagine you are scrolling very fast through your list. Now your views may be recycled before the old AsyncTask has finished loading the image. You now have two tasks running that in the onPostExecute method will set an image to the imageview. Now for a short time the wrong image will be shown until the second Task finishes, or even worse for some network related reason they don't finish in the order they started and you have the wrong image overwrite the correct image. To solve this you have to check what image should be displayed after the task finished. In the View class are two methods for things exact like this one:
setTag and getTag You can bind any object to the imageview that comes into your mind. In most of the cases I use setTag to bind the URL of the image as a String to the imageview before I start a task. Now I can cast getTag to a String after the task finished and compare the URL that should be displayed with the URL that I downloaded and only set the image if necessary.
Create a function called void setBitmap(Bitmap bitmap, int position) or similar in your adapter. Let your AsyncTask call this method when a new bitmap is available. This method may then call notifyDataSetChanged() on the UI-Thread itself to ensure the views get refreshed. Holding references to views in an adapter (even by holding them in an AsyncTask) is dangerous!
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!