Обратный вызов GATT не может зарегистрироваться

Я пытаюсь написать приложение для отправки сообщений по Bluetooth с низким энергопотреблением, которое затем будет передано UART на моем периферийном устройстве. Я следовал за шагами здесь, и приложение сканирует и находит устройство успешно. Однако подключение с использованием метода BluetoothGatt = BluetoothDevice.connectGatt(context, autoconnect, callback) завершается неудачно, а logcat говорит "Не удалось зарегистрировать обратный вызов".

Звонок сделан из:

//device scan callback
private BluetoothAdapter.LeScanCallback btScanCallback = new BluetoothAdapter.LeScanCallback() 
{
    @Override
    public void onLeScan(final BluetoothDevice device, final int rssi, final byte[] scanRecord)
    {       
        some stuff
        currBtGatt = device.connectGatt(parentActivity, false, btGattCallback);
    }
};

И обратный звонок Гатта:

//GATT callback
private BluetoothGattCallback btGattCallback = new BluetoothGattCallback()
{       
    @Override
    public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState)
    {
        // if connected successfully
        if(newState == BluetoothProfile.STATE_CONNECTED)
        {
            //discover services
            updateStatus("Connected");
            gatt.discoverServices();
        }
        else if(newState == BluetoothProfile.STATE_DISCONNECTED)
        {
            updateStatus("Disconnected");
        }
    }

    @Override
    public void onServicesDiscovered(BluetoothGatt gatt, int status)
    {
        if(status == BluetoothGatt.GATT_SUCCESS)
        {
            //pick out the (app side) transmit channel
            currBtService = gatt.getService(uartUuids[0]);
            currBtCharacteristic = currBtService.getCharacteristic(uartUuids[1]);
        }
        else 
        {
            updateStatus("Service discovery failed");
        }
    }
};

Logcat говорит:

11-19 10:40:39.363: D/BluetoothAdapter(11717): stopLeScan()
11-19 10:40:39.373: D/BluetoothGatt(11717): connect() - device: DC:6D:75:0C:0F:F9, auto: false
11-19 10:40:39.373: D/BluetoothGatt(11717): registerApp()
11-19 10:40:39.373: D/BluetoothGatt(11717): registerApp() - UUID=3ba20989-5026-4715-add3-a5e31684009a
11-19 10:40:39.373: I/BluetoothGatt(11717): Client registered, waiting for callback
11-19 10:40:49.373: E/BluetoothGatt(11717): Failed to register callback
11-19 10:40:49.533: D/BluetoothGatt(11717): onClientRegistered() - status=0 clientIf=5
11-19 10:40:49.533: E/BluetoothGatt(11717): Bad connection state: 0
11-19 10:40:49.593: D/BluetoothGatt(11717): onClientConnectionState() - status=0 clientIf=5 device=DC:6D:75:0C:0F:F9
11-19 10:40:49.593: W/BluetoothGatt(11717): Unhandled exception: java.lang.NullPointerException

Интересно, что мое периферийное устройство переходит в "подключенное" состояние (у меня есть индикаторы), и я могу подключиться к нему с того же телефона с помощью демонстрационного приложения или с помощью ключа BLE для ПК. Любые идеи приветствуются.

[EDIT] метод connectGatt возвращает null, что, я думаю, ожидается.

[EDIT] При проверке исходного кода API 18 выясняется, что сообщение "Не удалось зарегистрировать обратный вызов" доставлено, потому что метод registerApp() возвращает false, потому что метод registerClient() в IBluetoothGatt "mService" выдает удаленное исключение, вероятно. на линии:

enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");

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

4 ответа

Решение

Я наконец понял эту проблему. Устройство, которое я использую, - это Samsung Galaxy S4, и реальная проблема (спасибо Wibble за руководство в вашем ответе, но вы немного ошибаетесь в своем заключении), кажется, связана с многопоточностью.

В ответе Виббла он заявил, что добавление кнопки для подключения решило его проблему. Я начал задаваться вопросом, почему это важно, и я также могу подключаться и отключаться в течение всего сеанса без кнопки GUI, используя фоновые рабочие потоки. Как только я принудительно закрываю свое приложение, перезапускаю его и пытаюсь подключиться, я получаю сообщение об ошибке "Не удалось зарегистрировать обратный вызов". и ничего больше не работает. Я чуть не выдернул волосы над этим:)

