Android中的处理程序和内存泄漏

48
请看下面的代码:
public class MyGridFragment extends Fragment{
    
    Handler myhandler = new Handler() {
        @Override
        public void handleMessage(Message message) {
            switch (message.what) {
                case 2:   
                    ArrayList<HashMap<String,String>> theurls = (ArrayList<HashMap<String,String>>) message.obj;
                    urls.addAll(theurls);
                    theimageAdapter.notifyDataSetChanged();
                    dismissBusyDialog();
                    break;
            }
        }
    }
}

当我像这样使用处理程序时,我会收到警告“处理程序应该是静态的,否则容易导致内存泄漏。”有人可以告诉我做这件事的最佳方法是什么吗?


2
我不确定你是否正确使用了handler。请查看这个指南:http://www.vogella.com/articles/AndroidPerformance/article.html。在那里的示例代码中,它没有被声明为静态的。 :/ - Thomas Clayson
即使像那样使用它,我仍然遇到相同的错误。这种情况以前从未发生过,直到昨晚我升级了我的Android SDK。现在,只要将处理程序声明为类变量,就会弹出此lint警告。 - Rasmus
那你考虑将处理程序声明为静态的呢? - Zsombor Erdődy-Nagy
@Zsombor,我指的是处理程序内部的非静态对象。 - Rasmus
2
查看这篇博客文章,以获取更深入的分析。 - Adrian Monk
6个回答

101

最近我在自己的代码中更新了类似的内容。我只是把匿名Handler类变成了受保护的内部类,然后Lint警告就消失了。看看下面的代码是否适用于您:

public class MyGridFragment extends Fragment{

    static class MyInnerHandler extends Handler{
        WeakReference<MyGridFragment> mFrag;

        MyInnerHandler(MyGridFragment aFragment) {
            mFrag = new WeakReference<MyGridFragment>(aFragment);
        }

        @Override
        public void handleMessage(Message message) {
            MyGridFragment theFrag = mFrag.get();
            switch (message.what) {
            case 2:
                ArrayList<HashMap<String,String>> theurls = (ArrayList<HashMap<String,String>>) message.obj;
                theFrag.urls.addAll(theurls);
                theFrag.theimageAdapter.notifyDataSetChanged();
                theFrag.dismissBusyDialog();
                break;
            }//end switch
        }
    }
    MyInnerHandler myHandler = new MyInnerHandler(this);
}
你可能需要更改我放置“theFrag”的位置,因为我只能猜测那些引用的内容。

13
在mFrag.get()之后,应该再添加一个检查来查看theFrag是否为null,因为它可能被垃圾回收。 - Catalin Morosan
1
根据您的用例而定。在我的个人代码中,大多数情况下将内部静态类定义设置为私有,因为匿名类定义不应在其他地方使用(甚至不被后代使用)。当仅限于定义它的类时,每当父Fragment被销毁时,我们的内部类引用应该在Fragment变为NULL之前被销毁。我们使用WeakReference,以便我们的内部类不会干扰Fragment何时被垃圾回收。如果您使内部类公开或不确定,请在使用之前检查theFrag!= null。 - Uncle Code Monkey
1
这个答案绝对救了我!谢谢你发布它! - ossys
这种方法相对于仅将处理程序设置为静态的方法有何优势? - mgibson
@mgibson 是的 - 这种方法公开了父类的函数和可见成员,可以在handleMessage()中修改它们,而无需将它们声明为静态。使用WeakReference允许系统避免处理程序泄漏的可能性,同时避免使潜在的许多字段静态化所带来的内存占用增加。 - CCJ
显示剩余2条评论

11

这是一个我写的比较有用的小类,涉及到IT技术相关内容。但不幸的是,它仍然相当冗长,因为你无法使用匿名静态内部类。

import java.lang.ref.WeakReference;
import android.os.Handler;
import android.os.Message;

/** A handler which keeps a weak reference to a fragment. According to
 * Android's lint, references to Handlers can be kept around for a long
 * time - longer than Fragments for example. So we should use handlers
 * that don't have strong references to the things they are handling for.
 * 
 * You can use this class to more or less forget about that requirement.
 * Unfortunately you can have anonymous static inner classes, so it is a
 * little more verbose.
 * 
 * Example use:
 * 
 *  private static class MsgHandler extends WeakReferenceHandler<MyFragment>
 *  {
 *      public MsgHandler(MyFragment fragment) { super(fragment); }
 * 
 *      @Override
 *      public void handleMessage(MyFragment fragment, Message msg)
 *      {
 *          fragment.doStuff(msg.arg1);
 *      }
 *  }
 * 
 *  // ...
 *  MsgHandler handler = new MsgHandler(this);
 */
