0

My code has a massive memory leak on orientation change. The heap is full after rotating the phone once or twice. I tried to track down the leak for two days now without success.

The application uses SDK 15 (min 14).

MainActivity shows an ActionBar with list navigation (NAVIGATION_MODE_LIST). Navigating the spinner replaces the fragment shown.

The code works.

ListNavigationItem which contains the information needed to instantiate Fragments:

public class ListNavigationItem
{
    public final String fragmentClass;
    public final String title; // Used as fragment tag and as title

    public ListNavigationItem(String fragmentClass, String title)
    {
        this.fragmentClass = fragmentClass;
        this.title = title;
    }

    @Override
    public String toString()
    {
        return title;
    }
}

And finally my (simplified) MainActivity:

public class MainActivity extends Activity implements OnNavigationListener
{
    private MyApplication app;
    private ArrayAdapter<ListNavigationItem> adapter;

    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.main);

        app = (MyApplication) getApplicationContext();

        ActionBar actionBar = getActionBar();
        actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_LIST);
        actionBar.setDisplayShowTitleEnabled(false);
        actionBar.setDisplayShowHomeEnabled(false);

        adapter = new ArrayAdapter<ListNavigationItem>(this, android.R.layout.simple_spinner_dropdown_item);
        adapter.add(new ListNavigationItem(FirstFragment.class.getName(), "First"));
        adapter.add(new ListNavigationItem(SecondFragment.class.getName(), "Second"));

        actionBar.setListNavigationCallbacks(adapter, this);
    }

    public boolean onNavigationItemSelected(int itemPosition, long itemId)
    {
        ListNavigationItem item = adapter.getItem(itemPosition);
        if (item == null)
            return false;

        Fragment f = getFragmentManager().findFragmentByTag(item.title);

        if (f == null)
            f = Fragment.instantiate(this, item.fragmentClass);
        else if (f.isAdded())
            return true;

        FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
        fragmentTransaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
        fragmentTransaction.replace(R.id.main_content, f, item.title);
        fragmentTransaction.commit();

        return true;
    }

    @Override
    protected void onDestroy()
    {
        adapter = null; // Do I need this?
        app = null;
        super.onDestroy();
    }
}

Manifest:

<?xml version="1.0" encoding="utf-8"?>
<manifest
    xmlns:android="http://schemas.android.com/apk/res/android"
    package="at.company.app"
    android:versionCode="1"
    android:versionName="1.0">

    <uses-sdk android:minSdkVersion="14" />
    <uses-sdk android:targetSdkVersion="15" />

    <application
        android:name=".Application"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name">

        <activity
            android:name=".MainActivity"
            android:label="@string/app_name">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

    </application>

</manifest>

A MAT heap analysis says that the class android.content.res.Resources occupies 57% of the available memory, and android.graphics.Bitmap occupies 12%. My fragments, however, contain just a simple TextView.

The MAT histogram shows that there are 4 Objects (Instances, I assume?) of MainActivity. Also, logcat shows that the heap is frequently being emptied after orientation change (GC_CONCURRENT).

The official Dev Guide discourages from using android:configChanges="orientation". (Source)

I am relatively new to Java and the Android platform, and I would appreciate any help.

David
  • 3,392
  • 3
  • 36
  • 47

3 Answers3

0

I don't know why, how, or if this is my problem yet. But I seemed to stop getting the "grow heap" output when i removed the setTransition for the fragment transaction. Try that?

bboybz
  • 1
0

A common cause for Memory leaks is referring to the Activity instead of the Application in context references. The use of this (for e.g. adapter = new ArrayAdapter<ListNavigationItem>(this ) may cause a memory leak as explained in this question, so try replacing it with getApplicationContext() to refer to the Application context instead.

Community
  • 1
  • 1
Mohamed_AbdAllah
  • 5,311
  • 3
  • 28
  • 47
  • Thanks for the answer! I'll verify it the next time I work on the app again. – David Oct 06 '12 at 13:20
  • Changed `this` to `getApplicationContext()`, it still causes the GC to run every 3-5 seconds. It doesn't seem to affect the performance though. – David Oct 07 '12 at 10:26
  • GC running does not mean that you have a leak. On the contrary, Memory leak means that you have objects in the memory with references to them that is **preventing** the GC from running. So the heap keeps filling and you get out of memory exception. Check if your memory keeps growing, this indicates if you have a leak or not (not the GC). – Mohamed_AbdAllah Oct 07 '12 at 19:52
0

It will depend on your manifest config but to test you could do a finish onConfigurationChanged.. otherwise it will just create a new activity without destroying the old one potentially..

Manfred Moser
  • 29,539
  • 13
  • 92
  • 123
  • Thank you for your answer. I don't understand however, in what class should I implement the `onConfigurationChanged()` method? And what should I test? As far as I understand, the method is not being called without the `android:configChanges` attribute. – David Feb 14 '12 at 07:27
  • it is called whenever a screen rotates.. and you implement it in the activity.. just for investigation btw.. – Manfred Moser Feb 14 '12 at 18:13