Symptom
Some reads, writes or setCharacteristicNotification/CCCD writes never complete, randomly. Or connectGatt() keeps failing with status 133 (GATT_ERROR) after a few reconnects.
Cause 1: concurrent operations
BluetoothGatt handles one operation at a time. If you call writeCharacteristic() while a readCharacteristic() is still pending, the second call returns false (or the newer API returns an error code) and nothing is sent. Code that fires several operations in a row, e.g. enabling notifications on three characteristics in onServicesDiscovered(), loses all but the first.
Fix: an operation queue
- Put every GATT operation (read, write, descriptor write, MTU request,
readRemoteRssi) into a single FIFO queue per device. - Start the next operation only from the matching callback (
onCharacteristicRead,onCharacteristicWrite,onDescriptorWrite,onMtuChanged, ...). - Add a timeout per operation (a few seconds) that fails the current item and moves on, because callbacks can go missing when the link drops.
- Clear the queue on disconnect.
Enabling notifications is two steps: setCharacteristicNotification() (local) and a write of the CCCD descriptor (remote), and that write goes through the queue.
Cause 2: leaked clients (status 133)
Each connectGatt() allocates a client interface in the Bluetooth stack. The pool is limited. Apps that call disconnect() but never close(), or create a new BluetoothGatt for every retry, run out of them, and connections start failing with status 133.
Fix: lifecycle
- On disconnect or any connection error: call
gatt.close()and drop the reference. - Retry with a new
connectGatt()after a short delay instead of reusing a broken object. - Use
autoConnect = falsefor the first, user-triggered connection (faster);truefor background reconnection to a known device. - Run GATT calls from one thread (e.g. a dedicated handler) to avoid races in your queue.