tags:

views:

85

answers:

1

I am struggling with a memory leak associated with a ListView. I have created the following small program which exhibits this behavior.

What I do is create 2 LinearLayouts. The first has a Button and a GListView control. The code for GListView is below, but it just sub-classes ListView, and implements the ListAdapter interface. When the GListView is created, it sets it's adapter to itself.

Now when you press the button I switch to the second LinearLayout. This layout has only a single button. When you press this button, I create a new 1st layout with a new GListView and set it as the active view.

Run the program, and switch between the two views 20 times. Then bring up the DDMS and force a garbage collection. Then Dump the memory, and use the Memory Analyzer and you will find 21 GListView objects remaining. That is, the 20 GListViews that are not longer connected to anything have NOT been freed.

If I do a memory dump and take a look at one of the GListViews that should have been recycled, and list the incomming references using the Memory Analyzer, I get the following:

Class Name                                                              | Shallow Heap | Retained Heap 
-------------------------------------------------------------------------------------------------------
com.gabysoft.memoryleak.GListView @ 0x43e72270 Unknown                  |          672 |         3,528 
|- host android.view.View$ScrollabilityCache @ 0x43e72560               |           80 |           584 
|- this$0 android.widget.AbsListView$RecycleBin @ 0x43e72830            |           40 |           160 
|- mCallback android.graphics.drawable.StateListDrawable @ 0x43e728a8   |           64 |         1,464 
|- this$0 android.widget.AdapterView$AdapterDataSetObserver @ 0x43e730b8|           16 |            16 
-------------------------------------------------------------------------------------------------------

Now if I comment out the 'setAdapter(this)' function in the GListView constructor and repeat the above, I find that there is only 1 GListView that remains. That is, in this case, all of the unused GListViews have been properly recycled.

Someone suggested that I create a private class within my GListView to handle the ListAdapter interface, and I tried that, but it did not help. I have also tried creating a completely separate public class to handle the ListAdapter, but, alas, that doesn't seem to work either.

Surely there is some way to make these objects go away when they are no longer used anywhere. (Isn't that what garbage collection is all about?)

Any help would be appreciated. I am really pulling my hair out on this one.

Thanks.

/*
 * Activity
 */

package com.gabysoft.memoryleak;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.ListView;

public class MemoryLeak extends Activity implements android.view.View.OnClickListener 
{
    LinearLayout ll2;
    boolean page2 = false;

    private LinearLayout CreateLayout()
    {
        LinearLayout ll = new LinearLayout(this);

        Button btn1 = new Button(this);
        ListView    lv    = new GListView(this);

        btn1.setText("Press");
        btn1.setLayoutParams(new LinearLayout.LayoutParams(100, 40));
        btn1.setOnClickListener(this);

        ll.addView(btn1);
        ll.addView(lv);

        return(ll);
    }

    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) 
    {
        super.onCreate(savedInstanceState);

        CreateLayout();

        LinearLayout ll = CreateLayout();
        ll2 = new LinearLayout(this);

        Button btn2 = new Button(this);

        btn2.setText("Back");
        btn2.setLayoutParams(new LinearLayout.LayoutParams(100, 40));
        btn2.setOnClickListener(this);

        ll2.addView(btn2);

        setContentView(ll);
    }

    @Override
    public void onClick(View v) 
    {
        if (page2)
        {
            LinearLayout ll = CreateLayout();

            setContentView(ll);

            page2 = false;
        }
        else
        {
            setContentView(ll2);
            page2 = true;
        }
    }

}

/*
 * GListView
 */
package com.gabysoft.memoryleak;

import android.content.Context;
import android.database.DataSetObserver;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ListAdapter;
import android.widget.ListView;
import android.widget.TextView;

public class GListView extends ListView implements ListAdapter
{
    Context m_context;
    DataSetObserver m_observer = null;

    public GListView(Context context) 
    {
        super(context);

        m_context    = context;

        setAdapter(this);

        setChoiceMode(CHOICE_MODE_SINGLE);
    }

    /*
     * ListAdapter 
     */


    @Override
    public boolean areAllItemsEnabled() 
    {
        return true;
    }

    @Override
    public boolean isEnabled(int position) 
    {
        return true;
    }

    @Override
    public int getCount() 
    {
        return(0);
    }    

    @Override
    public Object getItem(int position) 
    {
        return null;
    }

    @Override
    public long getItemId(int position) 
    {
        return(position);
    }

    @Override
    public int getItemViewType(int position) 
    {
        return 0;
    } 

    @Override
    public View getView(int position, View convertView, ViewGroup parent) 
    {
        TextView tv = new TextView(m_context);

        tv.setText("Item");

        return(tv);
    }

    @Override
    public int getViewTypeCount() 
    {
        return 1;
    }

    @Override
    public boolean hasStableIds() 
    {
        return false;
    }

    @Override
    public boolean isEmpty() 
    {
        return false;
    }

    @Override
    public void registerDataSetObserver(DataSetObserver observer) 
    {
        m_observer    = observer;
    }

    @Override
    public void unregisterDataSetObserver(DataSetObserver observer) 
    {
        m_observer    = null;
    }
}
+1  A: 

