Android Bluetooth: DeadObjectException в обратном вызове BLE onNotify
Мы используем устройство ble, которое имеет заметную характеристику, и мы используем EnableNotification для получения данных через обратный вызов. Мы реализовали очередь команд и достигли состояния, в котором больше не выдаем команды, а только слушаем характерное уведомление.
Через случайное количество времени мы сталкиваемся с ошибкой, из-за которой служба/адаптер bluetooth умирает, в логарифме мы находим DeadObjectException. Эту ошибку очень сложно воспроизвести и отследить, но она критична для нашего приложения. В обратном вызове мы пытаемся обработать все возможные исключения (в каждом из методов класса), но DeadObjectException не может быть обработано, похоже, оно выброшено в другом процессе.
Используемое нами устройство отправляет 2 байта данных при каждом срабатывании и может отправлять около 20 пакетов в секунду.
Нам не повезло найти помощь по этому поводу. Кто-нибудь может дать нам совет? Есть ли способ зафиксировать исключения, вызванные связывателем Bluetooth или службой? Как правильно обращаться с заметными характеристиками?
Это вывод logcat:
2022-02-22 13:16:50.125 5423-5910/? I/bt_stack: [INFO:gatt_main.cc(919)] gatt_data_process op_code = 27, msg_len = 4
2022-02-22 13:16:50.125 5423-5910/? E/bt_btif: bta_gattc_process_indicate, ignore HID ind/notificiation
2022-02-22 13:16:50.125 5423-5541/? E/JavaBinder: !!! FAILED BINDER TRANSACTION !!! (parcel size = 152)
2022-02-22 13:16:50.126 5423-5541/? E/BtGatt.JNI: An exception was thrown by callback 'btgattc_notify_cb'.
2022-02-22 13:16:50.143 5423-5910/? I/bt_stack: [INFO:gatt_main.cc(919)] gatt_data_process op_code = 27, msg_len = 4
2022-02-22 13:16:50.143 5423-5910/? E/bt_btif: bta_gattc_process_indicate, ignore HID ind/notificiation
2022-02-22 13:16:50.171 17518-8879/---.---.--- I/SMNPlugin: Android [Main.cpp:763:_setStatePieceDetected()] [Piece State Detection] PIECE_NO_CORRECT.
2022-02-22 13:16:50.180 5423-5541/? E/BtGatt.JNI: android.os.DeadObjectException: Transaction failed on small parcel; remote process probably died
at android.os.BinderProxy.transactNative(Native Method)
at android.os.BinderProxy.transact(BinderProxy.java:550)
at android.bluetooth.IBluetoothGattCallback$Stub$Proxy.onNotify(IBluetoothGattCallback.java:561)
at com.android.bluetooth.gatt.GattService.onNotify(GattService.java:1530)
2022-02-22 13:16:50.180 5423-5541/? E/JavaBinder: !!! FAILED BINDER TRANSACTION !!! (parcel size = 152)
2022-02-22 13:16:50.180 5423-5541/? E/BtGatt.JNI: An exception was thrown by callback 'btgattc_notify_cb'.
2022-02-22 13:16:50.181 5423-5541/? E/BtGatt.JNI: android.os.DeadObjectException: Transaction failed on small parcel; remote process probably died
at android.os.BinderProxy.transactNative(Native Method)
at android.os.BinderProxy.transact(BinderProxy.java:550)
at android.bluetooth.IBluetoothGattCallback$Stub$Proxy.onNotify(IBluetoothGattCallback.java:561)
at com.android.bluetooth.gatt.GattService.onNotify(GattService.java:1530)
Это пример нашего обратного вызова:
public BluetoothDeviceInterface(final BluetoothDevice device, final int arrayIndex) {
_device = device;
_internalArrayIndex = arrayIndex;
_gatt = null;
_gattCallback = new BluetoothGattCallback() {
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
Log.i(LOGTAG, "On Connection State Change. Status: " + status + ". New state: " + newState);
try {
if (status == BluetoothGatt.GATT_SUCCESS) {
if (newState == BluetoothProfile.STATE_CONNECTED) {
Log.i(LOGTAG, "Connected to device: " + GetAddress());
_gatt = gatt;
_gatt.discoverServices();
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
Log.i(LOGTAG, "Disconnected from device: " + GetAddress());
_gatt.close();
_gatt = null;
}
} else {
Log.i(LOGTAG, "Error connecting GATT.");
_gatt.close();
_gatt = null;
}
_callback.OnStateChanged(status, newState);
} catch (Exception e) {
Log.e(LOGTAG, "Exception in onConnectionStateChange: " + e.toString());
}
}
@Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
Log.i(LOGTAG, "On Services Discovered. Status: " + status);
try {
Log.i(LOGTAG, "Services of device " + GetAddress() + " discovered. Total number of: " + gatt.getServices().size());
_callback.CleanServices();
for(BluetoothGattService service : gatt.getServices()) {
String serviceUUID = service.getUuid().toString();
_callback.AddNewService(service.getType(), serviceUUID);
for (BluetoothGattCharacteristic characteristic : service.getCharacteristics()) {
String characteristicUUID = characteristic.getUuid().toString();
int characteristicProperties = characteristic.getProperties();
boolean isReadable = (characteristicProperties & BluetoothGattCharacteristic.PROPERTY_READ) != 0;
boolean isWritable = (characteristicProperties & BluetoothGattCharacteristic.PROPERTY_WRITE) != 0;
boolean isWritableWithoutResponse = (characteristicProperties & BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE) != 0;
boolean isIndicateable = (characteristicProperties & BluetoothGattCharacteristic.PROPERTY_INDICATE) != 0;
boolean isNotifiable = (characteristicProperties & BluetoothGattCharacteristic.PROPERTY_NOTIFY) != 0;
_callback.AddNewCharacteristic(serviceUUID, characteristicUUID, isReadable, isWritable,
isWritableWithoutResponse, isIndicateable, isNotifiable);
}
}
_callback.OnServiceDiscoveryEnd();
} catch (Exception e) {
Log.e(LOGTAG, "Exception in onServicesDiscovered: " + e.toString());
}
}
@Override
public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
Log.i(LOGTAG, "On Characteristic Read. Status: " + status);
try {
if (status == BluetoothGatt.GATT_SUCCESS) {
_callback.OnCharacteristicRead(characteristic.getService().getUuid().toString(), characteristic.getUuid().toString(), characteristic.getValue());
} else if (status == BluetoothGatt.GATT_READ_NOT_PERMITTED) {
_callback.OnCharacteristicReadError(characteristic.getService().getUuid().toString(), characteristic.getUuid().toString(), "Read not permitted.");
} else {
_callback.OnCharacteristicReadError(characteristic.getService().getUuid().toString(), characteristic.getUuid().toString(), "Status error: " + status);
}
} catch (Exception e) {
Log.e(LOGTAG, "Exception in onCharacteristicRead: " + e.toString());
}
}
@Override
public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
Log.i(LOGTAG, "On Characteristic Write. Status: " + status);
try {
if (status == BluetoothGatt.GATT_SUCCESS) {
_callback.OnCharacteristicWrite(characteristic.getService().getUuid().toString(), characteristic.getUuid().toString());
} else if (status == BluetoothGatt.GATT_WRITE_NOT_PERMITTED) {
_callback.OnCharacteristicWriteError(characteristic.getService().getUuid().toString(), characteristic.getUuid().toString(), "Write not permitted.");
} else {
_callback.OnCharacteristicWriteError(characteristic.getService().getUuid().toString(), characteristic.getUuid().toString(), "Status error: " + status);
}
} catch (Exception e) {
Log.e(LOGTAG, "Exception in onCharacteristicWrite: " + e.toString());
}
}
@Override
public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
Log.i(LOGTAG, "On Descriptor Write. Status: " + status);
try {
if (status == BluetoothGatt.GATT_SUCCESS) {
_callback.OnDescriptorWrite(descriptor.getCharacteristic().getService().getUuid().toString(), descriptor.getCharacteristic().getUuid().toString());
} else if (status == BluetoothGatt.GATT_WRITE_NOT_PERMITTED) {
_callback.OnDescriptorWriteError(descriptor.getCharacteristic().getService().getUuid().toString(), descriptor.getCharacteristic().getUuid().toString(), "Write not permitted.");
} else {
_callback.OnDescriptorWriteError(descriptor.getCharacteristic().getService().getUuid().toString(), descriptor.getCharacteristic().getUuid().toString(), "Status error: " + status);
}
} catch (Exception e) {
Log.e(LOGTAG, "Exception in onDescriptorWrite: " + e.toString());
}
}
@Override
public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
Log.i(LOGTAG, "On Characteristic Changed.");
try {
Log.i(LOGTAG, characteristic.getService().getUuid().toString() + characteristic.getUuid().toString() + characteristic.getValue().toString());
_callback.OnCharacteristicChange(characteristic.getService().getUuid().toString(), characteristic.getUuid().toString(), characteristic.getValue());
} catch (Exception e) {
Log.e(LOGTAG, "Exception in onCharacteristicChanged: " + e.toString());
}
}
@Override
public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) {
Log.i(LOGTAG, "On Read Remote Rssi. Status: " + status + ". Rssi: " + rssi);
try {
if (status == BluetoothGatt.GATT_SUCCESS) {
_callback.OnRssiRead(rssi);
} else {
_callback.OnRssiRead(-1);
}
} catch (Exception e) {
Log.e(LOGTAG, "Exception in onReadRemoteRssi: " + e.toString());
}
}
};
С пустым обратным вызовом он все еще терпит неудачу:
public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
Log.i(LOGTAG, "On Characteristic Changed: " + characteristic.getStringValue(0));
}
Это код, который мы используем для включения уведомлений:
public boolean EnableNotifications(String serviceUUID, String characteristicUUID) {
if (!IsConnected())
return false;
BluetoothGattService service = _gatt.getService(UUID.fromString(serviceUUID));
if (service == null)
return false;
BluetoothGattCharacteristic characteristic = service.getCharacteristic(UUID.fromString(characteristicUUID));
if (characteristic == null)
return false;
int properties = characteristic.getProperties();
byte[] payload;
if ((properties & BluetoothGattCharacteristic.PROPERTY_NOTIFY) != 0) {
payload = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE;
} else if ((properties & BluetoothGattCharacteristic.PROPERTY_INDICATE) != 0) {
payload = BluetoothGattDescriptor.ENABLE_INDICATION_VALUE;
} else {
return false;
}
BluetoothGattDescriptor descriptor = characteristic.getDescriptor(CLIENT_CHARACTERISTIC_CONFIGURATION_UUID);
if (descriptor == null)
return false;
if (!_gatt.setCharacteristicNotification(characteristic, true))
return false;
descriptor.setValue(payload);
return _gatt.writeDescriptor(descriptor);
}