Bu Handler sınıfı statik olmalıdır veya sızıntı olabilir: IncomingHandler


297

Bir hizmet ile bir Android 2.3.3 uygulaması geliştiriyorum. Bu hizmet içinde Ana faaliyet ile iletişim kurmak için var:

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

Ve burada, final Messenger mMessenger = new Messenger(new IncomingHandler());aşağıdaki Lint uyarısını alıyorum:

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

Bu ne demek?


24
Bu konuda daha fazla bilgi için bu blog yayınına göz atın !
Adrian Monk

1
Çöp toplamanın neden olduğu bellek sızıntıları ... Bu, Java'nın nasıl tutarsız ve kötü bir şekilde tasarlandığını göstermek için yeterlidir
Gojir4

Yanıtlar:


392

Eğer IncomingHandlersınıf statik değildir, bu bir başvuru olacaktır Servicenesne.

Handler aynı iş parçacığındaki nesnelerin tümü, ileti gönderdikleri ve okudukları ortak bir Looper nesnesini paylaşır.

İletiler hedef içerdiğinden Handler, ileti kuyruğunda hedef işleyiciye sahip iletiler olduğu sürece, işleyici çöp toplanamaz. İşleyici statik değilse, imha edildikten sonra bile çöpleriniz toplanamaz Serviceveya Activitytoplanamaz.

Bu, mesajlar kuyrukta kaldığı sürece en azından bir süreliğine bellek sızıntılarına neden olabilir. Uzun gecikmeli mesajlar göndermediğiniz sürece bu çok önemli değildir.

IncomingHandlerStatik yapabilir ve WeakReferencehizmetinize bir sahip olabilirsiniz :

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

Bu bak yazıyı daha fazla referans için Romain Guy tarafından


3
Romain, dış sınıfa WeakReference öğesinin gerekli olduğunu gösterir - statik iç içe bir sınıf gerekli değildir. WeakReference yaklaşımını tercih edeceğimi düşünüyorum çünkü aksi takdirde tüm dış sınıf ihtiyacım olan tüm 'statik' değişkenler nedeniyle büyük ölçüde değişir.
Bir Yerde

35
Yuvalanmış bir sınıf kullanmak istiyorsanız, statik olması gerekir. Aksi takdirde, WeakReference hiçbir şeyi değiştirmez. İç (iç içe geçmiş ancak statik olmayan) sınıf, her zaman dış sınıfa güçlü bir başvuruda bulunur. Ancak statik değişkenlere gerek yoktur.
Tomasz Niedabylski

2
@SomeoneSomewhere mSerivce bir Zayıf Yönergedir. get()başvurulan nesne gc-ed olduğunda null değerini döndürür. Bu durumda, hizmet öldüğünde.
Tomasz Niedabylski

1
Not: IncomingHandler statik yaptıktan sonra "Yapıcı MyActivity.IncomingHandler () tanımsız." Hatası alıyordum. "son Messenger inMessenger = yeni Messenger (yeni IncomingHandler ());" satırında. Çözüm bu satırı "son Messenger inMessenger = yeni Messenger (yeni IncomingHandler (bu));" olarak değiştirmektir.
Lance Lefebure

4
@Birisi Bir Yerde Evet, Romain'in gönderisi yanlıştır çünkü iç sınıfı statik olarak tüm noktayı özleyen beyan etmeyi kaçırır. Sınıf değişkenlerini kullanmadığı zaman iç sınıfları otomatik olarak statik sınıflara dönüştüren ultra serin bir derleyici olmadığı sürece.
Sogger

67

Diğerlerinin de belirttiği gibi Lint uyarısı, potansiyel bellek sızıntısından kaynaklanmaktadır. Yaparken bir Handler.Callbackzaman geçirerek Lint uyarısından kaçınabilirsiniz Handler(yani alt sınıf yapmazsınız Handlerve Handlerstatik olmayan bir iç sınıf yoktur):

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