Your GListView objects are being garbage collected. Leastways, they are called with finalize() when a manual GC is done from DDMS. Unfortunately, the DDMS HPROF file seems incompatible with my version of jhat, and I'm not an Eclipse user so I do not have ready access to MAT.

CommonsWare
Strange, that is not the behavior that I am seeing. I added an override finalize method to my GListView class, and I only ever see it called one time, even though I create many GListView objects that need to be recycled. I uploaded the version with the log to http://gabysoft.com/download/MemoryLeak.zip. Is it possible that I am seeing this because I am using Eclipse and you are not?
John Gaby
@John Gaby: I downloaded your latest ZIP, compiled and installed it into a 2.2 emulator, clicked the button 40 times, did a manual GC from DDMS, and got 21 finalize debug messages. You might want to try running the test from outside of Eclipse once. To do this, go to your project directory, and run `android update project -p .` (assumes your SDK `tools/` directory is in your `PATH`). Install Apache Ant if you don't have it, start an emulator via the `android` command, and run `ant clean install`. Run DDMS via `ddms`.
CommonsWare
Thanks again for you help. I will have to track down Apache Ant and give it a try. I can tell you that the GListView is the source of my problem even in my real app. I have a page which contains a GListView has a large graphic as a background. If I reload that page enough times, the program crashes with an out of memory error. I fixed it so that I remove the GListView from it parent ViewGroup, and, although the GListView is not freed, the parent is. After this change, I can load the page many times without a crash. I do not understand why I see this with Eclipse.
John Gaby
@John Gaby: "If I reload that page enough times, the program crashes with an out of memory error." -- my guess is that is tied to your "large graphic as a background". Try not to recreate that `Bitmap` every time.
CommonsWare
Thanks, I know that. The problem is that the old page was not being freed by the GC. I tracked it down to the fact that the GListView was not being freed. If, after I am through with that page, I remove the GListView from the ViewGroup, then the page does get freed, and I no longer see the Out Of Memory condition. However, using the Memory Analyzer, I can see that the GListViews are still not being freed. I am trying to get Ant to work so that I can investigate the problem from that direction. It is very strange.
John Gaby
I might also add, that I NEED the page with the graphic to be freed so that I can load other pages which have different graphics, without running out of memory. Hence, I cannot simply cache that image for later use. The whole point is to get that image release.
John Gaby
@John Gaby: Call `recycle()` on the `Bitmap` as soon as you are done with it.
CommonsWare
I hate to bother you again, but I have tried running it using ant as you suggested. Everything seems to be working correctly, however, I am not getting any of my log message at all. Something may be wrong with my setup, since the DDMS is not showing the names of the processes. I have uploaded a screen shot (http://gabysoft.com/download/ddms.png) of my DDMS with the hope that you can tell me what is going wrong. Thanks again.
John Gaby
As an update, I put in a log in my onCreate to see if I was getting any log messages, and it does show up. So I am thinking the my clicking of the GC button is not causing a GC (there is no message to that effect). What does it mean by 'DDM-aware? - no'. Does this mean that something is not set up correctly? Thanks
John Gaby
I found that if I use the DDMS from within eclipse, I can get the GC button to work, and indeed, as you have said, my project built with ant does recycle the GListViews properly. However when it is built with Eclipse it most defiantly does not. Where would be the best place to go to try and figure out what is going on with my Eclipse version? Thanks.
John Gaby
@John Gaby: With respect to your DDMS screen, something is out of whack, with all those ?'s in the process name column. I have never seen that with the emulator. With respect to your other issues, I am starting to wonder if there is some problem with your development environment. However, I do not have any concrete advice on how to proceed. Sorry!
CommonsWare
Thanks for the help. Maybe I will try and install a new version on Eclipse on a clean machine and see if I have the same problem. I really appreciate your trying to help, though!
John Gaby
Well, I installed Eclipse, and the Android SDK on a completely virgin XP machine (virtual), and built the project again from scratch, and I get exactly the same result. I posted a screen shot of the DDMS (http://gabysoft.com/download/ddms2.png) which shows the results. You see the log for onCreate. I then switched the pages 20 times and did a GC. You can see the Heap GC request line, followed by a SINGLE GListView finalize call. Other people who use Eclipse MUST be experiencing the same problem. Is there any way to contact Google about this issue? Thanks.
John Gaby
@John Gaby: File an issue at http://b.android.com, attaching all this stuff. Make sure "Eclipse" appears in the subject line, and attach links to your various SO and android-developers discussions on the topic. Plus, of course, attach your demo project. If you think of it, @ me back here with a link to the issue.
CommonsWare
Well, talk about the proverbial wild geese! It appears to have nothing to do with whether it is built by Eclipse or Ant. It also has nothing to do with whether it is installed by Eclipse or Ant. What it DOES depend on is whether I am debugging the process or not. If I debug it, then the ListViews are not freed. If I run it without debugging then they are. I cannot believe that I have just spent the last week tracking down a non-bug. Thanks much for your help, I really appreciate it.
John Gaby