Этот класс Handler должен быть статическим, иначе могут возникнуть утечки: IncomingHandler

Я разрабатываю приложение для Android 2.3.3 со службой. У меня есть это внутри этой службы для связи с основным действием:

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

И вот, final Messenger mMessenger = new Messenger(new IncomingHandler());, я получаю следующее предупреждение Lint:

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

Что это значит?


person VansFannel    schedule 10.07.2012    source источник
comment
Прочтите сообщение в блоге для получения дополнительной информации по этой теме!   -  person Adrian Monk    schedule 18.01.2013
comment
Утечки памяти, вызванные сборкой мусора ... Этого достаточно, чтобы продемонстрировать, насколько Java непоследовательна и плохо спроектирована   -  person Gojir4    schedule 06.06.2018


Ответы (7)


Если класс IncomingHandler не является статическим, он будет иметь ссылку на ваш Service объект.

Handler объекты для одного и того же потока имеют общий объект Looper, который они отправляют сообщения и читают из него.

Поскольку сообщения содержат target Handler, пока в очереди сообщений есть сообщения с целевым обработчиком, обработчик не может быть собран сборщиком мусора. Если обработчик не является статическим, ваш Service или Activity не может быть обработан сборщиком мусора даже после уничтожения.

Это может привести к утечкам памяти, по крайней мере, на некоторое время - пока сообщения остаются в очереди. Это не проблема, если вы не отправляете сообщения с длительной задержкой.

Вы можете сделать IncomingHandler статическим и использовать WeakReference для своей службы:

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

Дополнительную информацию см. В сообщении Ромена Гая.

person Tomasz Niedabylski    schedule 10.07.2012
comment
Спасибо за Ваш ответ. Как я могу исправить эту проблему? - person VansFannel; 10.07.2012
comment
Большое спасибо за этот ответ. Предупреждение о ворсинах о статических обработчиках всегда озадачивало меня, и после примерно часа поисков по этой теме ваш ответ хорошо объяснил это и даже дал источник Ромена Гая, если бы я мог дать ему второй голос, я бы - person snctln; 11.07.2012
comment
Ромен показывает, что WeakReference для внешнего класса - это все, что нужно - статический вложенный класс не нужен. Я думаю, что предпочту подход WeakReference, потому что в противном случае весь внешний класс резко изменится из-за всех необходимых мне «статических» переменных. - person Someone Somewhere; 18.07.2012
comment
Если вы хотите использовать вложенный класс, он должен быть статическим. В противном случае WeakReference ничего не меняет. Внутренний (вложенный, но не статический) класс всегда содержит сильную ссылку на внешний класс. Однако нет необходимости в каких-либо статических переменных. - person Tomasz Niedabylski; 19.07.2012
comment
OK. Спасибо за повторение того, что класс Handler должен быть статическим. Мой обработчик вызывает функции во внешнем классе в зависимости от полученного сообщения, поэтому на самом деле вся моя служба (внешний класс) будет сильно затронута, если внутренний класс обработчика должен быть статическим. - person Someone Somewhere; 19.07.2012
comment
Томаш, что происходит, когда mService.get() == null? Что происходит с сообщением? Действительно ли возможно получить нуль от mService.get ()? - person Someone Somewhere; 23.07.2012
comment
@SomeoneSomewhere mSerivce - это WeakReference. get() вернет null, если указанный объект был gc-ed. В этом случае, когда сервис мертв. - person Tomasz Niedabylski; 24.07.2012
comment
@Tomasz Спасибо, что написали этот ответ. Это было действительно полезно. - person wojciii; 02.10.2012
comment
@Macarse Уважаемый Macarse. Можете ли вы отредактировать свой ответ и показать, как вы объявили UDPListenerService? - person user170317; 05.10.2012
comment
Сервис такой же, как в вопросе. Нужно только добавить в него метод handleMesage (msg). - person Tomasz Niedabylski; 05.10.2012
comment
@Macarse могу ли я избежать класса обработчика? Я пытался создать подкласс и использовать его с помощью общедоступного WeakHandler uaReceiverHandler = new WeakHandler (phonePadForUsing); но HangleMessage не запустился ( - person user170317; 05.10.2012
comment
Примечание: после того, как IncomingHandler был статическим, я получал сообщение об ошибке. Конструктор MyActivity.IncomingHandler () не определен. в строке final Messenger inMessenger = new Messenger (new IncomingHandler ()) ;. Решение состоит в том, чтобы изменить эту строку на final Messenger inMessenger = new Messenger (new IncomingHandler (this)) ;. - person Lance Lefebure; 05.02.2013
comment
@Tomasz Niedabylski Я получаю сообщение об ошибке, что последнее поле не может быть назначено (mService объявляется окончательным, затем мы пытаемся назначить его в конструкторе). Кроме того, если обработчик статический, getApplicationContext () не работает, поэтому я не могу создать тост. Есть мысли по любому из этих вопросов? - person David Doria; 01.10.2013
comment
Убедитесь, что mService только объявлен, а не назначен при объявлении (final mService = null; - ошибка). Что касается тоста, поставьте code in service.handleMessage() - person Tomasz Niedabylski; 01.10.2013
comment
@Someone Somewhere Да, сообщение Ромена ошибочно в том, что он пропустил объявление статического внутреннего класса, что упускает из виду всю суть. Если только у него нет классного компилятора, который автоматически преобразует внутренние классы в статические, когда они не используют переменные класса. - person Sogger; 07.01.2015
comment
Разработчики Android используют этот подход, так что все не так уж плохо, правда? developer.android.com/reference/android/app/. - person JohnyTex; 13.01.2016

Как уже упоминалось, предупреждение Lint связано с потенциальной утечкой памяти. Вы можете избежать предупреждения Lint, передав Handler.Callback при построении Handler (т.е. вы не подклассифицируете Handler и не существует Handler нестатического внутреннего класса):

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

Насколько я понимаю, это не предотвратит потенциальной утечки памяти. Объекты Message содержат ссылку на объект mIncomingHandler, который содержит ссылку на объект Handler.Callback, который содержит ссылку на объект Service. Пока в очереди сообщений Looper есть сообщения, Service не будет сборщиком мусора. Однако это не будет серьезной проблемой, если в очереди сообщений нет сообщений с длительной задержкой.

person Michael    schedule 11.02.2013
comment
@Braj Я не думаю, что избегать предупреждения о ворсинах, но все же сохранить ошибку - это вообще хорошее решение. Если, как указано в предупреждении о линтах, обработчик не помещен в ваш основной петлитель (и вы можете гарантировать, что все сообщения, ожидающие его, будут уничтожены при уничтожении класса), утечка ссылки будет смягчена. - person Sogger; 07.01.2015

Вот общий пример использования слабой ссылки и класса статического обработчика для решения проблемы (как рекомендовано в документации 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);
  }
}
person Sogger    schedule 07.01.2015
comment
Пример Соггера великолепен. Однако последний метод в Myclass следует объявить как public Handler getHandler() вместо public void. - person Jason Porter; 12.03.2015
comment
Это похоже на ответ Томаша Нидабыльского. - person CoolMind; 12.08.2015

