关于 android:BLuetooth Gatt 回调不适用于 Lollipop 的新 API

BLuetooth Gatt Callback not working with new API for Lollipop

我目前有一种方法可以写入 BLE 设备以发出哔哔声。我的蓝牙回调如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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();

我的 ReadCharacteristic 实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
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();
        }
    }


}

当前的方法非常适合运行 KitKat 及更低版本的设备。但是当我在 Lollipop 上运行相同的功能时,它会发出几次哔哔声,然后停止工作。从那时起,每当我尝试连接时,它都会说设备已断开连接,并在 OnConnectionStateChanged 方法中给我一个错误代码 257。

每当我调用此方法时,我也会收到此错误 -

1
2
3
4
5
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)

有没有人遇到过同样的问题?当我尝试调试时,我从未遇到过对象为空。


该问题已作为问题 183108:断开和关闭时 BluetoothGatt.java 中的 NullPointerException 报告给 Google。

一种解决方法是在您想要关闭 BLE 连接时调用 disconnect() - 然后只在 onConnectionStateChange 回调中调用 close()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
  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);
          }
        }
      }

这是我的完整源代码(一个进行正常扫描、定向扫描和发现服务的类):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
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);
  }
}

在每个 BLE 事件中,它通过 LocalBroadcastManager.

广播意图


感谢Shashank指出问题。

我查看了 Google 示例并遵循了他们的建议,它对我的??设备就像一个魅力。

您应该在 onUnbind 中调用 close() 函数,并在需要时调用 disconnect() 函数,例如当您退出应用程序时。