La devolución de llamada de Gatt de BLuetooth no funciona con la nueva API de Lollipop

Actualmente tengo un método que escribe a los dispositivos BLE para que la emita. Mi devolución de llamada Bluetooth es la siguiente:

ReadCharacteristic rc = new ReadCharacteristic(context, ds.getMacAddress(), serviceUUID, UUID.fromString(myUUID), "") { @Override public void onRead() { Log.w(TAG, "callDevice onRead"); try{Thread.sleep(1000);}catch(InterruptedException ex){} WriteCharacteristic wc = new WriteCharacteristic(activity, context, getMacAddress(), serviceUUID, UUID.fromString(myUUID), ""){ @Override public void onWrite(){ Log.w(TAG, "callDevice onWrite"); } @Override public void onError(){ Log.w(TAG, "callDevice onWrite-onError"); } }; // Store data in writeBuffer wc.writeCharacteristic(writeBuffer); } @Override public void onError(){ Log.w(TAG, "callDevice onRead-onError"); } }; rc.readCharacteristic(); 

Mi implementación de ReadCharacteristic es como sigue:

 public class ReadCharacteristic extends BluetoothGattCallback { public ReadCharacteristic(Context context, String macAddress, UUID service, UUID characteristic, Object tag) { mMacAddress = macAddress; mService = service; mCharacteristic = characteristic; mTag = tag; mContext = context; this.activity =activity; final BluetoothManager bluetoothManager = (BluetoothManager) mContext.getSystemService(Context.BLUETOOTH_SERVICE); mBluetoothAdapter = bluetoothManager.getAdapter(); } final private static String TAG = "ReadCharacteristic"; private Object mTag; private String mMacAddress; private UUID mService; private UUID mCharacteristic; private byte[] mValue; private Activity activity; private BluetoothAdapter mBluetoothAdapter; private Context mContext; private int retry = 5; public String getMacAddress() { return mMacAddress; } public UUID getService() { return mService; } public UUID getCharacteristic() { return mCharacteristic; } public byte[] getValue() { return mValue; } public void onRead() { Log.w(TAG, "onRead: " + getDataHex(getValue())); } public void onError() { Log.w(TAG, "onError"); } public void readCharacteristic(){ if (retry == 0) { onError(); return; } retry--; final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(getMacAddress()); if (device != null) { Log.w(TAG, "Starting Read [" + getService() + "|" + getCharacteristic() + "]"); final ReadCharacteristic rc = ReadCharacteristic.this; device.connectGatt(mContext, false, rc); } } @Override public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { Log.w(TAG,"onConnectionStateChange [" + status + "|" + newState + "]"); if ((newState == 2)&&(status ==0)) { gatt.discoverServices(); } else{ Log.w(TAG, "[" + status + "]"); // gatt.disconnect(); gatt.close(); try { Thread.sleep(2000); } catch(Exception e) { } readCharacteristic(); } } @Override public void onServicesDiscovered(BluetoothGatt gatt, int status) { Log.w(TAG,"onServicesDiscovered [" + status + "]"); BluetoothGattService bgs = gatt.getService(getService()); if (bgs != null) { BluetoothGattCharacteristic bgc = bgs.getCharacteristic(getCharacteristic()); gatt.readCharacteristic(bgc); } } @Override public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { Log.w(TAG,"onCharacteristicRead [" + status + "]"); if (status == BluetoothGatt.GATT_SUCCESS) { mValue = characteristic.getValue(); Log.w(TAG,"onCharacteristicRead [" + mValue + "]"); gatt.disconnect(); gatt.close(); onRead(); } else { gatt.disconnect(); gatt.close(); } } } 

Este método actual funciona perfectamente bien para los dispositivos que ejecutan KitKat y siguientes. Pero cuando ejecuto la misma función en Lollipop, emite un pitido el dispositivo un par de veces y luego deja de funcionar. Desde entonces en salas, cuando intento conectar, dice que el dispositivo está desconectado y me da un código de error de 257 en el método OnConnectionStateChanged.

También consigo este error cuando llamo este método –

 04-20 14:14:23.503 12329-12384/com.webble.xy W/BluetoothGatt﹕ Unhandled exception in callback java.lang.NullPointerException: Attempt to invoke virtual method 'void android.bluetooth.BluetoothGattCallback.onConnectionStateChange(android.bluetooth.BluetoothGatt, int, int)' on a null object reference at android.bluetooth.BluetoothGatt$1.onClientConnectionState(BluetoothGatt.java:181) at android.bluetooth.IBluetoothGattCallback$Stub.onTransact(IBluetoothGattCallback.java:70) at android.os.Binder.execTransact(Binder.java:446) 

¿Hay alguien que haya enfrentado el mismo problema? Nunca he encontrado el objeto para ser nulo cuando he tratado de depurar.

El problema se ha informado a Google como problema 183108: NullPointerException en BluetoothGatt.java al desconectar y cerrar .

Una solución es llamar a disconnect() cuando desee cerrar la conexión BLE y, a continuación, llame a close() en la onConnectionStateChange llamada onConnectionStateChange :

  public void shutdown() { try { mBluetoothGatt.disconnect(); } catch (Exception e) { Log.d(TAG, "disconnect ignoring: " + e); } } private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() { @Override public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { if (newState == BluetoothProfile.STATE_CONNECTED) { } else if (newState == BluetoothProfile.STATE_DISCONNECTED) { try { gatt.close(); } catch (Exception e) { Log.d(TAG, "close ignoring: " + e); } } } 

Aquí está mi código fuente completo (una clase que hace el escaneo normal, el escaneo dirigido y el descubrimiento de servicios):

 public class BleObject { public static final String ACTION_BLUETOOTH_ENABLED = "action.bluetooth.enabled"; public static final String ACTION_BLUETOOTH_DISABLED = "action.bluetooth.disabled"; public static final String ACTION_DEVICE_FOUND = "action.device.found"; public static final String ACTION_DEVICE_BONDED = "action.device.bonded"; public static final String ACTION_DEVICE_CONNECTED = "action.device.connected"; public static final String ACTION_DEVICE_DISCONNECTED = "action.device.disconnected"; public static final String ACTION_POSITION_READ = "action.position.read"; public static final String EXTRA_BLUETOOTH_DEVICE = "extra.bluetooth.device"; public static final String EXTRA_BLUETOOTH_RSSI = "extra.bluetooth.rssi"; private Context mContext; private IntentFilter mIntentFilter; private LocalBroadcastManager mBroadcastManager; private BluetoothAdapter mBluetoothAdapter; private BluetoothGatt mBluetoothGatt; private BluetoothLeScanner mScanner; private ScanSettings mSettings; private List<ScanFilter> mScanFilters; private Handler mConnectHandler; public BleObject(Context context) { mContext = context; if (!context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) { Log.d(TAG, "BLE not supported"); return; } BluetoothManager bluetoothManager = (BluetoothManager) context.getSystemService(Context.BLUETOOTH_SERVICE); mBluetoothAdapter = bluetoothManager.getAdapter(); if (mBluetoothAdapter == null) { Log.d(TAG, "BLE not accessible"); return; } mIntentFilter = new IntentFilter(); mIntentFilter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED); mIntentFilter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED); mSettings = new ScanSettings.Builder().setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY).build(); mScanFilters = new ArrayList<ScanFilter>(); mConnectHandler = new Handler(); mBroadcastManager = LocalBroadcastManager.getInstance(context); } public boolean isEnabled() { return (mBluetoothAdapter != null && mBluetoothAdapter.isEnabled()); } private ScanCallback mScanCallback = new ScanCallback() { @Override public void onScanResult(int callbackType, ScanResult result) { processResult(result); } @Override public void onBatchScanResults(List<ScanResult> results) { for (ScanResult result: results) { processResult(result); } } private void processResult(ScanResult result) { if (result == null) return; BluetoothDevice device = result.getDevice(); if (device == null) return; Intent i = new Intent(Utils.ACTION_DEVICE_FOUND); i.putExtra(Utils.EXTRA_BLUETOOTH_DEVICE, device); i.putExtra(Utils.EXTRA_BLUETOOTH_RSSI, result.getRssi()); mBroadcastManager.sendBroadcast(i); } }; private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() { @Override public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { if (newState == BluetoothProfile.STATE_CONNECTED) { if (gatt == null) return; BluetoothDevice device = gatt.getDevice(); if (device == null) return; Log.d(TAG, "BluetoothProfile.STATE_CONNECTED: " + device); Intent i = new Intent(Utils.ACTION_DEVICE_CONNECTED); i.putExtra(Utils.EXTRA_BLUETOOTH_DEVICE, device); mBroadcastManager.sendBroadcast(i); } else if (newState == BluetoothProfile.STATE_DISCONNECTED) { Log.d(TAG, "BluetoothProfile.STATE_DISCONNECTED"); Intent i = new Intent(Utils.ACTION_DEVICE_DISCONNECTED); mBroadcastManager.sendBroadcast(i); // Issue 183108: https://code.google.com/p/android/issues/detail?id=183108 try { gatt.close(); } catch (Exception e) { Log.d(TAG, "close ignoring: " + e); } } } @Override public void onServicesDiscovered(BluetoothGatt gatt, int status) { if (gatt == null) return; for (BluetoothGattService service: gatt.getServices()) { Log.d(TAG, "service: " + service.getUuid()); for (BluetoothGattCharacteristic chr: service.getCharacteristics()) { Log.d(TAG, "char: " + chr.getUuid()); } } } }; private final BroadcastReceiver mReceiver = new BroadcastReceiver() { public void onReceive(Context context, Intent intent) { final String action = intent.getAction(); if (BluetoothAdapter.ACTION_STATE_CHANGED.equals(action)) { final int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR); switch (state) { case BluetoothAdapter.STATE_TURNING_OFF: { Log.d(TAG, "BluetoothAdapter.STATE_TURNING_OFF"); break; } case BluetoothAdapter.STATE_OFF: { Log.d(TAG, "BluetoothAdapter.STATE_OFF"); Intent i = new Intent(Utils.ACTION_BLUETOOTH_DISABLED); mBroadcastManager.sendBroadcast(i); break; } case BluetoothAdapter.STATE_TURNING_ON: { Log.d(TAG, "BluetoothAdapter.STATE_TURNING_ON"); break; } case BluetoothAdapter.STATE_ON: { Log.d(TAG, "BluetoothAdapter.STATE_ON"); Intent i = new Intent(Utils.ACTION_BLUETOOTH_ENABLED); mBroadcastManager.sendBroadcast(i); break; } } } else if (BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(action)) { final int state = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.ERROR); final int prevState = intent.getIntExtra(BluetoothDevice.EXTRA_PREVIOUS_BOND_STATE, BluetoothDevice.ERROR); if (state == BluetoothDevice.BOND_BONDED && prevState == BluetoothDevice.BOND_BONDING) { if (mBluetoothGatt != null) { BluetoothDevice device = mBluetoothGatt.getDevice(); if (device == null) return; Intent i = new Intent(Utils.ACTION_DEVICE_BONDED); i.putExtra(Utils.EXTRA_BLUETOOTH_DEVICE, device); mBroadcastManager.sendBroadcast(i); } } } } }; // scan for all BLE devices nearby public void startScanning() { Log.d(TAG, "startScanning"); mScanFilters.clear(); // create the scanner here, rather than in init() - // because otherwise app crashes when Bluetooth is switched on mScanner = mBluetoothAdapter.getBluetoothLeScanner(); mScanner.startScan(mScanFilters, mSettings, mScanCallback); } // scan for a certain BLE device and after delay public void startScanning(final String address) { Log.d(TAG, "startScanning for " + address); mScanFilters.clear(); mScanFilters.add(new ScanFilter.Builder().setDeviceAddress(address).build()); // create the scanner here, rather than in init() - // because otherwise app crashes when Bluetooth is switched on mScanner = mBluetoothAdapter.getBluetoothLeScanner(); mScanner.startScan(mScanFilters, mSettings, mScanCallback); } public void stopScanning() { Log.d(TAG, "stopScanning"); if (mScanner != null) { mScanner.stopScan(mScanCallback); mScanner = null; } mScanFilters.clear(); } public void connect(final BluetoothDevice device) { Log.d(TAG, "connect: " + device.getAddress() + ", mBluetoothGatt: " + mBluetoothGatt); mConnectHandler.post(new Runnable() { @Override public void run() { setPin(device, Utils.PIN); mBluetoothGatt = device.connectGatt(mContext, true, mGattCallback); } }); } private void setPin(BluetoothDevice device, String pin) { if (device == null || pin == null || pin.length() < 4) return; try { device.setPin(pin.getBytes("UTF8")); } catch (Exception e) { Utils.logw("setPin ignoring: " + e); } } // called on successful device connection and will toggle reading coordinates public void discoverServices() { if (mBluetoothGatt != null) mBluetoothGatt.discoverServices(); } public boolean isBonded(BluetoothDevice device) { Set<BluetoothDevice> bondedDevices = mBluetoothAdapter.getBondedDevices(); if (bondedDevices == null || bondedDevices.size() == 0) return false; for (BluetoothDevice bondedDevice: bondedDevices) { Log.d(TAG, "isBonded bondedDevice: " + bondedDevice); if (bondedDevice.equals(device)) { Log.d(TAG, "Found bonded device: " + device); return true; } } return false; } public void startup() { try { mContext.registerReceiver(mReceiver, mIntentFilter); } catch (Exception e) { Log.d(TAG, "registerReceiver ignoring: " + e); } } public void shutdown() { Log.d(TAG, "BleObject shutdown"); try { mContext.unregisterReceiver(mReceiver); } catch (Exception e) { Log.d(TAG, "unregisterReceiver ignoring: " + e); } try { stopScanning(); } catch (Exception e) { Log.d(TAG, "stopScanning ignoring: " + e); } try { mBluetoothGatt.disconnect(); } catch (Exception e) { Log.d(TAG, "disconnect ignoring: " + e); } mConnectHandler.removeCallbacksAndMessages(null); } } 

En cada evento BLE difunde intenciones a través de LocalBroadcastManager .

Gracias por señalar el problema Shashank.

He mirado el ejemplo de Google y he seguido lo que recomiendan, funciona como un encanto con mi dispositivo.

Debe llamar a la función close () en su onUnbind y desconectar () cuando lo necesite, por ejemplo, cuando abandone su aplicación.

  • Android: perfil de baja energía del GATT de Bluetooth
  • BluetoothGattServer cancelConnection no cancela la conexión
  • BluetoothGatt: la negociación de nuevos MTU tiene éxito pero el nuevo tamaño no se puede utilizar (diferencia de 3 bytes)
  • Notificación de baja energía del bluetooth
  • StartLeScan con 128 UUIDs de bits no funciona en la implementación BLE de Android nativa
  • Bluetooth Bluetooth de bajo consumo de energía
  • BLE con Android 5.0: ¿Cómo conseguir que un dispositivo actúe como Central AND Server?
  • Android Cómo leer las propiedades de BLE Readable Writable Notifiable GATT Características
  • Android, ¿Cómo puedo hacer que el dispositivo BLE esté conectado a un dispositivo (vinculado)
  • ¿Por qué setCharactersticNotification () no habilita realmente las notificaciones?
  • Android BLE 4.3 onDescriptorWrite devuelve el estado 128 al habilitar la notificación de características
  • FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.