35

I followed "Avoiding Memory Leaks" article from here.

However the proposed solution does not solve the leak problem. I tested this with android emulator on Windows XP (SDK 2.3.1). I dumped the heap and checked the main activity is still in the heap (I used MAT)

Here's what I did:

  1. create HelloWorld app with HelloWorldActivity (it has no child views)
  2. run Emulator and launch HelloWorld app.
  3. close it by clicking back-key.
  4. Cause gc in DDMS and dump heap <-- Here I found HelloWorldActivity instance.
  5. 'Path to GC Roots' from it shows the following path.

HelloWorldActivity <- PhoneWindow$DecorView <- InputMethodManager

InputMethodManager is a singleton and three references to DecorView which references HelloWorldActivity.

I can't understand why InputMethodManager still references DecorView instance even after the activity is destroyed.

Is there any way to make sure that the main activity is destroyed and GC-able after closing it?

Suragch
  • 484,302
  • 314
  • 1,365
  • 1,393
hjy
  • 523
  • 5
  • 9
  • I have tested this on two phones and in both case the Activity (without overrides) is GC-ed after the back press. – kupsef Jun 28 '14 at 07:34
  • the link in the question to the article doesn't work. Here is the correct one (i assume this is the intended link anyway): http://android-developers.blogspot.com/2009/01/avoiding-memory-leaks.html – Andre Perkins Jul 02 '14 at 16:59

3 Answers3

20

It seems that calling InputMethodManager's methods 'windowDismissed' and 'startGettingWindowFocus' do the stuff.

Something like this:

@Override
protected void onDestroy()
{
    super.onDestroy();
    //fix for memory leak: http://code.google.com/p/android/issues/detail?id=34731
    fixInputMethodManager();
}

private void fixInputMethodManager()
{
    final Object imm = getSystemService(Context.INPUT_METHOD_SERVICE);

    final Reflector.TypedObject windowToken
        = new Reflector.TypedObject(getWindow().getDecorView().getWindowToken(), IBinder.class);

    Reflector.invokeMethodExceptionSafe(imm, "windowDismissed", windowToken);

    final Reflector.TypedObject view
        = new Reflector.TypedObject(null, View.class);

    Reflector.invokeMethodExceptionSafe(imm, "startGettingWindowFocus", view);
}

Reflector's code:

public static final class TypedObject
{
    private final Object object;
    private final Class type;

    public TypedObject(final Object object, final Class type)
    {
    this.object = object;
    this.type = type;
    }

    Object getObject()
    {
        return object;
    }

    Class getType()
    {
        return type;
    }
}

public static void invokeMethodExceptionSafe(final Object methodOwner, final String method, final TypedObject... arguments)
{
    if (null == methodOwner)
    {
        return;
    }

    try
    {
        final Class<?>[] types = null == arguments ? new Class[0] : new Class[arguments.length];
        final Object[] objects = null == arguments ? new Object[0] : new Object[arguments.length];

        if (null != arguments)
        {
            for (int i = 0, limit = types.length; i < limit; i++)
            {
                types[i] = arguments[i].getType();
                objects[i] = arguments[i].getObject();
            }
        }

        final Method declaredMethod = methodOwner.getClass().getDeclaredMethod(method, types);

        declaredMethod.setAccessible(true);
        declaredMethod.invoke(methodOwner, objects);
    }
    catch (final Throwable ignored)
    {
    }
}
Denis Gladkiy
  • 2,084
  • 1
  • 26
  • 40
  • 1
    onStop should be a better candidate though. – Snicolas Jul 05 '14 at 15:26
  • 3
    @Snicolas it depends. "onStop" is just a signal that your activity is not visible. "onDestroy" is defenetly activity's "exit point". Calling the methods can corrupt the state and behaviour after "onStart" is formally will be undefined. So one should test a lot after placing the workaround inside "onStop". – Denis Gladkiy Jul 09 '14 at 10:45
  • Thanks! I think the call to `fixInputMethodManager();` needs to happen before `super.onDestroy();`, though. At least that worked for me. – Jonas Lüthke Jan 07 '16 at 13:28
  • I used this fix in a number of Activities successfully. In one that contains fragments the trick did not work. Do you need to add this to onDestroy of the fragments as well? – barq Feb 22 '16 at 09:22
  • "windowDismissed" seems to be enough. Why startGettingWindowFocus? – ernazm Mar 28 '17 at 13:24
  • what is the import for Reflector? – Riddhi Shah Feb 02 '21 at 05:50
3

If I understand your question correctly, the answer is: no, you cannot make sure the activity is gc'ed. Your activity's onDestroy() method should have been called and the activity shut down. That does not mean, however, that the process is killed or that the activity is gc'ed; that's managed by the system.

Ted Hopp
  • 232,168
  • 48
  • 399
  • 521
  • 1
    Actually, when being in a rush - or under pressure - one tends to follow the workflow 'activity start-stop-hprof-dump'. But don't forget that you can't know when the GC will happen, ergo your 'leaked' activity might be gc'ed in 3 minutes, but again, you inspected the un-gc'ed heap dump... That was the case with me, anyways. – Bondax Jun 12 '12 at 07:46
  • 1
    you can always take the dump after doing a GC, it helps – redDragonzz Jan 24 '14 at 10:55
3

I have noticed that some listeners tend to keep a reference to the activity under some circumstances, even after the activity supposedly has been finished. A rotation from portrait to landscape can, for example, restart your activity and if you're unfortunate your first activity is not gc-ed properly (in my case due to some listeners still holding a reference to it).

Being a former C/C++ programmer I have it implanted in my spine to "un-set" any listeners in Activity.onDestroy() (setXyzListener(null)).

EDIT:

Just as Ted commented below, one should indeed "set" and "un-set" listeners in Activity.onResume() and Activity.onPause() respectively.

dbm
  • 10,376
  • 6
  • 44
  • 56
  • 3
    This is the right idea. Generally, though, the right time to unregister listeners is in `onPause()`, not in `onDestroy()`. (This also implies that listeners should be registered in `onResume()`, not earlier in the lifecycle.) Why is this? After `onPause()` returns, there's no guarantee that any further call-backs will be made to your activity. (That is, your activity is killable after `onPause()` returns.) – Ted Hopp Feb 18 '11 at 17:01
  • @Ted Hopp: Great comment! I've changed my answer accordingly. – dbm Feb 18 '11 at 17:18
  • 3
    @Ted thank you. In my case I found that InputMethodManager kept referencing to an activity' DecorView as a current root view even after it had been finished. Notice that the activity is empty (just extends Activity and no overrides) and I didn't set any listeners to it. Do I have to do something to unregister activity from InputMethodManager ? If so, how can I do that? – hjy Feb 18 '11 at 23:31
  • I don't know of anything extra you should do, actually. As you mentioned: it's an empty activity, you haven't actively added any listeners or other dependencies yourself. Every existing dependency is added by "the system", then I think it's fair to expect "the system" to remove those dependencies as well. Have you tested the situation over a longer perspective? What happens if you wait for a couple of minutes? Is your Activity GC-ed after, say, 5 minutes? What if you use the phone/emulator regularly? Is the memory leak really a concistent memory leak? – dbm Feb 19 '11 at 10:17