1

I'm quite new to Android development and was about to finish my first application when I noticed that I have a huge memory leak.

The leak occurs when I start a new activity from within a thread:

private static class CatalogRowOnClickListener implements OnClickListener 
{
    List<CatalogRow> catRows;
    WeakReference<CatalogViewActivity> mActivity;

    CatalogRowOnClickListener(WeakReference<CatalogViewActivity> mActivity) 
    {
        this.mActivity = mActivity;
        catRows = new ArrayList<CatalogRow>();
    }


    public void addCatalogRow(CatalogRow row) 
    {
        catRows.add(row);
    }

    public void onClick(View v) 
    {   
        v.setBackgroundColor(Color.argb(150, 255, 255, 255));


        final ProgressBar linProgressBar = (ProgressBar) v.findViewById(R.id.CatProgressBar);
        linProgressBar.setVisibility(View.VISIBLE);

        final View fv = v;
        final fItem fhitem = findFItem(fv);
        try
        {
            new Thread()
            {


                public void run() 
                {
                    initializeApp();

                }
                public void initializeApp()
                {
                    // Initialize application data here


                    FItemListStore.getItemsFromWebservice(fhitem);
                    boolean isArticleOverview = false;

                    for(FItem item: FItemListStore.fItemViewStorage) 
                    {   
                        if( item instanceof fiArticle ) 
                        {
                            isArticleOverview = true;
                            break;
                        }
                    }


                    Intent intent = new Intent();
                    intent.setClassName("nl.project.Android", "nl.project.Android.Activities.HomeViewActivity");
                    Bundle bundle = new Bundle();

                    if(isArticleOverview)
                    {
                        bundle.putInt("OverviewNr", 1);
                    }
                    else
                    {
                        bundle.putInt("OverviewNr", 0);
                    }
                    intent.putExtras(bundle);

                    //if( mActivity.get() != null )
                        mActivity.get().startActivity(intent);

                    mActivity.clear();

                }
            }.start();
        }
        catch (Exception e) {}

        //Debug.stopMethodTracing();
        //android.os.Debug.stopMethodTracing();
    }     

A HPROF dump of the app after executing this code shows that the thread will always be retained in the heap and grow exponentially. Researching this points towards referencing a context inside a thread causing leaks. So I changed this code a few times until it became this version with only a weak reference in a static class (that's some things that were mentioned in other posts)

The reason why I want to start the Activity in a thread is because I need to read data from a slow and big webservice, which takes some time. I displayed a loading bar in the activity, highlighting the selected item until the XMLparser is done, and started a new activity afterwards.

Might it be that there is no correct way to start an activity within a thread without causing a memory leak? And, if so, is there another way for me to have the intent started after the xml parser finishes his job?


On advice of Vladimir and Heiko, I changed my thread to a AnyncTask. The grow of my retained heap has stopped but the AsyncTask threads I started still remain in the heap. Here's my new code:

private static class QueryFHTask extends AsyncTask<FredHopperItem, Integer, Boolean> 
 {

     WeakReference<CatalogViewActivity> mActivity;

     QueryFHTask(WeakReference<CatalogViewActivity> mActivity) 
     {
        this.mActivity = mActivity;

     }


     protected void onPreExecute() 
     {

     }
     protected Boolean doInBackground(FItem... items) {
         int count = items.length;
         long totalSize = 0;
         for (int i = 0; i < count; i++) {
             FItemListStore.getItemsFromWebservice(items[i]);
             boolean isArticleOverview = false;

             for(FItem item: FItemListStore.fhItemViewStorage) 
             {  
                if( item instanceof FArticle ) 
                {
                    isArticleOverview = true;
                    break;
                }
             }

             Intent intent = new Intent();
             intent.setClassName("nl.project.Android", "nl.project.Android.Activities.HomeViewActivity");
             Bundle bundle = new Bundle();

             if(isArticleOverview)
             {
                bundle.putInt("OverviewNr", 1);
             }
             else
             {
                bundle.putInt("OverviewNr", 0);
             }
             intent.putExtras(bundle);
             mActivity.get().startActivity(intent);

         }
         return true;
     }

     protected void onProgressUpdate(Integer... progress) {
         //setProgressPercent(progress[0]);

     }

     protected void onPostExecute(Long result) {
         //showDialog("Downloaded " + result + " bytes");
     }
 }

private static class CatalogRowOnClickListener implements OnClickListener 
{
    List<CatalogRow> catRows;
    WeakReference<CatalogViewActivity> mActivity;

    CatalogRowOnClickListener(WeakReference<CatalogViewActivity> mActivity) 
    {
        this.mActivity = mActivity;
        catRows = new ArrayList<CatalogRow>();
    }


    public void addCatalogRow(CatalogRow row) 
    {
        catRows.add(row);
    }

    public void onClick(View v) 
    {   
        v.setBackgroundColor(Color.argb(150, 255, 255, 255));
        final ProgressBar linProgressBar = (ProgressBar) v.findViewById(R.id.CatProgressBar);
        linProgressBar.setVisibility(View.VISIBLE);

        final View fv = v;
        final fItem fhitem = findFItem(fv);     
        new QueryFHTask(mActivity).execute(fhitem);       


    }

To clarify my problem, here's a copy of one of my HPROF thread overview heap dumps (it's very hard to read because I was not allowed to post html with links and images like in MAT because of my user level...). The AsyncTask threads retain a heap of 376 or 344. As I browser the application longer, there will be more and more retained heaps from AsyncActivity threads...

Name Instance Shallow Heap Retained Heap Context Class Loader 
main java.lang.Thread @ 0x40027550 80 1.464 dalvik.system.PathClassLoader @ 0x4051ce30 
JDWP java.lang.Thread @ 0x40515838 80 384  
AsyncTask #6 java.lang.Thread @ 0x406ea788 80 376 dalvik.system.PathClassLoader @ 0x4051ce30 
AsyncTask #7 java.lang.Thread @ 0x40690178 80 376 dalvik.system.PathClassLoader @ 0x4051ce30 
AsyncTask #4 java.lang.Thread @ 0x4068a630 80 376 dalvik.system.PathClassLoader @ 0x4051ce30 
AsyncTask #3 java.lang.Thread @ 0x406851f0 80 376 dalvik.system.PathClassLoader @ 0x4051ce30 
AsyncTask #5 java.lang.Thread @ 0x405d9f28 80 376 dalvik.system.PathClassLoader @ 0x4051ce30 
AsyncTask #2 java.lang.Thread @ 0x40539db0 80 376 dalvik.system.PathClassLoader @ 0x4051ce30 
AsyncTask #1 java.lang.Thread @ 0x40517180 80 376 dalvik.system.PathClassLoader @ 0x4051ce30 
AsyncTask #8 java.lang.Thread @ 0x4068cdb0 80 344 dalvik.system.PathClassLoader @ 0x4051ce30 
AsyncTask #9 java.lang.Thread @ 0x4068b558 80 344 dalvik.system.PathClassLoader @ 0x4051ce30 
AsyncTask #10 java.lang.Thread @ 0x4068a178 80 344 dalvik.system.PathClassLoader @ 0x4051ce30 
AsyncTask #11 java.lang.Thread @ 0x406639f0 80 344 dalvik.system.PathClassLoader @ 0x4051ce30 
AsyncTask #12 java.lang.Thread @ 0x40661b00 80 344 dalvik.system.PathClassLoader @ 0x4051ce30 
Binder Thread #2 java.lang.Thread @ 0x40519b20 80 344  
Binder Thread #1 java.lang.Thread @ 0x40516868 80 344  
Thread-2 java.util.logging.LogManager$2$1 @ 0x40195298 80 168 dalvik.system.PathClassLoader @ 0x400277f8 
Signal Catcher java.lang.Thread @ 0x40515778 80 160  
Compiler java.lang.Thread @ 0x405158e8 80 152  
HeapWorker java.lang.Thread @ 0x40515618 80 152  
GC java.lang.Thread @ 0x405156d0 80 136  
Total: 21 entries  1.680 7.656 
Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
mrTanaka
  • 13
  • 5
  • If you are debugging then the debugger "holds" onto some threads so you won't necessarily see them disappear from the debugger even after they've finished. Try just running your app and check if the threads are gone. See the two answers to this question for more info http://stackoverflow.com/questions/3077461/asynctask-threads-never-die-android – Joseph Earl May 17 '11 at 14:34
  • It appears that the threads are not yet gone. I don't know if a HPROF dump shows threads that are already finished but I can say that the application memory slowly keeps getting bigger and bigger, until the app eventually crashes under the load. This only happens when I use threads or AsyncTasks, but the AsyncTasks take much longer to make the app crash – mrTanaka May 18 '11 at 12:04

2 Answers2

1

Oh my, there is a special thing to start an activity after the some work in background is done. It is AsyncTask with ProgressDialog bounded. See an example.

Vladimir Ivanov
  • 42,730
  • 18
  • 77
  • 103
  • Thanks! I tried AsyncTask and it indeed improves the performance. threads are still left in the heap afterwards unfortunately, but they don't grow over time anymore. Still, it would be great if I could get those threads to disappear, I'll update my question shortly – mrTanaka May 17 '11 at 12:58
0

You should look at AsyncTask , where you do the loading in doInBackground(), set up the status bar in onPreExecute, publish progress updates with publishProgess and then remove the progress bar in onPostExecute.

Heiko Rupp
  • 30,426
  • 13
  • 82
  • 119
  • 1
    Thanks! I tried AsyncTask and it indeed improves the performance. threads are still left in the heap afterwards unfortunately, but they don't grow over time anymore. Still, it would be great if I could get those threads to disappear, I'll update my question shortly – mrTanaka May 17 '11 at 12:58