Anladığım kadarıyla, bu potansiyel bellek sızıntısını önlemeyecektir. Messagenesneler, bir mIncomingHandlernesnenin başvurusunu Handler.Callbacktutan nesneye bir başvuru içerir Service. Looperİleti kuyruğunda iletiler olduğu sürece , ServiceGC olmaz. Ancak, ileti kuyruğunda uzun gecikme iletileri yoksa ciddi bir sorun olmaz.


10
@Braj Tüysüz uyarıdan kaçınmayı düşünmüyorum ama yine de hatayı tutmak hiç de iyi bir çözüm değil. Tüy bırakma uyarısı, işleyicinin ana ilmek yapıcıya konulmadığını (ve sınıf yok edildiğinde bekleyen tüm mesajların yok edilmesini sağlayabileceğinizi) belirtmediği sürece, referansı sızıntı hafifletir.
Sogger

33

Sorunu çözmek için zayıf bir referans ve statik işleyici sınıfı kullanmanın genel bir örneği (Lint belgelerinde önerildiği gibi):

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
Sogger örneği harika. Ancak, son yöntem yerine Myclassolarak ilan edilmelidirpublic Handler getHandler()public void
Jason Porter

Tomasz Niedabylski
CoolMind

24

Bu şekilde benim için iyi çalıştı, mesajı kendi iç sınıfında ele alarak kodu temiz tutar.

Kullanmak istediğiniz işleyici

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

İç sınıf

class IncomingHandlerCallback implements Handler.Callback{

        @Override
        public boolean handleMessage(Message message) {

            // Handle message code

            return true;
        }
}

2
Burada handleMessage yöntemi sonunda true değerini döndürür. Bunun tam olarak ne anlama geldiğini açıklayabilir misiniz (true / false dönüş değeri)? Teşekkürler.
JibW

2
Doğruyu döndürme anlayışım, iletiyi ele aldığınızı belirtmek ve bu nedenle iletinin, başka bir yere, örneğin altta yatan işleyiciye iletilmemesi gerektiğini belirtmektir. Bu, herhangi bir belge bulamadığımı ve mutlu bir şekilde düzeltileceğini söyledi.
Stuart Campbell

1
Javadoc diyor ki: Yapıcı bu işleyiciyi geçerli iş parçacığı için Lüper ile ilişkilendirir ve iletileri işleyebileceğiniz bir geri arama arabirimi alır. Bu iş parçacığında bir ilmek yapıcı yoksa, bu işleyici ileti alamaz, bu nedenle bir istisna atılır. <- Bence iş parçacığı bağlı bir ilmek yapıcı yok yeni işleyici (yeni IncomingHandlerCallback ()) çalışmaz ve durum böyle olabilir. Bazı durumlarda bunun yanlış olduğunu söylemiyorum, sadece her zaman beklediğiniz gibi çalışmadığını söylüyorum.
user504342


2

@ Sogger'ın cevabının yardımıyla, genel bir Handler oluşturdum:

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

Arayüz:

public interface MessageHandler {

    void handleMessage(Message msg);

}

Aşağıdaki gibi kullanıyorum. Ama bunun sızdırmaz olup olmadığından% 100 emin değilim. Belki birisi bu konuda yorum yapabilir:

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

Emin değilim ama onDestroy () null için intialising işleyici deneyebilirsiniz


1
Aynı iş parçacığının işleyici nesnelerinin tümü, ileti gönderdikleri ve okudukları ortak bir Looper nesnesini paylaşır. İletiler hedef İşleyici içerdiğinden, ileti kuyruğunda hedef işleyiciye sahip iletiler olduğu sürece işleyici çöp toplanamaz.
15:02

0

Kafam karıştı. Bulduğum örnek, statik özelliği tamamen önler ve UI iş parçacığını kullanır:

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

Bu çözüm hakkında sevdiğim şey, sınıf ve yöntem değişkenlerini karıştırmaya çalışırken sorun yok.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.