这个Handler类应该是静态的,否则可能会出现内存泄漏:IncomingHandler。

314

我正在开发一个带有服务的Android 2.3.3应用程序。我在该服务中包含以下内容以与主活动进行通信:

public class UDPListenerService extends Service
{
    private static final String TAG = "UDPListenerService";
    //private ThreadGroup myThreads = new ThreadGroup("UDPListenerServiceWorker");
    private UDPListenerThread myThread;
    /**
     * Handler to communicate from WorkerThread to service.
     */
    private Handler mServiceHandler;

    // Used to receive messages from the Activity
    final Messenger inMessenger = new Messenger(new IncomingHandler());
    // Use to send message to the Activity
    private Messenger outMessenger;

    class IncomingHandler extends Handler
    {
        @Override
        public void handleMessage(Message msg)
        {
        }
    }

    /**
     * Target we publish for clients to send messages to Incoming Handler.
     */
    final Messenger mMessenger = new Messenger(new IncomingHandler());
    [ ... ]
}

在这里,final Messenger mMessenger = new Messenger(new IncomingHandler());,我收到以下 Lint 警告:

此 Handler 类应为静态的,否则可能会出现泄漏: IncomingHandler

这是什么意思?


27
请查看此博客文章,以获取更多关于该主题的信息! - Adrian Monk
2
由垃圾回收引起的内存泄漏...这足以证明Java是不一致和设计不良的。 - Gojir4
8个回答

400
如果IncomingHandler类不是静态的,它将引用您的Service对象。
同一线程的Handler对象共享一个公共的Looper对象,它们向其发布和读取消息。
由于消息包含目标Handler,只要消息队列中有目标处理程序的消息,处理程序就无法被垃圾回收。如果处理程序不是静态的,则您的ServiceActivity即使在销毁之后也无法被垃圾回收。
这可能会导致内存泄漏,至少在消息保留在队列中的时间内如此。除非您发布长时间延迟的消息,否则这并不是什么大问题。
您可以使IncomingHandler静态,并拥有对服务的WeakReference
static class IncomingHandler extends Handler {
    private final WeakReference<UDPListenerService> mService; 

    IncomingHandler(UDPListenerService service) {
        mService = new WeakReference<UDPListenerService>(service);
    }
    @Override
    public void handleMessage(Message msg)
    {
         UDPListenerService service = mService.get();
         if (service != null) {
              service.handleMessage(msg);
         }
    }
}

请参考Romain Guy的这篇帖子


3
Romain表明,只需要对外部类使用弱引用(WeakReference),不必使用静态嵌套类。我认为我会更喜欢使用弱引用的方法,因为否则由于我需要的所有“静态”变量,整个外部类会发生巨大变化。 - Someone Somewhere
37
如果想使用嵌套类,它必须是静态的。否则,WeakReference 没有任何变化。内部(嵌套但非静态)类始终会对外部类保持强引用。不过,无需使用任何静态变量。 - Tomasz Niedabylski
2
@SomeoneSomewhere mService 是一个弱引用。当所引用的对象被垃圾回收时,get() 方法将返回 null。在这种情况下,服务已经停止。 - Tomasz Niedabylski
2
注意:在将IncomingHandler设置为静态后,我在“final Messenger inMessenger = new Messenger(new IncomingHandler());”这一行上遇到了错误“构造函数MyActivity.IncomingHandler()未定义。”。解决方法是将该行更改为“final Messenger inMessenger = new Messenger(new IncomingHandler(this));”。 - Lance Lefebure
4
@Someone Somewhere 没错,Romain的帖子有误,他忘了声明内部类为static,这就失去了整个意义。除非他使用了某种超酷的编译器,在内部类不使用类变量时自动将其转换为静态类。 - Sogger
显示剩余11条评论

73

正如其他人所提到的,Lint警告是由于潜在的内存泄漏问题。您可以通过在构造Handler时传递一个Handler.Callback来避免Lint警告(即不要创建Handler子类,也没有Handler非静态内部类):

Handler mIncomingHandler = new Handler(new Handler.Callback() {
    @Override
    public boolean handleMessage(Message msg) {
        // todo
        return true;
    }
});

据我理解,这并不能避免潜在的内存泄漏。 Message 对象持有对 mIncomingHandler 对象的引用,后者又持有对 Handler.Callback 对象的引用,而后者又持有对 Service 对象的引用。只要消息队列中有消息,Service 就不会被垃圾回收。但是,除非消息队列中存在长时间延迟的消息,否则这不会成为严重问题。


14
@Braj,我认为避免出现lint警告但仍保留错误的做法根本不是一个好的解决方案。除非如lint警告所述,如果处理程序没有放在您的主循环器上(并且您可以确保在销毁该类时摧毁所有悬而未决的消息),则可减轻引用泄漏的问题。 - Sogger

33
这是使用弱引用和静态处理程序类解决问题的通用示例(如Lint文档中推荐的):
public class MyClass{

