Diese Handler-Klasse sollte statisch sein, da sonst Lecks auftreten können: IncomingHandler

297

Ich entwickle eine Android 2.3.3-Anwendung mit einem Dienst. Ich habe dies in diesem Dienst, um mit der Hauptaktivität zu kommunizieren:

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());
    [ ... ]
}

Und hier final Messenger mMessenger = new Messenger(new IncomingHandler());bekomme ich folgende Lint-Warnung:

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

Was bedeutet das?

VansFannel
quelle
24
In diesem Blogbeitrag finden Sie viele weitere Informationen zu diesem Thema!
Adrian Monk
1
Speicherverluste durch Speicherbereinigung ... Dies reicht aus, um zu demonstrieren, wie inkonsistent und schlecht Java gestaltet ist
Gojir4

Antworten:

392

Wenn IncomingHandler Klasse nicht statisch ist, enthält sie einen Verweis auf Ihr ServiceObjekt.

Handler Objekte für denselben Thread haben alle ein gemeinsames Looper-Objekt, an das sie Nachrichten senden und von denen sie lesen.

Da Nachrichten ein Ziel enthalten Handler, kann der Handler nicht mit Müll gesammelt werden, solange sich Nachrichten mit dem Zielhandler in der Nachrichtenwarteschlange befinden. Wenn der Handler nicht statisch ist, ist Ihr ServiceoderActivity lässt sich nicht Müll gesammelt, auch nach zerstört.

Dies kann zumindest für einige Zeit zu Speicherverlusten führen - solange die Nachrichten in der Warteschlange bleiben. Dies ist kein großes Problem, es sei denn, Sie veröffentlichen lange verzögerte Nachrichten.

Sie können IncomingHandlerstatisch machen und einen WeakReferencezu Ihrem Dienst haben:

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

Siehe diesen Beitrag von Romain Guy für weitere Referenz

Tomasz Niedabylski
quelle
3
Romain zeigt, dass die WeakReference auf die äußere Klasse alles ist, was benötigt wird - eine statisch verschachtelte Klasse ist nicht erforderlich. Ich denke, ich werde den WeakReference-Ansatz bevorzugen, da sich sonst die gesamte äußere Klasse aufgrund aller benötigten 'statischen' Variablen drastisch ändert.
Jemand irgendwo
35
Wenn Sie eine verschachtelte Klasse verwenden möchten, muss sie statisch sein. Ansonsten ändert WeakReference nichts. Die innere (verschachtelte, aber nicht statische) Klasse enthält immer einen starken Bezug zur äußeren Klasse. Es sind jedoch keine statischen Variablen erforderlich.
Tomasz Niedabylski
2
@SomeoneSomewhere mSerivce ist eine schwache Referenz. get()gibt null zurück, wenn das referenzierte Objekt gc-ed wurde. In diesem Fall, wenn der Dienst unterbrochen ist.
Tomasz Niedabylski
1
Hinweis: Nachdem der IncomingHandler statisch gemacht wurde, wurde die Fehlermeldung "Der Konstruktor MyActivity.IncomingHandler () ist undefiniert" angezeigt. in der Zeile "final Messenger inMessenger = neuer Messenger (neuer IncomingHandler ());". Die Lösung besteht darin, diese Zeile in "final Messenger inMessenger = neuer Messenger (neuer IncomingHandler (this));" zu ändern.
Lance Lefebure
4
@Someone Somewhere Yeah, Romains Beitrag ist insofern falsch, als er es versäumt hat, die innere Klasse als statisch zu deklarieren, was den ganzen Punkt verfehlt. Es sei denn, er hat einen ultra coolen Compiler, der innere Klassen automatisch in statische Klassen konvertiert, wenn sie keine Klassenvariablen verwenden.
Sogger
67

Wie andere bereits erwähnt haben, ist die Lint-Warnung auf den möglichen Speicherverlust zurückzuführen. Sie können die Lint-Warnung vermeiden, indem Sie Handler.Callbackbeim Konstruieren ein übergeben Handler(dh Sie haben keine Unterklasse Handlerund es gibt keine Handlernicht statische innere Klasse):

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

So wie ich es verstehe, wird dies den möglichen Speicherverlust nicht vermeiden. MessageObjekte enthalten eine Referenz auf das mIncomingHandlerObjekt, das eine Referenz enthält. Das Handler.CallbackObjekt enthält eine Referenz auf das ServiceObjekt. Solange sich Nachrichten in der LooperNachrichtenwarteschlange befinden, Serviceist dies kein GC. Es wird jedoch kein ernstes Problem sein, es sei denn, Sie haben Nachrichten mit langer Verzögerung in der Nachrichtenwarteschlange.

