web-dev-qa-db-fra.com

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

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 suivant:

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

Qu'est-ce que ça veut dire?

284
VansFannel

Si la classe IncomingHandler n'est pas statique, elle fera référence à votre objet Service.

Les objets Handler du même thread partagent tous un objet Looper commun, sur lequel ils publient des messages et les lisent.

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

Cela peut entraîner des fuites de mémoire, du moins pendant un certain temps, à condition que les messages restent dans la file d'attente. Ce n’est pas un problème si vous ne postez pas de longs messages différés.

Vous pouvez rendre IncomingHandler statique et avoir un WeakReference à votre service:

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

See this post par Romain Guy pour plus de références

378
Tomasz Niedabylski

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

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

Si j'ai bien compris, cela n'évitera pas la fuite de mémoire potentielle. Les objets Message contiennent une référence à l'objet mIncomingHandler qui contient une référence à l'objet Handler.Callback qui contient une référence à l'objet Service. Tant qu'il y aura des messages dans la file d'attente de messages Looper, le Service ne sera pas GC. Toutefois, ce ne sera pas un problème grave, sauf si vous avez des messages avec un retard important dans la file d'attente.

65
Michael

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 de 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);
  }
}
32
Sogger

Cette méthode a bien fonctionné pour moi, maintient le code propre en conservant le lieu 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;
        }
}
24
Stuart Campbell

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% s'il s'agit d'une fuite sans danger. 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;
        }
    }

    ...

}
2
Marius

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

0
Chaitanya