Esta classe Handler deve ser estática ou podem ocorrer vazamentos: IncomingHandler

297

Estou desenvolvendo um aplicativo Android 2.3.3 com um serviço. Eu tenho isso dentro desse serviço para se comunicar com a atividade principal:

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 aqui, final Messenger mMessenger = new Messenger(new IncomingHandler());recebo o seguinte aviso do Lint:

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

O que isso significa?

VansFannel
fonte
24
Confira esta postagem no blog para obter mais informações sobre este assunto!
Adrian Monk
1
Vazamentos de memória causados pela coleta de lixo ... Isso é suficiente para demonstrar como Java é inconsistente e mal projetado
Gojir4

Respostas:

392

Se a IncomingHandlerclasse não for estática, ela terá uma referência ao seu Serviceobjeto.

Handler objetos para o mesmo encadeamento compartilham um objeto Looper comum, para o qual eles postam mensagens e leem.

Como as mensagens contêm destino Handler, desde que haja mensagens com manipulador de destino na fila de mensagens, o manipulador não poderá ser coletado como lixo. Se manipulador não é estático, a sua Serviceou Activitynão pode ser lixo coletado, mesmo depois de ser destruído.

Isso pode levar a vazamentos de memória, por algum tempo, pelo menos - desde que as mensagens permaneçam na fila. Isso não é muito problemático, a menos que você poste mensagens atrasadas.

Você pode tornar IncomingHandlerestático e ter um WeakReferenceao seu serviço:

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

Veja este post de Romain Guy para referência adicional

Tomasz Niedabylski
fonte
3
Romain mostra que o WeakReference para a classe externa é tudo o que é necessário - uma classe aninhada estática não é necessária. Acho que vou preferir a abordagem WeakReference porque, caso contrário, toda a classe externa mudará drasticamente devido a todas as variáveis ​​'estáticas' de que preciso.
Alguém em algum lugar
35
Se você deseja usar uma classe aninhada, ela deve ser estática. Caso contrário, o WeakReference não muda nada. A classe interna (aninhada, mas não estática) sempre mantém uma forte referência à classe externa. Porém, não há necessidade de variáveis ​​estáticas.
Tomasz Niedabylski
2
@SomeoneSomewhere mSerivce é um WeakReference. get()retornará nulo quando o objeto referenciado foi gc-ed. Nesse caso, quando o serviço estiver morto.
Tomasz Niedabylski
1
Nota: depois de tornar o IncomingHandler estático, recebi o erro "O construtor MyActivity.IncomingHandler () está indefinido." na linha "Messenger final inMessenger = novo Messenger (novo IncomingHandler ());". A solução é alterar essa linha para "Messenger final inMessenger = new Messenger (novo IncomingHandler (this));".
Lance LEFEBURE
4
@Alguém em algum lugar Sim, o post de Romain está errado, pois ele perdeu a declaração da estática da classe interna, que perde todo o sentido. A menos que ele tenha um compilador ultra-legal que converte automaticamente classes internas em classes estáticas quando elas não usam variáveis ​​de classe.
Sogger 07/01
67

Como outros usuários mencionaram, o aviso do Lint é devido ao possível vazamento de memória. Você pode evitar o aviso do Lint passando a Handler.Callbackao construir Handler(ou seja, você não subclasse Handlere não há Handlerclasse interna não estática):

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

Pelo que entendi, isso não evitará o possível vazamento de memória. Messageobjetos mantêm uma referência ao mIncomingHandlerobjeto que mantém uma referência ao Handler.Callbackobjeto que mantém uma referência ao Serviceobjeto. Enquanto houver mensagens na Looperfila de mensagens, o Servicenão será o GC. No entanto, não será um problema sério, a menos que você tenha longas mensagens de atraso na fila de mensagens.

Michael
fonte
10
@ Braj Eu não acho que evitar o aviso de fiapos, mas ainda assim manter o erro é uma boa solução. A menos que, como o aviso de fiapo declare se o manipulador não for colocado no seu looper principal (e você puder garantir que todas as mensagens pendentes nele sejam destruídas quando a classe for destruída), o vazamento da referência será atenuado.
Sogger 07/01
33

Aqui está um exemplo genérico de uso de uma classe fraca de referência e manipulador estático para resolver o problema (conforme recomendado na documentação do 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
fonte
2
O exemplo de Sogger é ótimo. No entanto, o último método em Myclassdeve ser declarado como em public Handler getHandler()vez depublic void
Jason Porter
É semelhante a uma resposta de Tomasz Niedabylski
CoolMind 12/08/2015
24

Dessa maneira, funcionou bem para mim, mantendo o código limpo, mantendo onde você lida com a mensagem em sua própria classe interna.

O manipulador que você deseja usar

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

A classe interna

class IncomingHandlerCallback implements Handler.Callback{

        @Override
        public boolean handleMessage(Message message) {

            // Handle message code

            return true;
        }
}
Stuart Campbell
fonte
2
Aqui, o método handleMessage retorna true no final. Você poderia explicar o que exatamente isso significa (o valor de retorno verdadeiro / falso)? Obrigado.
JibW
2
Meu entendimento de retornar true é indicar que você manipulou a mensagem e, portanto, a mensagem não deve ser transmitida a nenhum outro lugar, por exemplo, o manipulador subjacente. Dito isto, não consegui encontrar nenhuma documentação e seria felizmente corrigido.
Stuart Campbell
1
Javadoc diz: O construtor associa esse manipulador ao Looper para o encadeamento atual e utiliza uma interface de retorno de chamada na qual você pode manipular mensagens. Se esse segmento não tiver um looper, esse manipulador não poderá receber mensagens, portanto, uma exceção será lançada. <- Eu acho que o novo Handler (new IncomingHandlerCallback ()) não funcionará quando não houver Looper conectado ao thread, e esse pode ser o caso. Não estou dizendo que é errado fazê-lo em alguns casos, apenas estou dizendo que nem sempre está funcionando como você poderia esperar.
user504342
1
@StuartCampbell: Você está correto. Consulte: groups.google.com/forum/#!topic/android-developers/L_xYM0yS6z8 .
MDTech.us_MAN
2

Com a ajuda da resposta de @ Sogger, criei um manipulador genérico:

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

A interface:

public interface MessageHandler {

    void handleMessage(Message msg);

}

Estou usando da seguinte maneira. Mas não tenho 100% de certeza se isso é à prova de vazamentos. Talvez alguém possa comentar sobre isso:

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
fonte
0

Não tenho certeza, mas você pode tentar inicializar o manipulador para nulo em onDestroy ()

Chaitanya
fonte
1
Os objetos manipuladores para o mesmo encadeamento compartilham um objeto Looper comum, para o qual eles postam mensagens e leem. Como as mensagens contêm o Manipulador de destino, desde que haja mensagens com o manipulador de destino na fila de mensagens, o manipulador não poderá ser coletado como lixo.
msysmilu
0

Estou confuso. O exemplo que encontrei evita completamente a propriedade estática e usa o thread da interface do usuário:

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

O que eu mais gosto nessa solução é que não há problema em tentar misturar variáveis ​​de classe e método.

user2515235
fonte