I am pretty newbie for android development and I just read Romain Guy's "Avoid memory leaks on Android" from the following link
http://www.curious-creature.org/2008/12/18/avoid-memory-leaks-on-android/
Then I did a little test by his famous code snippet on my android emulator
private static Drawable drawable;
private StringBuilder sb;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
TextView label = new TextView(this);
label.setText("Leaks are bad");
if (drawable == null) {
drawable = getResources().getDrawable(R.drawable.ic_launcher);
}
sb = new StringBuilder();
for (int i = 0; i < 1024; i++) {
sb.append('a');
}
label.setCompoundDrawables(null, drawable, null, null);
setContentView(label);
}
This code is supposed to leak the first activity context while changing orientation. So I ran the program in emulator and change the orientation once (I also printed out 2 activity context ca.welcomelm.leaktest.MainActivity#45f81f98 and ca.welcomelm.leaktest.MainActivity#45f8d6f8
). Then I dump the HPROF and jump to list objects by incoming reference char[]. I though I am supposed to see 2 char[] with the pattern "aaaaaaaaaaaa...", which are referenced by those 2 activity context. But I swear I only saw one referenced by the second context. The leaked context is not there. I think it is GCed. Can anyone else confirm this? Thanks
Change:
if (drawable == null) {
drawable = getResources().getDrawable(R.drawable.ic_launcher);
}
To something like:
if (drawable == null)
drawable = getResources().getDrawable(R.drawable.ic_launcher);
else
Log.i("blablabla", "Drawable initialized in prior activity");
And then start doing the screen rotations. You'll notice that Log.i will kick-in because the drawable is marked as static and is initialized already in prior activity. This is the point where memory leaks start to begin.
Since modern devices are pretty much hefty with memory resources thus you do not notice such leaks so easily. However, if your application contains a lot of such references or running over a low-end device then you may get lucky to head memory-leaks soon enough.
You are correct, the original TextView and the objects it referenced (Context, Activity, and more) will be GC'ed once the drawable releases the reference to the TextView. The release occurs when label.setCompoundDrawables() (in #user3375547's example) or label.setBackgroundDrawable() (in Romain Guy's original example) is called.
However, the Context was leaked for a time because the Drawable kept a reference to the TextView too long, preventing the previous Context (and all other referenced objects) from being GC'ed when they could have been.
Now, imagine a situation where Android did not create another instance of the Activity. For example, the user backs out of Activity. Android may destroy the Activity at this point. In this case the static reference still exists and keeps the Context from being GC'ed, causing a leak. The GC will only happen when the Activity is recreated, the application process is terminated, or the Classloader is GC'ed as #uDevel mentions in the comments below.
Related
In this offical blog site, I read below example of memory leak.
http://android-developers.blogspot.com/2009/01/avoiding-memory-leaks.html
private static Drawable sBackground;
#Override
protected void onCreate(Bundle state) {
super.onCreate(state);
TextView label = new TextView(this);
label.setText("Leaks are bad");
if (sBackground == null) {
sBackground = getDrawable(R.drawable.large_bitmap);
}
label.setBackgroundDrawable(sBackground);
setContentView(label);
}
To quote the original post "This code is very fast and also very wrong; it leaks the first activity created upon the first screen orientation change. 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.)"
I do not understand this part. When the activity is recreated, the onCreate() method will be executed, the static Drawable object sBackground will be attached to the new TextView in the second activity. Meaning that the object sBackground will reference to the new textview instead of the old textview in the first activity, leaving the first activity un-referenced.
Can anybody tell me where is wrong in my reasoning? Thanks in advance~~
Oops, it seems this thread is a duplicate, someone asked exactly the same thing here
Understanding memory leaks in Android application
Sorry for my carelessness.
As far as I know after checking the source of View class, when a Drawable is attached to a view, the view is set as a callback on the drawable. Beside, the view also keep a reference to this Drawable. Please see this link http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/5.1.1_r1/android/view/View.java#16292.
And because static variable has long lifetime, then every time you recreate activity & set a Drawable to its view, the activity and its Context also keep a reference to the static Drawable. Then take memories as long as variable does.
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.
I have a ListView in my app, and I've over-ridden the getView() method so I can change the row's ImageView src depending on the row's text.
The problem is, I've noticed the ListView scrolling is lagging, and when I check DDMS, it seems the Garbage Collector is being called everytime the ListView is being scrolled, thus slowing the scrolling.
I've also noticed the Garbage Collector being called in a different part of my app, when reading lines from a BufferedReader, which makes opening a 2,000 line file take ~47 seconds, where as a file exporer I have installed on my phone opens that same file in about 2 seconds.
So my question is, what could be causing the constant Garbage Collection every 200ms or so, and how do I prevent it? It's really slowing my app down and I fear it will put some users off if I don't solve it.
Thanks,
Alex.
ListView getView():
class IconicAdapter extends ArrayAdapter<String> {
IconicAdapter(){
super(FileBrowser.this, R.layout.filebrowser_listview_row, R.id.listtext, directoryEntries);
}
#Override
public View getView(int position, View convertView, ViewGroup parent){
View row = super.getView(position, convertView, parent);
TextView text = (TextView) row.findViewById(R.id.listtext);
ImageView icon = (ImageView) row.findViewById(R.id.listicon);
entryFullFileName = directoryEntries.get(position).toString();
if(entryFullFileName.contains(".") && !entryFullFileName.matches("^\\.+$")){
String[] parts = entryFullFileName.split("\\.");
lastIndex = parts.length - 1;
fileType = parts[lastIndex];
}else{
fileType = "";
}
if(fileIsDir.get(position) == true){
icon.setImageResource(R.drawable.folderlightblue);
}else if(fileType.equals("html")){
icon.setImageResource(R.drawable.filehtml);
}else if(fileType.equals("css")){
icon.setImageResource(R.drawable.filecss);
}else if(fileType.equals("js")){
icon.setImageResource(R.drawable.filejs);
}else if(fileIsDir.get(position) == false){
icon.setImageResource(R.drawable.fileplain);
}
return(row);
}
}
Code To Open File
I removed the code the other day that logged how many seconds it took to open the file, but it took 47 seconds and definitely took too long, and again while the while loop is doing it's thing, there's constant calls to the Garbage Collector, which I'm guessing in the cause of the slow file reading - and yes, this function is called in a thread with progressDialog showing while the file is being read
private String getLocalFileContents(String fileUri){
try{
String contents = "";
BufferedReader in = new BufferedReader(new FileReader(fileUri));
String line;
while((line = in.readLine()) != null){
contents += line + "\n";
}
in.close();
return contents;
}catch(Exception e){
toast.setText("Failed to open sdcard file, try again.");
}
return null;
}
UPDATE:
The file reading problem is solved, turns out the String concatenation made the Garbage Collector get called after each loop, dramatically slowing the file reading down. As suggested by an answer I used StringBuilder instead and it now opens in a second - hooray!
2ND UPDATE:
I know what the cause of the constant GC calls when scrolling my ListView is, it's the ListView attribute android:cacheColorHint="#android:color/transparent" - but I don't know a work-around!
In general, garbage collection is happening because you're creating too many objects unnecessarily. It'd be easier to help with your code, but I'll give it a shot anyway.
In the case of your list, you're probably recreating your view in every call to getView. You should instead re-use convertView when appropriate. See my answer to this other SO question for an idea of how to structure your getView method.
Your file reading problem is a bit harder to guess at, but 47s seems ridiculously long for 2,000 lines. Are you also creating objects in that loop?
Update:
So apparently your problem isn't really with your View objects themselves, but it's all the work you do every time you get a View. You're doing quite a bit of work every time: a RegEx match, string splitting (and associated string object creation), etc. You should at minimum cache the results of this so that you don't have to redo the work for each item every time it comes back into view.
One optimization would be to stop splitting the entire string to get the filetype. You could use something like
String fileType = "";
int lastDot = entryFullFileName.lastIndexOf(".");
if(lastDot!=-1) {
fileType = entryFullFileName.substring()
}
That certainly shouldn't take 47s though.
Look at EfficientAdapter here is link
And explained more about efficient adapter and getView method in other thread have a look at it, here is link
Hope this help!!!
Yes, android:cacheColorHint="#android:color/transparent" is causing excessive calling of the garbage collector on some OS versions (not sure if the newest ones this is fixed).
Well, just try to not use it. For example, I spoke with my designers, explained them the problem about the cause of the lags, and they agreed to not use the transparent background.
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.
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.