BluetoothAdapter.getDefaultAdapter() бросает RuntimeException, а не в действие

Когда я пытаюсь получить адаптер bluetooth по умолчанию, пока я НЕ в действии, но в TimerTask (создан внутри Service), используя:

BluetoothAdapter.getDefaultAdapter();

Я получаю следующее исключение:

Exception while invoking java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()

У моего приложения нет активности - так есть ли возможность отключить этот адаптер от Activity?

Ответ 1

Это, кажется, ошибка в Android и все еще существует в Android 4.0 (Ice Cream Sandwich)

Чтобы обойти это и иметь возможность вызывать BluetoothAdapter.getDefaultAdapter() из рабочего потока (например, AsyncTask), вам нужно только вызвать BluetoothAdapter.getDefaultAdapter() один раз в основном потоке пользовательского интерфейса (например, внутри onCreate() вашей текущей активности).

RuntimeException генерируется только при инициализации, а BluetoothAdapter.getDefaultAdapter() инициализирует только первый раз, когда вы его вызываете. Последующие вызовы будут успешными даже в фоновом потоке.

Ответ 2

Вызов BluetoothAdapter.getDefaultAdapter() в потоке UI работает, но не очень практичен. Я пробовал обходной путь с поддельной Activity, но, так как я ненавижу такие обходные пути, я решил прочитать, что сообщение об ошибке действительно говорит, и это не что иное, как поток не вызывал Looper.prepare().

Поэтому вызов Looper.prepare() непосредственно перед вызовом BluetoothAdapter.getDefaultAdapter() должен решить проблему в любом месте, а не только в потоке пользовательского интерфейса.

Прекрасно работает для меня.

Ответ 3

Не знаю, насколько это правильно, но я добавил эту функцию обертки:

static boolean m_calledLooperAlready = false;

BluetoothAdapter getDefaultBluetoothAdapter() {
    if ( !m_calledLooperAlready ) {
        try  {
            android.os.Looper.prepare();
        } catch ( RuntimeException e ) { e.printStackTrace(); }
        m_calledLooperAlready = true;
    }
    return BluetoothAdapter.getDefaultAdapter();
}

... и заменил все вхождения BluetoothAdapter.getDefaultAdapter() на getDefaultBluetoothAdapter(). Это работает нормально для меня: 2.2.1, 2.3.3, 4.0.4, 4.3

Ответ 4

Остерегайтесь gotcha, который существует в 2.3.x, но который был исправлен в 4.x: если вы вызываете BluetoothAdapter.getDefaultAdapter() в любом потоке, отличном от основного потока приложения, этот поток должен вызывать Looper.prepare(), а затем Looper.loop().

В противном случае будет возникать хотя бы одна проблема, с которой я столкнулся: accept() будет успешным при первом попытке подключения, но затем не будет успешным при последовательных попытках даже после использования close() в ServerSocket.

Это происходит потому, что в старой версии BluetoothAdapter очистка записи SDP происходит путем сообщения, отправленного в обработчик, созданный в потоке, где вызывается getDefaultAdapter().