Этот класс обработчика должен быть статическим, иначе могут возникнуть утечки: 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

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

VansFannel
источник
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);
  }
}
Sogger
источник
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
1
@StuartCampbell: Вы правы. См. Groups.google.com/forum/#!topic/android-developers/L_xYM0yS6z8 .
MDTech.us_MAN
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;
        }
    }

    ...

}
Marius
источник
0

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

Chaitanya
источник
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();
                }
            }
        };
    }

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

user2515235
источник