Ta klasa Handler powinna być statyczna, w przeciwnym razie mogą wystąpić wycieki: IncomingHandler

297

Tworzę aplikację na Androida 2.3.3 z usługą. Mam to wewnątrz tej usługi, aby komunikować się z główną działalnością:

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

I tutaj final Messenger mMessenger = new Messenger(new IncomingHandler());pojawia się następujące ostrzeżenie Lint:

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

Co to znaczy?

VansFannel
źródło
24
Sprawdź ten post na blogu, aby uzyskać więcej informacji na ten temat!
Adrian Monk
1
Wycieki pamięci spowodowane przez wyrzucanie elementów bezużytecznych ... To wystarczy, aby pokazać, w jaki sposób Java jest niespójna i źle zaprojektowana
Gojir4,

Odpowiedzi:

391

Jeśli IncomingHandlerklasa nie jest statyczna, będzie miała odniesienie do twojego Serviceobiektu.

Handler wszystkie obiekty tego samego wątku współużytkują wspólny obiekt Looper, do którego wysyłają wiadomości i czytają.

Ponieważ wiadomości zawierają cel Handler, dopóki w kolejce komunikatów znajdują się komunikaty z funkcją obsługi, nie można wyrzucać pamięci. Jeżeli przewodnik nie jest statyczna, swoje Servicelub Activitynie mogą być zbierane śmieci, nawet po zniszczeniu.

Może to prowadzić do wycieków pamięci, przynajmniej przez jakiś czas - o ile wiadomości pozostają w kolejce. Nie stanowi to większego problemu, chyba że opublikujesz wiadomości z dużym opóźnieniem.

Możesz zrobić IncomingHandlerstatyczne i mieć WeakReferencedo swojej usługi:

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

Zobacz ten post Romain Guy w celu uzyskania dalszych odniesień

Tomasz Niedabylski
źródło
3
Romain pokazuje, że WeakReference do klasy zewnętrznej jest wszystkim, czego potrzeba - statyczna klasa zagnieżdżona nie jest konieczna. Myślę, że wolę podejście WeakReference, ponieważ w przeciwnym razie cała klasa zewnętrzna zmieni się drastycznie ze względu na wszystkie zmienne „statyczne”, których potrzebuję.
Ktoś gdzieś
35
Jeśli chcesz użyć zagnieżdżonej klasy, musi ona być statyczna. W przeciwnym razie WeakReference niczego nie zmieni. Klasa wewnętrzna (zagnieżdżona, ale nie statyczna) zawsze zawiera silne odniesienie do klasy zewnętrznej. Nie potrzeba jednak żadnych zmiennych statycznych.
Tomasz Niedabylski,
2
@SomeoneSomewhere mSerivce to WeakReference. get()zwróci null, gdy obiekt odniesienia był gc-ed. W takim przypadku, gdy usługa nie działa.
Tomasz Niedabylski,
1
Uwaga: po ustawieniu statycznego obiektu IncomingHandler pojawił się błąd „Konstruktor MyActivity.IncomingHandler () jest niezdefiniowany”. w wierszu „final Messenger inMessenger = new Messenger (new IncomingHandler ());”. Rozwiązaniem jest zmiana tej linii na „final Messenger inMessenger = new Messenger (new IncomingHandler (this));”.
Lance Lefebure
4
@ Someone Somewhere Somah, post Romaina jest błędny, ponieważ nie udało mu się zadeklarować wewnętrznej klasy statycznej, która pomija cały punkt. Chyba że ma jakiś super fajny kompilator, który automatycznie konwertuje klasy wewnętrzne na klasy statyczne, gdy nie używają zmiennych klas.
Sogger,
67

Jak wspomnieli inni, ostrzeżenie Lint jest spowodowane potencjalnym wyciekiem pamięci. Możesz uniknąć ostrzeżenia Lint, przekazując a Handler.Callbackpodczas konstruowania Handler(tzn. Nie podklasujesz Handleri nie ma Handlerniestatycznej klasy wewnętrznej):

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