Michael
quelle
10
@Braj Ich denke nicht, dass es eine gute Lösung ist, die Flusenwarnung zu vermeiden, aber den Fehler beizubehalten. Sofern in der Flusenwarnung nicht angegeben ist, dass der Handler nicht auf Ihrem Hauptschleifer installiert ist (und Sie sicherstellen können, dass alle darauf anstehenden Nachrichten zerstört werden, wenn die Klasse zerstört wird), wird das Durchsickern der Referenz verringert.
Sogger
33

Hier ist ein allgemeines Beispiel für die Verwendung einer schwachen Referenz- und statischen Handlerklasse zur Behebung des Problems (wie in der Lint-Dokumentation empfohlen):

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);
  }
}
Sogger
quelle
2
Sogger Beispiel ist großartig. Die letzte Methode in Myclasssollte jedoch als public Handler getHandler()anstelle vonpublic void
Jason Porter
Es ist ähnlich wie eine Antwort von Tomasz Niedabylski
CoolMind
24

Diese Methode hat bei mir gut funktioniert und den Code sauber gehalten, indem Sie die Stelle, an der Sie die Nachricht verarbeiten, in einer eigenen inneren Klasse aufbewahren.

Der Handler, den Sie verwenden möchten

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

Die innere Klasse

class IncomingHandlerCallback implements Handler.Callback{

        @Override
        public boolean handleMessage(Message message) {

            // Handle message code

            return true;
        }
}
Stuart Campbell
quelle
2
Hier gibt die handleMessage-Methode am Ende true zurück. Könnten Sie bitte erklären, was genau dies bedeutet (der Rückgabewert true / false)? Vielen Dank.
JibW
2
Mein Verständnis der Rückgabe von true besteht darin, anzuzeigen, dass Sie die Nachricht verarbeitet haben und die Nachricht daher nirgendwo anders weitergegeben werden sollte, z. B. im zugrunde liegenden Handler. Das heißt, ich konnte keine Dokumentation finden und würde gerne korrigiert werden.
Stuart Campbell
1
Javadoc sagt: Constructor ordnet diesen Handler dem Looper für den aktuellen Thread zu und verwendet eine Rückrufschnittstelle, über die Sie Nachrichten verarbeiten können. Wenn dieser Thread keinen Looper hat, kann dieser Handler keine Nachrichten empfangen, sodass eine Ausnahme ausgelöst wird. <- Ich denke, der neue Handler (neuer IncomingHandlerCallback ()) funktioniert nicht, wenn kein Looper an den Thread angeschlossen ist, und das kann der Fall sein. Ich sage nicht, dass es in einigen Fällen falsch ist, dies zu tun. Ich sage nur, dass es nicht immer so funktioniert, wie Sie es vielleicht erwarten.
user504342
1
@ StuartCampbell: Du bist richtig. Siehe: groups.google.com/forum/#!topic/android-developers/L_xYM0yS6z8 .
MDTech.us_MAN
2

Mit Hilfe der Antwort von @ Sogger habe ich einen generischen Handler erstellt:

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

Die Schnittstelle:

public interface MessageHandler {

    void handleMessage(Message msg);

}

Ich benutze es wie folgt. Ich bin mir aber nicht 100% sicher, ob dies auslaufsicher ist. Vielleicht könnte jemand dies kommentieren:

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

    ...

}
Marius
quelle
0

Ich bin nicht sicher, aber Sie können versuchen, den Handler in onDestroy () auf null zu setzen.

Chaitanya
quelle
1
Handlerobjekte für denselben Thread haben alle ein gemeinsames Looper-Objekt, an das sie Nachrichten senden und von denen sie lesen. Da Nachrichten den Zielhandler enthalten, kann der Handler nicht durch Müllbereinigung erfasst werden, solange sich Nachrichten mit dem Zielhandler in der Nachrichtenwarteschlange befinden.
Msysmilu
0

Ich bin verwirrt. Das Beispiel, das ich gefunden habe, vermeidet die statische Eigenschaft vollständig und verwendet den UI-Thread:

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

Was mir an dieser Lösung gefällt, ist, dass es kein Problem gibt, Klassen- und Methodenvariablen zu mischen.

user2515235
quelle