BLuetooth Gatt Callback not working with new API for Lollipop

不问归期 提交于 2019-12-04 00:17:30

The problem has been reported to Google as Issue 183108: NullPointerException in BluetoothGatt.java when disconnecting and closing.

A workaround is to call disconnect() when you want to close BLE connection - and then only call close() in the onConnectionStateChange callback:

  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);
          }
        }
      }

Here is my full source code (a class doing normal scan, directed scan - and discovering services):

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);
  }
}

On each BLE event it broadcasts intents via LocalBroadcastManager.

Thanks for pointing the problem Shashank.

I have looked at the Google example and followed what they recommend, it works like a charm with my device.

You should call the close() function in your onUnbind and the disconnect() when you need to, for example when you quit your application.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!