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


297

サービス付きの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警告が表示されます。

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

どういう意味ですか?


24
この件に関する詳細については、このブログ投稿をご覧ください。
エイドリアンモンク

1
ガベージコレクションによるメモリリーク...これは、Javaの一貫性がなく、設計が
不適切で

回答:


392

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

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

メッセージにはtargetが含ま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);
         }
    }
}

詳細については、Romain Guyによるこの投稿を参照してください


3
Romainは、外部クラスへのWeakReferenceが必要なすべてであることを示しています-静的なネストされたクラスは必要ありません。私が必要とするすべての「静的」変数が原因で外部クラス全体が劇的に変化するため、私はWeakReferenceアプローチを好むと思います。
誰かどこか

35
ネストされたクラスを使用する場合は、静的である必要があります。それ以外の場合、WeakReferenceは何も変更しません。内部(ネストされているが静的ではない)クラスは常に外部クラスへの強い参照を保持します。ただし、静的変数は必要ありません。
Tomasz Niedabylski 2012

2
@SomeoneSomewhere mSerivceはWeakReferenceです。get()参照されたオブジェクトがgc-edされた場合はnullを返します。この場合、サービスが停止しています。
Tomasz Niedabylski 2012

1
注:IncomingHandlerを静的にした後、「コンストラクターMyActivity.IncomingHandler()が未定義です」というエラーが発生しました。「final Messenger inMessenger = new Messenger(new IncomingHandler());」の行にあります。解決策は、その行を「最終的なメッセンジャーinMessenger = new Messenger(new IncomingHandler(this));」に変更することです。
Lance Lefebure、2013

4
@Someone Somewhereええ、Romainの投稿は、内部クラスstaticを宣言しなかったという点で間違っています。クラス変数を使用しないときに内部クラスを静的クラスに自動的に変換する超クールなコンパイラーがない限り。
Sogger、2015年

67

他の人がLint警告を述べたように、潜在的なメモリリークが原因です。Lint警告を回避するにはHandler.Callback、構築時にaを渡しHandlerます(つまり、サブクラスを使用せずHandlerHandler静的でない内部クラスはありません)。

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

私が理解しているように、これは潜在的なメモリリークを回避しません。Messageオブジェクトは、mIncomingHandlerオブジェクトへの参照Handler.Callbackを保持するServiceオブジェクトへの参照を保持するオブジェクトへの参照を保持します。Looperメッセージキューにメッセージがある限り、ServiceGCにはなりません。ただし、メッセージキューに長い遅延メッセージがない限り、深刻な問題にはなりません。


10
@ブラジ私は糸くずの警告を回避しないと思いますが、それでもエラーを維持することはまったく良い解決策です。ハンドラーがメインルーパーに配置されていない場合(およびクラスが破棄されるときにハンドラーで保留中のすべてのメッセージが確実に破棄される場合)、lintの警告がそうでない限り、参照のリークは軽減されます。
Sogger、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
Soggerの例は素晴らしいです。しかし中に、最後の方法Myclassとして宣言されなければならないpublic Handler getHandler()代わりにpublic void
ジェイソン・ポーター

これは、トマシュ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を返すことについての私の理解は、メッセージを処理したことを示すことであり、したがって、メッセージは他のどこにも渡すべきではありません(例:基礎となるハンドラー)。とはいえ、ドキュメンテーションが見つからず、喜んで訂正されます。
スチュアートキャンベル

1
Javadocによると:コンストラクターは、このハンドラーを現在のスレッドのルーパーに関連付け、メッセージを処理できるコールバックインターフェースを取ります。このスレッドにルーパーがない場合、このハンドラーはメッセージを受信できないため、例外がスローされます。<-スレッドに接続されたルーパーがない場合、新しいHandler(new IncomingHandlerCallback())は機能しないと考えられます。私はそうするのが間違っていると言っているのではなく、あなたが期待するように常に機能しているわけではないというだけです。
user504342 2013年

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

よくわかりません。私が見つけた例では、静的プロパティを完全に回避し、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();
                }
            }
        };
    }

このソリューションで気に入っているのは、クラス変数とメソッド変数を混在させても問題がないことです。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.