Jak rozumiem, nie pozwoli to uniknąć potencjalnego wycieku pamięci. Messageobiekty zawierają odniesienie do mIncomingHandlerobiektu, który zawiera odniesienie do Handler.Callbackobiektu, który zawiera odniesienie do Serviceobiektu. Dopóki w Looperkolejce komunikatów znajdują się komunikaty, Servicenie będzie to GC. Nie będzie to jednak poważny problem, chyba że w kolejce wiadomości znajdują się długie wiadomości opóźniające.

Michał
źródło
10
@Braj Nie sądzę, że unikanie ostrzeżenia o kłaczkach, ale nadal utrzymywanie błędu, jest w ogóle dobrym rozwiązaniem. Chyba że, jak ostrzeżenie o strzępach mówi, że program obsługi nie jest umieszczony na głównym looperrze (i możesz upewnić się, że wszystkie komunikaty na nim są niszczone, gdy klasa zostanie zniszczona), wówczas wyciek odniesienia zostanie złagodzony.
Sogger,
33

Oto ogólny przykład użycia słabej klasy referencyjnej i statycznej klasy procedury obsługi do rozwiązania problemu (zgodnie z zaleceniami w dokumentacji 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
źródło
2
Przykład Soggera jest świetny. Jednak ostatnia metoda Myclasspowinna być uznana jako public Handler getHandler()zamiastpublic void
Jason Porter
Jest podobny do odpowiedzi Tomasza Niedabylskiego
CoolMind
24

Ten sposób działał dla mnie dobrze, utrzymuje kod w czystości, utrzymując miejsce, w którym obsługujesz komunikat w jego własnej klasie wewnętrznej.

Przewodnik, którego chcesz użyć

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

Klasa wewnętrzna

class IncomingHandlerCallback implements Handler.Callback{

        @Override
        public boolean handleMessage(Message message) {

            // Handle message code

            return true;
        }
}
Stuart Campbell
źródło
2
Tutaj metoda handleMessage zwraca na końcu wartość true. Czy możesz wyjaśnić, co to dokładnie oznacza (zwracana wartość true / false)? Dzięki.
JibW
2
Rozumiem, że zwracanie „prawda” oznacza, że ​​wiadomość została przetworzona, a zatem nie należy jej przekazywać nigdzie indziej, np. Do podstawowego programu obsługi. To powiedziawszy nie mogłem znaleźć żadnej dokumentacji i chętnie zostanie poprawiony.
Stuart Campbell,
1
Javadoc mówi: Constructor kojarzy ten moduł obsługi z Looper dla bieżącego wątku i przyjmuje interfejs zwrotny, w którym można obsługiwać wiadomości. Jeśli ten wątek nie ma pętli, ten moduł obsługi nie będzie mógł odbierać wiadomości, więc zostanie zgłoszony wyjątek. <- Myślę, że nowy moduł obsługi (nowy moduł IncomingHandlerCallback ()) nie będzie działał, gdy do wątku nie zostanie podłączony Looper, i może tak być. Nie twierdzę, że jest to złe w niektórych przypadkach, po prostu mówię, że nie zawsze działa, jak można się spodziewać.
user504342,
1
@StuartCampbell: Masz rację. Zobacz: groups.google.com/forum/#!topic/android-developers/L_xYM0yS6z8 .
MDTech.us_MAN
2

Za pomocą odpowiedzi @ Sogger stworzyłem ogólny program obsługi:

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

Interfejs:

public interface MessageHandler {

    void handleMessage(Message msg);

}

Używam tego w następujący sposób. Ale nie jestem w 100% pewien, czy to jest szczelne. Może ktoś mógłby to skomentować:

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
źródło
0

Nie jestem pewien, ale możesz spróbować zainicjalizować moduł obsługi, aby null w onDestroy ()

Chaitanya
źródło
1
Obiekty programu obsługi dla tego samego wątku współużytkują wspólny obiekt Looper, do którego wysyłają wiadomości i czytają. Ponieważ komunikaty zawierają docelowy moduł obsługi, tak długo, jak w kolejce komunikatów znajdują się komunikaty z docelowym modułem obsługi, nie można zbierać elementów pamięci.
msysmilu
0

Jestem zmieszany. Przykład, który znalazłem, całkowicie unika właściwości statycznej i wykorzystuje wątek interfejsu użytkownika:

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

W tym rozwiązaniu podoba mi się to, że nie ma problemu z mieszaniem zmiennych klas i metod.

użytkownik2515235
źródło