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


297

Я разрабатываю приложение для 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

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


24
Проверьте это сообщение в блоге для получения дополнительной информации на эту тему!
— Адриан Монк

1
Утечки памяти, вызванные сборкой мусора ... Этого достаточно, чтобы продемонстрировать, как Java несовместима и плохо спроектирована
— Gojir4

Ответы:


392

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

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

Поскольку сообщения содержат цель 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);
         }
    }
}

Смотрите этот пост от Romain Guy для дальнейшего использования


3
Ромен показывает, что WeakReference для внешнего класса - это все, что нужно - статический вложенный класс не нужен. Я думаю, что я предпочту подход WeakReference, потому что в противном случае весь внешний класс кардинально изменится из-за всех «статических» переменных, которые мне понадобятся.
— Кто-то где-то

35
Если вы хотите использовать вложенный класс, он должен быть статическим. В противном случае WeakReference ничего не меняет. Внутренний (вложенный, но не статический) класс всегда содержит сильную ссылку на внешний класс. Там нет необходимости каких-либо статических переменных, хотя.
— Томаш Недабыльски

2
@SomeoneSomewhere mSerivce - Слабая ссылка. get()вернет ноль, когда указанный объект был gc-ed. В этом случае, когда сервис мертв.
— Томаш Недабыльски

1
Примечание: после создания статического IncomingHandler я получаю сообщение об ошибке «Конструктор MyActivity.IncomingHandler () не определен». в строке "окончательный мессенджер inMessenger = новый мессенджер (новый IncomingHandler ());". Решение состоит в том, чтобы изменить эту строку на "final Messenger inMessenger = new Messenger (new IncomingHandler (this));".
— Ланс Лефебур

4
@ Кто-то где-то Да, пост Ромен неправильный, потому что он пропустил объявление статики внутреннего класса, которая пропускает весь смысл. Если у него нет какого-то очень крутого компилятора, который автоматически конвертирует внутренние классы в статические классы, когда они не используют переменные класса.
— Соггер

67

Как уже упоминали другие, предупреждение 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очереди сообщений есть сообщения, ServiceGC не будет. Тем не менее, это не будет серьезной проблемой, если у вас нет сообщений с длительной задержкой в ​​очереди сообщений.


10
@Braj Я не думаю, что избегать предупреждения lint, но все еще держать ошибку - хорошее решение вообще. Если в предупреждении lint указано, что обработчик не помещен в ваш главный петлитель (и вы можете убедиться, что все ожидающие сообщения уничтожены при уничтожении класса), то утечка ссылки будет смягчена.
— Соггер

33

Вот общий пример использования слабой ссылки и класса статического обработчика для решения проблемы (как рекомендуется в документации 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
Sogger пример отличный. Однако последний метод Myclassдолжен быть объявлен как public Handler getHandler()вместоpublic void
— Джейсон Портер

Это похоже на ответ Томаша Недабыльского
— CoolMind

24

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

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

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

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

class IncomingHandlerCallback implements Handler.Callback{

        @Override
        public boolean handleMessage(Message message) {

            // Handle message code

            return true;
        }
}

2
Здесь метод handleMessage возвращает истину в конце. Не могли бы вы объяснить, что именно это означает (возвращаемое значение true / false)? Спасибо.
— JibW

2
Насколько я понимаю, возвращая true, я должен указать, что вы обработали сообщение, и поэтому сообщение не должно передаваться где-либо еще, например, в основной обработчик. Это сказало, что я не мог найти документацию и был бы счастливо исправлен.
— Стюарт Кэмпбелл

1
Javadoc говорит: Constructor связывает этот обработчик с Looper для текущего потока и принимает интерфейс обратного вызова, в котором вы можете обрабатывать сообщения. Если этот поток не имеет петлителя, этот обработчик не сможет получать сообщения, поэтому выдается исключение. <- Я думаю, что новый обработчик (new IncomingHandlerCallback ()) не будет работать, если к потоку не подключен Looper, и это МОЖЕТ быть в этом случае. Я не говорю, что это неправильно в некоторых случаях, я просто говорю, что это не всегда работает, как вы могли ожидать.
— user504342


2

С помощью ответа @ 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;
        }
    }

    ...

}

0

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


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

0

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

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

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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.