Цей клас обробника повинен стати статичним або можуть виникати витоки: 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

Відповіді:


391

Якщо 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);
         }
    }
}

Дивіться цю публікацію Ромена Гая для подальшого ознайомлення


3
Ромен показує, що слабке посилання на зовнішній клас - це все, що потрібно - статичний вкладений клас не потрібен. Я думаю, що я віддаю перевагу підходу WeakReference, тому що в іншому випадку весь зовнішній клас різко змінюється через усі «статичні» змінні, які мені знадобляться.
Хтось десь

35
Якщо ви хочете використовувати вкладений клас, він повинен бути статичним. Інакше WeakReference нічого не змінює. Внутрішній (вкладений, але не статичний) клас завжди сильно посилається на зовнішній клас. Однак немає потреби в статичних змінних.
Томаш Нідабильський

2
@SomeoneSomewhere mSerivce - це слабка референція. get()поверне null, коли посилався об'єкт був gc-ed. У цьому випадку, коли служба загинула.
Томаш Нідабильський

1
Примітка: після створення InitingHandler статичним я отримав помилку "Конструктор MyActivity.IncomingHandler () не визначений." у рядку "final Messenger inMessenger = новий Messenger (новий IncomingHandler ());". Рішення полягає в зміні цього рядка на "остаточний Messenger inMessenger = новий Messenger (новий IncomingHandler (це));".
Lance Lefebure

4
@ Хтось десь Так. Повідомлення Ромена помиляється тим, що він пропустив оголошення внутрішнього класу статичним, що пропускає всю точку. Якщо у нього є якийсь надзвичайно класний компілятор, який автоматично перетворює внутрішні класи в статичні класи, коли вони не використовують змінні класу.
Соггер

67

Як уже згадували інші, попередження Lint - це через потенційну витік пам'яті. Ви можете уникнути попередження Lint, передавши a 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 Я не думаю, що уникати попередження про ворсинки, але все-таки зберігати помилку - це взагалі хороше рішення. За винятком випадків, коли зазначено попередження про ворсинки, якщо обробник не розміщений на вашому головному циклі (і ви можете переконати, що всі повідомлення, що очікують на нього, знищені, коли клас знищений), то витоку посилання зменшується.
Соггер

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
Приклад Соггер - чудовий. Однак останній метод у 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
Моє розуміння повернення істини полягає в тому, щоб вказати, що ви обробляли повідомлення, і тому повідомлення не слід передавати більше ніде, наприклад, основний обробник. Це сказало, що я не можу знайти жодної документації, і я буде щасливо виправлена.
Стюарт Кемпбелл

1
Javadoc каже: Constructor пов'язує цей обробник із Looper для поточного потоку та приймає інтерфейс зворотного виклику, в якому можна обробляти повідомлення. Якщо в цьому потоці немає петлевого механізму, цей обробник не зможе отримувати повідомлення, тому викидається виняток. <- Я думаю, що новий обробник (новий IncomingHandlerCallback ()) не працюватиме, якщо до потоку не буде прикріплений Looper, і це може бути так. Я не кажу, що це неправильно робити в деяких випадках, я просто кажу, що це не завжди працює, як ви могли очікувати.
користувач504342

1
@StuartCampbell: Ви маєте рацію. Див.: Groups.google.com/forum/#!topic/android-developers/L_xYM0yS6z8 .
MDTech.us_MAN

2

За допомогою відповіді @ Соггера я створив загальний обробник:

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.