Cette classe de gestionnaire doit être statique ou des fuites peuvent se produire: IncomingHandler

297

Je développe une application Android 2.3.3 avec un service. J'ai ceci à l'intérieur de ce service pour communiquer avec l'activité principale:

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

Et ici, final Messenger mMessenger = new Messenger(new IncomingHandler());je reçois l'avertissement Lint suivant:

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

Qu'est-ce que ça veut dire?

VansFannel
la source
24
Consultez cet article de blog pour plus d'informations sur ce sujet!
Adrian Monk
1
Fuites de mémoire causées par la récupération de place ... Cela suffit pour démontrer à quel point Java est incohérent et mal conçu
Gojir4

Réponses:

392

Si la IncomingHandlerclasse n'est pas statique, elle aura une référence à votre Serviceobjet.

Handler les objets pour le même thread partagent tous un objet Looper commun, dans lequel ils publient des messages et y lisent.

Comme les messages contiennent la cible Handler, tant qu'il y a des messages avec le gestionnaire cible dans la file d'attente de messages, le gestionnaire ne peut pas être récupéré. Si le gestionnaire n'est pas statique, votre Serviceou Activityne peut pas être récupéré, même après avoir été détruit.

Cela peut entraîner des fuites de mémoire, du moins pendant un certain temps - tant que les messages restent dans la file d'attente. Ce n'est pas vraiment un problème à moins que vous ne publiez des messages retardés.

Vous pouvez rendre IncomingHandlerstatique et avoir un WeakReferenceservice à votre disposition:

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

Voir cet article de Romain Guy pour plus de référence

Tomasz Niedabylski
la source
3
Romain montre que la WeakReference à la classe externe est tout ce qui est nécessaire - une classe imbriquée statique n'est pas nécessaire. Je pense que je préférerai l'approche WeakReference parce que sinon toute la classe externe change radicalement en raison de toutes les variables «statiques» dont j'ai besoin.
Someone Somewhere
35
Si vous souhaitez utiliser une classe imbriquée, elle doit être statique. Sinon, WeakReference ne change rien. La classe interne (imbriquée mais non statique) contient toujours une référence forte à la classe externe. Il n'y a cependant pas besoin de variables statiques.
Tomasz Niedabylski
2
@SomeoneSomewhere mSerivce est une référence faible. get()renverra la valeur null lorsque l'objet référencé a été modifié. Dans ce cas, lorsque le service est mort.
Tomasz Niedabylski
1
Remarque: après avoir rendu le IncomingHandler statique, j'obtenais l'erreur "Le constructeur MyActivity.IncomingHandler () n'est pas défini." sur la ligne "Messenger final inMessenger = nouveau Messenger (nouveau IncomingHandler ());". La solution consiste à remplacer cette ligne par "Messenger final inMessenger = new Messenger (new IncomingHandler (this));".
Lance Lefebure
4
@Someone Somewhere Yeah, le message de Romain est erroné en ce qu'il a manqué de déclarer la classe interne statique qui manque tout le point. Sauf s'il a un compilateur ultra cool qui convertit automatiquement les classes internes en classes statiques quand elles n'utilisent pas de variables de classe.
Sogger
67

Comme d'autres l'ont mentionné, l'avertissement Lint est dû à une fuite de mémoire potentielle. Vous pouvez éviter l'avertissement Lint en passant un Handler.Callbacklors de la construction Handler(c'est-à-dire que vous ne sous Handler- classe pas et qu'il n'y a pas de Handlerclasse interne non statique):

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

Si je comprends bien, cela n'évitera pas la fuite de mémoire potentielle. Messageles objets contiennent une référence à l' mIncomingHandlerobjet qui contient une référence l' Handler.Callbackobjet qui contient une référence à l' Serviceobjet. Tant qu'il y aura des messages dans la Looperfile d'attente de messages, le Servicene sera pas GC. Cependant, ce ne sera pas un problème grave, sauf si vous avez des messages à retard long dans la file d'attente de messages.

Michael
la source
10
@Braj Je ne pense pas qu'éviter l'avertissement de peluches mais garder l'erreur soit une bonne solution. À moins que, comme l'avertissement lint indique si le gestionnaire n'est pas placé sur votre boucleur principal (et vous pouvez vous assurer que tous les messages en attente sont détruits lorsque la classe est détruite), la fuite de la référence est atténuée.
Sogger
33

Voici un exemple générique d'utilisation d'une référence faible et d'une classe de gestionnaire statique pour résoudre le problème (comme recommandé dans la documentation 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);
  }
}
Sogger
la source
2
L'exemple de Sogger est génial. Cependant, la dernière méthode Myclassdoit être déclarée au public Handler getHandler()lieu depublic void
Jason Porter
Elle est similaire à une réponse de Tomasz Niedabylski
CoolMind
24

De cette façon, cela a bien fonctionné pour moi, maintient le code propre en gardant où vous gérez le message dans sa propre classe interne.

Le gestionnaire que vous souhaitez utiliser

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

La classe intérieure

class IncomingHandlerCallback implements Handler.Callback{

        @Override
        public boolean handleMessage(Message message) {

            // Handle message code

            return true;
        }
}
Stuart Campbell
la source
2
Ici, la méthode handleMessage renvoie true à la fin. Pourriez-vous expliquer ce que cela signifie exactement (la valeur de retour true / false)? Merci.
JibW
2
Ma compréhension de renvoyer true est d'indiquer que vous avez traité le message et que le message ne doit donc pas être transmis ailleurs, par exemple le gestionnaire sous-jacent. Cela dit, je n'ai trouvé aucune documentation et je serais heureux de le corriger.
Stuart Campbell
1
Javadoc dit: Le constructeur associe ce gestionnaire au Looper pour le thread actuel et prend une interface de rappel dans laquelle vous pouvez gérer les messages. Si ce thread n'a pas de zone répétée, ce gestionnaire ne pourra pas recevoir de messages, donc une exception est levée. <- Je pense que le nouveau Handler (new IncomingHandlerCallback ()) ne fonctionnera pas quand aucun Looper n'est attaché au thread, et cela PEUT être le cas. Je ne dis pas que c'est mal de le faire dans certains cas, je dis juste que ça ne fonctionne pas toujours comme on pourrait s'y attendre.
user504342
1
@StuartCampbell: Vous avez raison. Voir: groups.google.com/forum/#!topic/android-developers/L_xYM0yS6z8 .
MDTech.us_MAN
2

Avec l'aide de la réponse de @ Sogger, j'ai créé un gestionnaire générique:

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

L'interface:

public interface MessageHandler {

    void handleMessage(Message msg);

}

Je l'utilise comme suit. Mais je ne suis pas sûr à 100% si cela est étanche. Peut-être que quelqu'un pourrait commenter ceci:

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
la source
0

Je ne suis pas sûr mais vous pouvez essayer d'initialiser le gestionnaire à null dans onDestroy ()

Chaitanya
la source
1
Les objets gestionnaire pour le même thread partagent tous un objet Looper commun, dans lequel ils publient des messages et y lisent. Comme les messages contiennent le gestionnaire cible, tant qu'il y a des messages avec le gestionnaire cible dans la file d'attente de messages, le gestionnaire ne peut pas être récupéré.
msysmilu
0

Je suis confus. L'exemple que j'ai trouvé évite complètement la propriété statique et utilise le thread d'interface utilisateur:

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

Ce que j'aime dans cette solution, c'est qu'il n'y a aucun problème à mélanger les variables de classe et de méthode.

user2515235
la source