web-dev-qa-db-ja.com

このHandlerクラスは静的にする必要があります。そうしないとリークが発生する可能性があります。IncomingHandler

私はサービスを備えた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());、私は以下のリント警告を受けます:

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

どういう意味ですか?

278
VansFannel

IncomingHandlerクラスが静的でない場合は、Serviceオブジェクトへの参照があります。

同じスレッドのHandlerオブジェクトはすべて、共通のLooperオブジェクトを共有しています。これらのオブジェクトは、メッセージの投稿と読み取りを行います。

メッセージにターゲットHandlerが含まれているため、メッセージキューにターゲットハンドラを持つメッセージがある限り、ハンドラをガベージコレクションすることはできません。 handlerが静的ではない場合、あなたのServiceまたはActivityは破棄された後でもガベージコレクションできません。

メッセージがキューに残っている限り、少なくともしばらくの間、メモリリークが発生する可能性があります。あなたが長く遅れたメッセージを投稿しない限り、これは大した問題ではありません。

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

詳しくは post を参照してください。

373

他の人が述べているように、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オブジェクトへの参照を保持し、Serviceオブジェクトへの参照を保持するHandler.Callbackオブジェクトを保持します。 Looperメッセージキューにメッセージがある限り、ServiceはGCにはなりません。ただし、メッセージキューに長い遅延メッセージがない限り、これは深刻な問題にはなりません。

61
Michael

弱い参照と静的ハンドラクラスを使用して問題を解決する一般的な例を示します(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);
  }
}
30
Sogger

この方法は私にとってはうまくいきました。独自の内部クラスでメッセージを処理する場所を保つことでコードをきれいに保ちます。

使いたいハンドラ

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

インナークラス

class IncomingHandlerCallback implements Handler.Callback{

        @Override
        public boolean handleMessage(Message message) {

            // Handle message code

            return true;
        }
}
23
Stuart Campbell

@ Soggerの答えを参考にして、私は一般的なHandlerを作成しました。

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

    ...

}
1
Marius

よくわかりませんが、onDestroy()で初期化ハンドラをnullにすることができます。

0
Chaitanya