Этот способ хорошо сработал для меня, сохраняет код чистым, сохраняя место обработки сообщения в его собственном внутреннем классе.

Обработчик, который вы хотите использовать

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

Внутренний класс

class IncomingHandlerCallback implements Handler.Callback{

        @Override
        public boolean handleMessage(Message message) {

            // Handle message code

            return true;
        }
}
person Stuart Campbell    schedule 17.04.2013
comment
Здесь метод handleMessage в конце возвращает true. Не могли бы вы объяснить, что именно это означает (возвращаемое значение true / false)? Спасибо. - person JibW; 15.08.2013
comment
Я понимаю, что возврат истины означает, что вы обработали сообщение, и поэтому сообщение не должно передаваться в другое место, например. базовый обработчик. Тем не менее, я не смог найти никакой документации и буду с радостью исправлен. - person Stuart Campbell; 22.08.2013
comment
Javadoc говорит: Конструктор связывает этот обработчик с Looper для текущего потока и принимает интерфейс обратного вызова, в котором вы можете обрабатывать сообщения. Если этот поток не имеет петлителя, этот обработчик не сможет получать сообщения, поэтому создается исключение. ‹- Я думаю, что новый обработчик (new IncomingHandlerCallback ()) не будет работать, если к потоку не прикреплен Looper, и это МОЖЕТ быть так. Я не говорю, что это неправильно в некоторых случаях, я просто говорю, что это не всегда работает так, как вы могли ожидать. - person user504342; 20.10.2013
comment
@StuartCampbell: Вы правы. См. groups.google.com/forum/#!topic/android- разработчики / L_xYM0yS6z8. - person MDTech.us_MAN; 20.04.2014

С помощью ответа @Sogger я создал общий обработчик:

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

Интерфейс:

public interface MessageHandler {

    void handleMessage(Message msg);

}

Пользуюсь следующим образом. Но я не уверен на 100%, безопасно ли это от утечек. Может быть, кто-нибудь мог бы это прокомментировать:

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

    ...

}
person mbo    schedule 27.03.2017

Я не уверен, но вы можете попробовать обнулить обработчик инициализации в onDestroy ()

person Chaitanya    schedule 07.01.2015
comment
Все объекты-обработчики для одного и того же потока имеют общий объект Looper, который они отправляют сообщения и читают из него. Поскольку сообщения содержат целевой обработчик, до тех пор, пока в очереди сообщений есть сообщения с целевым обработчиком, обработчик не может быть собран сборщиком мусора. - person msysmilu; 25.11.2015

Я запутался. В найденном мною примере полностью отсутствует статическое свойство и используется поток пользовательского интерфейса:

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

Что мне нравится в этом решении, так это то, что при попытке смешать переменные класса и метода нет проблем.

person user2515235    schedule 10.01.2020