Questa classe Handler dovrebbe essere statica o potrebbero verificarsi delle perdite: IncomingHandler


297

Sto sviluppando un'applicazione Android 2.3.3 con un servizio. Ho questo all'interno di quel servizio per comunicare con l'attività 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());
    [ ... ]
}

E qui, final Messenger mMessenger = new Messenger(new IncomingHandler());ricevo il seguente avviso Lint:

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

Cosa significa?


24
Dai un'occhiata a questo post per ulteriori informazioni su questo argomento!
Adrian Monk,

1
Perdite di memoria causate dalla garbage collection ... Questo è abbastanza per dimostrare come Java sia incoerente e mal progettato
Gojir4

Risposte:


392

Se la IncomingHandlerclasse non è statica, avrà un riferimento al tuo Serviceoggetto.

Handler gli oggetti per lo stesso thread condividono tutti un oggetto Looper comune, dal quale postano messaggi e da cui leggono.

Poiché i messaggi contengono target Handler, purché nella coda dei messaggi siano presenti messaggi con il gestore target, il gestore non può essere garbage collection. Se il gestore non è statico, la tua Serviceo Activitynon può essere spazzatura raccolta, anche dopo essere stata distrutta.

Ciò può comportare perdite di memoria, almeno per qualche tempo, purché i messaggi rimangano nella coda. Questo non è un grosso problema a meno che non pubblichi messaggi a lungo ritardati.

Puoi rendere IncomingHandlerstatico e avere un WeakReferenceal tuo servizio:

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

Vedi questo post di Romain Guy per ulteriori riferimenti


3
Romain mostra che WeakReference alla classe esterna è tutto ciò che serve - non è necessaria una classe nidificata statica. Penso che preferirò l'approccio WeakReference perché altrimenti l'intera classe esterna cambia drasticamente a causa di tutte le variabili "statiche" di cui avrei bisogno.
Someone Somewhere,

35
Se si desidera utilizzare una classe nidificata, deve essere statica. Altrimenti, WeakReference non cambia nulla. La classe interna (nidificata ma non statica) contiene sempre un forte riferimento alla classe esterna. Non sono necessarie variabili statiche.
Tomasz Niedabylski,

2
@SomeoneSomewhere mSerivce è un riferimento debole. get()restituirà null quando l'oggetto referenziato è stato gc-ed. In questo caso, quando il servizio è morto.
Tomasz Niedabylski,

1
Nota: dopo aver reso IncomingHandler statico, ho visualizzato l'errore "Il costruttore MyActivity.IncomingHandler () non è definito." sulla linea "final Messenger inMessenger = new Messenger (new IncomingHandler ());". La soluzione è quella di cambiare quella linea in "final Messenger inMessenger = new Messenger (new IncomingHandler (this));".
Lance Lefebure,

4
@ Qualcuno da qualche parte Sì, il post di Romain è sbagliato in quanto ha mancato di dichiarare la classe interna statica che manca l'intero punto. A meno che non abbia un compilatore ultra cool che converte automaticamente le classi interne in classi statiche quando non usano variabili di classe.
Soggetto

67

Come altri hanno già detto, l'avvertimento Lint è a causa della potenziale perdita di memoria. Puoi evitare l'avvertimento Lint passando un Handler.Callbackdurante la costruzione Handler(cioè non esegui una sottoclasse Handlere non esiste Handleruna classe interna non statica):

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

A quanto ho capito, questo non eviterà la potenziale perdita di memoria. Messagegli oggetti contengono un riferimento mIncomingHandlerall'oggetto che contiene un riferimento Handler.Callbackall'oggetto che contiene un riferimento Serviceall'oggetto. Finché ci sono messaggi nella Loopercoda dei messaggi, Servicenon sarà GC. Tuttavia, non sarà un problema serio a meno che tu non abbia messaggi a lungo ritardo nella coda dei messaggi.


10
@Braj Non credo che evitare l'avvertenza di lanugine, ma mantenere l'errore sia una buona soluzione. A meno che, come indicato dall'avvertimento per i filacci, se il gestore non viene inserito nel proprio looper principale (e si può garantire che tutti i messaggi in sospeso su di esso vengano distrutti quando la classe viene distrutta), viene mitigata la perdita del riferimento.
Soggetto

33

Ecco un esempio generico di utilizzo di un riferimento debole e di una classe di gestori statici per risolvere il problema (come raccomandato nella documentazione di 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
L'esempio del soggetto è fantastico. Tuttavia, l'ultimo metodo Myclassdovrebbe essere dichiarato al public Handler getHandler()posto dipublic void
Jason Porter l'

È simile a una risposta di Tomasz Niedabylski
CoolMind

24

In questo modo ha funzionato bene per me, mantiene pulito il codice mantenendo dove gestisci il messaggio nella sua classe interiore.

Il gestore che desideri utilizzare

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

La classe interiore

class IncomingHandlerCallback implements Handler.Callback{

        @Override
        public boolean handleMessage(Message message) {

            // Handle message code

            return true;
        }
}

2
Qui il metodo handleMessage restituisce true alla fine. Potresti spiegare cosa significa esattamente questo (il valore restituito vero / falso)? Grazie.
JibW

2
La mia comprensione del ritorno a true è di indicare che hai gestito il messaggio e quindi il messaggio non deve essere passato altrove, ad esempio il gestore sottostante. Detto questo, non sono riuscito a trovare alcuna documentazione e sarei felicemente corretto.
Stuart Campbell,

1
Javadoc afferma: Il costruttore associa questo gestore al Looper per il thread corrente e utilizza un'interfaccia di callback in cui è possibile gestire i messaggi. Se questo thread non ha un looper, questo gestore non sarà in grado di ricevere messaggi, quindi viene generata un'eccezione. <- Penso che il nuovo gestore (nuovo IncomingHandlerCallback ()) non funzionerà quando non è presente alcun Looper collegato al thread, e che può essere il caso. Non sto dicendo che è sbagliato farlo in alcuni casi, sto solo dicendo che non funziona sempre come ci si potrebbe aspettare.
user504342


2

Con l'aiuto della risposta di @ Sogger, ho creato un gestore generico:

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'interfaccia:

public interface MessageHandler {

    void handleMessage(Message msg);

}

Lo sto usando come segue. Ma non sono sicuro al 100% se questo è a prova di perdite. Forse qualcuno potrebbe commentare questo:

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

Non sono sicuro ma puoi provare a inizializzare il gestore su null in onDestroy ()


1
Gli oggetti gestore per lo stesso thread condividono tutti un oggetto Looper comune, dal quale postano messaggi e leggono. Poiché i messaggi contengono un gestore di destinazione, purché ci siano messaggi con un gestore di destinazione nella coda dei messaggi, il gestore non può essere garbage collection.
msysmilu,

0

Non ho capito bene. L'esempio che ho trovato evita del tutto la proprietà statica e usa il thread dell'interfaccia utente:

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

La cosa che mi piace di questa soluzione è che non c'è nessun problema a provare a mescolare variabili di classe e metodo.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.