Смотрите мой пост на форумах Samsung для более подробной информации о моих точных проблемах.

Решение. Чтобы обойти эту проблему, просто запустите любой код взаимодействия BLE (устройство #connectGatt, подключение, отключение и т. Д.) В UIThread (с обработчиком, локальной службой или Activity#runOnUiThread). Следуйте этому правилу, и вы, надеюсь, избежите этой ужасной проблемы.

Глубоко в нашей библиотеке я имел доступ только к контексту приложения. Вы можете создать обработчик из контекста, который будет публиковать сообщения в главном потоке, используя new Handler(ctx.getMainLooper());

Если вы столкнулись с другими проблемами подключения, разверните пример приложения в samples\android-18\legacy\BluetoothLeGatt и посмотрите, работает ли это приложение. Это было своего рода основой для понимания того, что BLE действительно работает с моим периферийным устройством, и дало мне надежду, что, если я откопаю достаточно в нашей библиотеке, я в конечном итоге найду ответ.

РЕДАКТИРОВАТЬ: я не видел эту проблему "Не удалось зарегистрировать обратный вызов" на Nexus 4, Nexus 5 или Nexus 7 2013 при использовании фоновых потоков для выполнения операций BLE. Это может быть просто проблемой в реализации Samsungs 4.3.

Итак, моей проблемой было запустить его из рекурсивного сервиса. connectGatt отлично работал с lollipop, но более старые версии возвращали ноль. запуск в главном потоке решил проблему. Это мое решение:

public void connectToDevice( String deviceAddress) {
    mDeviceAddress = deviceAddress;
    final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(mDeviceAddress);

    Handler handler = new Handler(Looper.getMainLooper());
    handler.post(new Runnable() {
        @Override
        public void run() {


            if (device != null) {

                mGatt = device.connectGatt(getApplicationContext(), true, mGattCallback);
                scanLeDevice(false);// will stop after first device detection
            }
        }
    });
}

Я также могу подтвердить, что Lo-Tan - это ответ, который нужно проверить первым. Я протестировал множество устройств, некоторые из них работают хорошо, когда вы запускаете из вторичного потока. Некоторые могут через некоторое время заблокировать поведение непредсказуемо.

Вот список вещей, которые нужно сделать:

  1. Создатель уверен, что вы используете новый обработчик (Looper.getMainLooper ()). Post (новый Runnable) для любой операции Гатта (подключение, отключение, закрытие), но также и для операций сканера (startScan, stopScan и т. Д.).

  2. На Android 6 (или, может быть, 5) есть условие гонки для прямого подключения, поэтому попробуйте подключить gatt следующим образом:

     new Handler(getContext().get().getMainLooper()).post(() -> {
         if (CommonHelper.isNOrAbove()) {
            connectedGatt = connectedBLEDevice.connectGatt(context.get(), true, gattCallback, BluetoothDevice.TRANSPORT_AUTO);
             Timber.tag("HED-BT").d("Connecting BLE after N");
         } else {   
            try {
                Method connectGattMethod = connectedBLEDevice.getClass().getMethod("connectGatt", Context.class, boolean.class, BluetoothGattCallback.class, int.class);
                 connectedGatt = (BluetoothGatt) connectGattMethod.invoke(connectedBLEDevice, context.get(), false, gattCallback, BluetoothDevice.TRANSPORT_AUTO);
                 Timber.tag("HED-BT").d("Connecting BLE before N");
             } catch (Exception e) {
                 failedConnectingBLE();
             }
         }
     });
    
  3. При отсоединении гатта сначала вызовите метод connect (), а затем - после него в процедуре GattCallback.

Для автоматического подключения к устройству Bluetooth, т. Е. Без явного пользовательского ввода, как я пытался сделать, требуется разрешение BLUETOOTH_PRIVILEDGE. Однако это не доступно сторонним приложениям, поэтому мой код не удался. Добавление пункта меню для подключения и использования того же кода работает нормально.

http://developer.android.com/reference/android/Manifest.permission.html

Другие вопросы по тегам