主活动在销毁后不会被垃圾回收,因为它被输入法管理器间接引用。

35

我按照这里的“避免内存泄漏”文章进行操作。

然而,该解决方案并不能解决泄漏问题。我在Windows XP (SDK 2.3.1)上使用Android模拟器进行了测试。我导出了堆信息并检查主活动仍然存在于堆中(我使用MAT)。

下面是我所做的:

  1. 创建HelloWorld应用程序,其中包含HelloWorldActivity(它没有子视图)
  2. 运行模拟器并启动HelloWorld应用程序。
  3. 通过点击返回键关闭应用程序。
  4. 在DDMS中执行垃圾回收并导出堆 <-- 在这里我发现了HelloWorldActivity实例。
  5. 从它的'Path to GC Roots'中显示以下路径。

HelloWorldActivity <- PhoneWindow$DecorView <- InputMethodManager

InputMethodManager是一个单例,有三个对DecorView的引用,而DecorView又引用了HelloWorldActivity。

我无法理解为什么即使活动已被销毁,InputMethodManager仍然引用DecorView实例。

是否有任何方法可以确保主活动在关闭后被销毁且可进行垃圾回收?


我已经在两部手机上测试过了,在两种情况下,Activity(没有覆盖)在返回键按下后被GC回收了。 - kupsef
问题中的链接到文章无法打开。这是正确的链接(我认为这应该是预期的链接):http://android-developers.blogspot.com/2009/01/avoiding-memory-leaks.html - Andre Perkins
3个回答

20

看起来调用InputMethodManager的方法'windowDismissed'和'startGettingWindowFocus'可以完成这件事。

像这样:

@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);
}

反射器的代码:

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)
    {
    }
}

1
onStop 应该是更好的选择。 - Snicolas
3
这取决于情况。 "onStop" 只是一个信号,表示您的活动不可见。 "onDestroy" 明确是活动的“退出点”。调用这些方法可能会在“onStart”之后破坏状态和行为,因此应在“onStop”内部放置解决方法后进行大量测试。 - Denis Gladkiy
谢谢! 我认为调用fixInputMethodManager();需要在super.onDestroy();之前发生,至少这样对我有效。 - Jonas Lüthke
我已经成功地在许多活动中使用了这个修复方法。但是,在一个包含片段的活动中,这个技巧却不起作用。你需要在片段的onDestroy中也加入这个吗? - barq
“windowDismissed” 似乎已经足够了。为什么要开始获取窗口焦点? - ernazm
Reflector 的导入是什么? - Riddhi Shah

3
如果我理解你的问题正确,答案是:不,你不能确保该活动被垃圾回收。你的活动的onDestroy()方法应该已经被调用并关闭了该活动。然而,并不意味着进程被杀掉或该活动被垃圾回收;这由系统管理。

1
实际上,当你匆忙或在压力下时,往往会遵循“活动开始-停止-hprof-dump”的工作流程。但不要忘记,你无法知道GC何时发生,因此你的“泄漏”活动可能在3分钟内被gc'ed,但是你检查了未gc'ed堆转储...这就是我的情况,无论如何。 - Bondax
1
你可以在进行垃圾回收后进行转储,这有助于。 - redDragonzz

3
我注意到在某些情况下,一些听众会保留对活动的引用,即使活动已经结束。例如,从纵向旋转到横向可能会重新启动您的活动,如果您不幸的话,您的第一个活动可能无法正确地gc-ed(在我的情况下,由于一些听众仍然持有对它的引用)。
作为一名前C/C++程序员,我习惯于在Activity.onDestroy()中“取消设置”任何侦听器(setXyzListener(null))。
编辑:
正如Ted在下面评论中所说,确实应该在Activity.onResume()Activity.onPause()中分别“设置”和“取消设置”侦听器。

3
这是正确的想法。然而,通常注销监听器的正确时间是在“onPause()”而不是“onDestroy()”中。这也意味着监听器应该在生命周期的“onResume()”中注册,而不是之前。为什么呢?因为在“onPause()”返回后,不能保证会有任何进一步的回调被发送到您的活动。也就是说,在“onPause()”返回后,您的活动可能会被杀死。 - Ted Hopp
@Ted Hopp:非常好的评论!我已经相应地修改了我的答案。 - dbm
3
@ Ted谢谢。在我的情况下,我发现InputMethodManager仍然将一个活动的DecorView作为当前根视图引用,即使它已经完成。请注意,该活动为空(只是扩展了Activity而没有覆盖),我也没有为它设置任何监听器。我需要做些什么来取消注册活动的InputMethodManager吗?如果需要,我应该如何做到这一点? - hjy
实际上,我不知道你是否需要做任何额外的事情。正如你所提到的:这是一个空的活动,你没有主动添加任何侦听器或其他依赖项。每个现有的依赖项都是由“系统”添加的,那么我认为可以期望“系统”也会删除这些依赖项。你是否在更长的时间内测试过这种情况?如果你等待几分钟会发生什么?你的Activity在5分钟后是否被GC-ed?如果你经常使用手机/模拟器会发生什么?内存泄漏真的是一致的吗? - dbm

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接