该Handler类应该是静态的,否则可能会发生泄漏:IncomingHandler


297

我正在开发带有服务的Android 2.3.3应用程序。我在服务内部拥有此功能,可与Main活动进行通信:

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警告:

This Handler class should be static or leaks might occur: IncomingHandler

这是什么意思?


24
请查看此博客文章,以获取有关此主题的更多信息!
Adrian Monk 2013年

1
由垃圾回收引起的内存泄漏...这足以说明Java如何不一致和设计
不当

Answers:


391

如果IncomingHandlerclass不是静态的,它将引用您的Service对象。

Handler 同一线程的所有对象都共享一个通用的Looper对象,它们将消息发布到其中并从中读取。

由于消息包含target Handler,因此只要消息队列中存在带有目标处理程序的消息,就无法对处理程序进行垃圾回收。如果处理程序不是静态的,则即使被销毁,您的ServiceActivity也无法被垃圾回收。

只要消息保留在队列中,这至少可能会导致内存泄漏至少一段时间。除非您发布长时间延迟的邮件,否则这并不是什么大问题。

您可以使IncomingHandlerstatic并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即可-不需要静态嵌套类。我想我会首选WeakReference方法,因为否则,由于我需要的所有“静态”变量,整个外部类都会发生巨大变化。
某处某人2012年

35
如果要使用嵌套类,则它必须是静态的。否则,WeakReference不会更改任何内容。内部(嵌套但不是静态的)类始终强烈引用外部类。但是,不需要任何静态变量。
Tomasz Niedabylski 2012年

2
@SomeoneSomewhere mSerivce是弱引用。get()gc-ed引用的对象时,将返回null。在这种情况下,服务中断了。
Tomasz Niedabylski 2012年

1
注意:将IncomingHandler设为静态后,出现错误“构造函数MyActivity.IncomingHandler()未定义”。在“最终Messenger inMessenger = new Messenger(new IncomingHandler());”行中。解决方案是将该行更改为“最终Messenger inMessenger = new Messenger(new IncomingHandler(this));”。
Lance Lefebure

4
@Someone在某处,是的,Romain的帖子是错误的,因为他错过了声明内部类的静态性,从而错过了整个要点。除非他有一些超酷的编译器,当它们不使用类变量时,它们会自动将内部类转换为静态类。
索格2015年

67

正如其他人提到的那样,Lint警告是由于潜在的内存泄漏。您可以通过Handler.Callback在构造时传递a来避免Lint警告Handler(即,您不是子类,Handler并且没有Handler非静态内部类):

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

据我了解,这将无法避免潜在的内存泄漏。 Message对象包含对对象的引用,该mIncomingHandler对象包含对对象的引用Handler.Callback对象包含对对象的引用Service。只要Looper消息队列中有消息,Service它将不会是GC。但是,除非您在消息队列中有很长的延迟消息,否则它将不是一个严重的问题。


10
@Braj我认为没有避免掉毛警告,但仍然保持错误是一个很好的解决方案。除非,除非出现绒毛警告,否则如果处理程序没有放在主循环程序上(并且可以确保在销毁类时可以销毁所有待处理的消息),则可以减轻对引用的泄漏。
索格2015年

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
索格的例子很棒。但是,Myclass应声明为中的最后一个方法,public Handler getHandler()而不是public void
Jason Porter

这类似于Tomasz Niedabylski的回答
CoolMind

24

这种方式对我来说效果很好,通过在内部类中处理消息的位置来保持代码干净。

您要使用的处理程序

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 2013年

2
我对返回true的理解是表明您已经处理了消息,因此不应将消息传递到其他任何地方,例如基础处理程序。那就是说我找不到任何文档,很高兴得到纠正。
斯图尔特·坎贝尔2013年

1
Javadoc说:构造函数将此处理程序与当前线程的Looper关联,并采用一个回调接口,您可以在其中处理消息。如果此线程没有循环程序,则此处理程序将无法接收消息,因此将引发异常。<-我认为当没有Looper附加到线程时,新的Handler(new IncomingHandlerCallback())将无法工作,情况可能是这样。我并不是说在某些情况下这样做是错误的,我只是说它并不总是如您所愿。
user504342

1
@StuartCampbell:你是正确的。请参阅:groups.google.com/forum/# ! topic/android-developers/L_xYM0yS6z8
MDTech.us_MAN 2014年

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

}

我使用它如下。但是我不确定100%是否安全。也许有人可以对此发表评论:

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()中将处理程序初始化为null


1
同一线程的处理程序对象都共享一个公共Looper对象,它们将消息发布到其中并从中读取。由于消息包含目标处理程序,因此只要消息队列中存在带有目标处理程序的消息,就不能对处理程序进行垃圾回收。
msysmilu 2015年

0

我糊涂了。我发现的示例完全避免使用static属性,而是使用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();
                }
            }
        };
    }

我喜欢这个解决方案的地方是,尝试混合类和方法变量没有问题。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.