public abstract class WeakReferenceHandler<T> extends Handler
{
    private WeakReference<T> mReference;

    public WeakReferenceHandler(T reference)
    {
        mReference = new WeakReference<T>(reference);
    }

    @Override
    public void handleMessage(Message msg)
    {
        if (mReference.get() == null)
            return;
        handleMessage(mReference.get(), msg);
    }

    protected abstract void handleMessage(T reference, Message msg);
}

如果我不需要重写handleMessage方法,只是使用Handler来发布Runnable,那么我可以只使用new Handler()吗? - virsir
5
如果 (mReference.get() == null) 便返回;handleMessage(mReference.get(), msg); 是个糟糕的做法,因为在 null 检查和 handleMessage 方法之间,引用可能被垃圾回收导致空指针异常。因此更好的方式是先将返回值存储在 T reference 变量中,然后检查是否为空,并将本地变量传递给 handleMessage 方法。 - Kerem Kusmezer

5

根据ADT 20更改,看起来您应该将其设置为静态。

新的Lint检查:

检查Fragment类是否可实例化。如果意外地使片段内部类非静态或忘记具有默认构造函数,则在系统尝试在配置更改后重新实例化片段时可能会出现运行时错误。

查找处理程序泄漏:此检查确保处理程序内部类不会持有对其外部类的隐式引用。


那么你认为我应该如何处理上述问题?你需要看更多的代码吗?如果是的,请告诉我。 - Rasmus
如果您无法将Handler设置为静态的,最好的方法可能是将其移动到父Activity并将引用传递给Fragment。 - Geobits
感谢您的回复,Geobits...但将其移动到父活动仍然无法消除警告。虽然在我的情况下,内部类不会比外部类存在更长时间,但摆脱警告是我更关心的事情。 - Rasmus

4
如果你阅读关于AccountManager或PendingIntent的文档,你会发现一些方法将Handler作为其中一个参数之一。
例如:
- onFinished - 发送完成后回调的对象,如果不需要回调则为null。 - handler - 标识回调所在线程的Handler。如果为null,则回调将从进程的线程池中发生。
想象一下这种情况。某个Activity调用PendingIntent.send(...)并放置了非静态内部子类的Handler。然后Activity被销毁了。但是内部类还活着。
内部类仍然持有对已销毁的Activity的链接,它不能被垃圾回收。
如果您不打算将处理程序发送到这些方法中,则无需担心。

2
谢谢QuickNick...嗯,当你说“如果你不打算将你的处理程序发送到这些方法中,你就没有什么可担心的”时,我同意你的看法,但我需要摆脱那个警告。 - Rasmus

0

这种情况的一个简单解决方案可能是:

Handler handler=new Handler(new Handler.Callback() {
    @Override
    public boolean handleMessage(Message message) {
        //do your stuff here
        return false;
    } });

0
我遇到了同样的问题,发现这是一个有很多问题但答案很少的话题之一。我的解决方案很简单,希望能对某人有所帮助:
/* BEFORE */
private Handler mHandler= new Handler() {
        @Override public void handleMessage(Message msg) {
        this.doSomething();
    };
};

我们可以创建一个静态的Handler子类,它只是运行一个Runnable。实际的handler实例将通过可访问实例变量的runnable知道要做什么。
/* AFTER */
static class RunnableHandler extends Handler {
    private Runnable mRunnable;
    public RunnableHandler(Runnable runnable) { 
        mRunnable = runnable;
    }
    @Override public void handleMessage(Message msg) {
        mRunnable.run();
    };
}
private RunnableHandler mHandler = new RunnableHandler(new Runnable() {
    @Override public void run() {
        this.doSomething();
    } });

警告已经消失,而功能仍然相同。


1
感谢您回复,Urkidi。但出于某些原因,我觉得这更像是一种 hack。我并不是专家,可能非常错误。 - Rasmus
我喜欢这种方式,尽管我希望有更多知识的人告诉我这是否只是一种权宜之计。 - SatanEnglish
这样做可以隐藏lint警告,但不能解决潜在的问题。Runnable将引用'this',而由于RunnableHandler保留了对runnable的引用,因此您将遇到相同的错误,只是链条更加复杂。 - Sogger

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