  //static inner class doesn't hold an implicit reference to the outer class
  private static class MyHandler extends Handler {
    //Using a weak reference means you won't prevent garbage collection
    private final WeakReference<MyClass> myClassWeakReference; 

    public MyHandler(MyClass myClassInstance) {
      myClassWeakReference = new WeakReference<MyClass>(myClassInstance);
    }

    @Override
    public void handleMessage(Message msg) {
      MyClass myClass = myClassWeakReference.get();
      if (myClass != null) {
        ...do work here...
      }
    }
  }

  /**
   * An example getter to provide it to some external class
   * or just use 'new MyHandler(this)' if you are using it internally.
   * If you only use it internally you might even want it as final member:
   * private final MyHandler mHandler = new MyHandler(this);
   */
  public Handler getHandler() {
    return new MyHandler(this);
  }
}

2
Sogger的例子很棒。然而,Myclass中的最后一个方法应该声明为public Handler getHandler()而不是public void - Jason Porter
这类似于Tomasz Niedabylski的答案。 - CoolMind

27

这种方式对我很有效,通过将处理消息的位置保留在其自己的内部类中,可以使代码保持整洁。

您希望使用的处理程序

Handler mIncomingHandler = new Handler(new IncomingHandlerCallback());

内部类

class IncomingHandlerCallback implements Handler.Callback{

        @Override
        public boolean handleMessage(Message message) {

            // Handle message code

            return true;
        }
}

2
在这里,handleMessage方法最终返回true。您能否解释一下这究竟意味着什么(返回值为true/false)?谢谢。 - JibW
2
我的理解是,返回 true 表示您已处理了消息,因此消息不应传递到其他地方,例如底层处理程序。话虽如此,我找不到任何文档,如果有错误,我会很高兴地接受更正。 - Stuart Campbell
1
Javadoc指出:构造函数将此处理程序与当前线程的Looper关联,并接受一个回调接口,您可以在其中处理消息。如果此线程没有Looper,则此处理程序将无法接收消息,因此会抛出异常。 <-- 我认为当线程没有附加Looper时,新的Handler(new IncomingHandlerCallback())将无法工作,这种情况可能会发生。我不是说在某些情况下这样做是错误的,我只是说它并不总是像您所期望的那样工作。 - user504342
1
@StuartCampbell:你说得对。请查看:https://groups.google.com/forum/#!topic/android-developers/L_xYM0yS6z8。 - Maxwell175

2

在@Sogger的回答帮助下,我创建了一个通用的处理程序:

public class MainThreadHandler<T extends MessageHandler> extends Handler {

    private final WeakReference<T> mInstance;

    public MainThreadHandler(T clazz) {
        // Remove the following line to use the current thread.
        super(Looper.getMainLooper());
        mInstance = new WeakReference<>(clazz);
    }

    @Override
    public void handleMessage(Message msg) {
        T clazz = mInstance.get();
        if (clazz != null) {
            clazz.handleMessage(msg);
        }
    }
}

接口:

public interface MessageHandler {

    void handleMessage(Message msg);

}

我是这样使用它的。但我不太确定这是否是无泄漏的。或许有人可以对此进行评论:
public class MyClass implements MessageHandler {

    private static final int DO_IT_MSG = 123;

    private MainThreadHandler<MyClass> mHandler = new MainThreadHandler<>(this);

    private void start() {
        // Do it in 5 seconds.
        mHandler.sendEmptyMessageDelayed(DO_IT_MSG, 5 * 1000);
    }

    @Override
    public void handleMessage(Message msg) {
        switch (msg.what) {
            case DO_IT_MSG:
                doIt();
                break;
        }
    }

    ...

}

0

我不确定,但是你可以尝试在onDestroy()中将handler初始化为null。


1
同一线程的处理程序对象共享一个公共的Looper对象,它们将消息发布到该对象并从中读取消息。 由于消息包含目标处理程序,只要消息队列中有目标处理程序的消息,处理程序就不会被垃圾回收。 - msysmilu

0
如果您正在使用Kotlin,只需在声明嵌套类时删除inner关键字即可。
在Kotlin中,嵌套类默认为静态的,使用inner来声明它们会使它们变得非静态。
将您的嵌套Handler子类声明更改为:
class myService : Service() {

inner class IncomingHandler : Handler(Looper.getMainLooper()) {
/////
}

}

class myService : Service() {

class IncomingHandler : Handler(Looper.getMainLooper()) {
/////
}

}

0
我有些困惑。 我找到的例子完全避免了静态属性,而是使用了UI线程:
    public class example extends Activity {
        final int HANDLE_FIX_SCREEN = 1000;
        public Handler DBthreadHandler = new Handler(Looper.getMainLooper()){
            @Override
            public void handleMessage(Message msg) {
                int imsg;
                imsg = msg.what;
                if (imsg == HANDLE_FIX_SCREEN) {
                    doSomething();
                }
            }
        };
    }

我喜欢这个解决方案的原因是不需要担心混合使用类和方法变量会出现问题。

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