Как я могу убедиться, что обработчик другого потока не является нулевым перед вызовом? - PullRequest
7 голосов
/ 02 февраля 2010

Моя программа вызвала исключение NullPointerException на днях, когда она пыталась использовать обработчик, созданный в другом потоке, для отправки этому потоку сообщения. Обработчик, созданный другим потоком, еще не был создан или еще не виден вызывающему потоку, несмотря на то, что вызывающий поток уже вызвал start в другом потоке. Это случается очень редко. Почти каждый тестовый прогон не получает исключения.

Мне было интересно, как лучше всего избежать этой проблемы с минимальными сложностями и ухудшением производительности. Программа представляет собой игру и очень чувствительна к производительности, особенно после запуска. Поэтому я стараюсь, например, избегать использования синхронизации после настройки и предпочитаю избегать вращения переменной в любое время.

Справочная информация:
В Android класс Handler может использоваться для «постановки в очередь действия, которое должно быть выполнено в другом потоке, чем ваш». Документация здесь:
http://developer.android.com/intl/de/reference/android/os/Handler.html

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

Когда обработчик предназначен для потока, отличного от потока пользовательского интерфейса, также должен использоваться класс Looper:
http://developer.android.com/intl/de/reference/android/os/Looper.html

В документации приведен пример использования двух классов для этой цели:

class LooperThread extends Thread {
    public Handler mHandler;

    public void run() {
        Looper.prepare();

        mHandler = new Handler() {
            public void handleMessage(Message msg) {
                // process incoming messages here
            }
        };

        Looper.loop();
    }
}

Мой очень уродливый обходной путь в настоящее время выглядит так:

public class LooperThread extends Thread {

    public volatile Handler mHandler;

    public final ArrayBlockingQueue<Object> setupComplete = new ArrayBlockingQueue<Object>(1);

    public void run() {
        Looper.prepare();

        mHandler = new Handler() {
            public void handleMessage(Message msg) {
                // process incoming messages here
            }
        };

        setupComplete();

        Looper.loop();
    }

    public void waitForSetupComplete() {
        while ( true ) {
            try {
                setupComplete.take();
                return;
            } catch (InterruptedException e) {
                //Ignore and try again.
            }
        }
    }

    private void setupComplete() {
        while( true ) {
            try {
                setupComplete.put(new Object());
                return;
            } catch (InterruptedException e) {
                //Ignore and try again.
            }        
        }
    }

}

Код в потоке создания выглядит следующим образом:

    LooperThread otherThread = new LooperThread();
    otherThread.start();        
    otherThread.waitForSetupComplete();
    otherThread.mHandler.sendEmptyMessage(0);

Есть ли лучшие решения? Спасибо.

Ответы [ 3 ]

12 голосов
/ 03 февраля 2010

Я бы пошел с классическим ожиданием / уведомлением

public class LooperThread extends Thread {

    private Handler mHandler;

    public void run() {
        Looper.prepare();

        synchronized (this) {
            mHandler = new Handler() {
                public void handleMessage(Message msg) {
                    // process incoming messages here
                }
            };
            notifyAll();
        }

        Looper.loop();
    }

    public synchronized Handler getHandler() {
        while (mHandler == null) {
            try {
                wait();
            } catch (InterruptedException e) {
                //Ignore and try again.
            }
        }
        return mHandler;
    }
}

Обработчик, возвращаемый из getHandler, можно использовать много раз, не вызывая синхронизированный getHandler.

5 голосов
/ 03 февраля 2010

Подготовка Looper может на некоторое время заблокировать, поэтому я предполагаю, что вы попали в состояние, когда prepare() занимает некоторое время, поэтому mHandler все еще не определено.

Вы можете иметь Thread продление HandlerThread, хотя даже тогда вам все еще придется ждать, чтобы инициализировать Looper. Возможно, что-то подобное может сработать, когда у вас есть Handler, определенный отдельно, но с использованием Looper вашего пользовательского потока.

Может быть.

private void setUp() {
    mHandlerThread = new CustomThread("foo", Process.THREAD_PRIORITY_BACKGROUND);
    mHandlerThread.start();

    // Create our handler; this will block until looper is initialised
    mHandler = new CustomHandler(mHandlerThread.getLooper());
    // mHandler is now ready to use
}

private class CustomThread extends HandlerThread {
    public void run() {
        // ...
    }
}   

private class CustomHandler extends Handler {
    CustomHandler(Looper looper) {
        super(looper);
    }

    @Override
    public void handleMessage(Message msg) {
        // ...
    }
}
1 голос
/ 12 декабря 2013

Я просто хочу добавить, что проверенный ответ является лучшим, но если вы проверите его так, он не будет работать, потому что вам нужно вызывать метод super при запуске, так как он отвечает за подготовку петлителя, поэтому код должен быть как это:

private void setUp() {
  mHandlerThread = new CustomThread("foo", Process.THREAD_PRIORITY_BACKGROUND);
  mHandlerThread.start();

  // Create our handler; this will block until looper is initialised
  mHandler = new CustomHandler(mHandlerThread.getLooper());
  // mHandler is now ready to use
}

private class CustomThread extends HandlerThread {
   public void run() {
    super.run() // <- VERY IMPORTANT OTHERWISE IT DOES NOT WORK
    // your code goes here
   }
}   

private class CustomHandler extends Handler {
CustomHandler(Looper looper) {
    super(looper);
}

 @Override
 public void handleMessage(Message msg) {
    // ...
 }

}

...