在 Android 低功耗蓝牙上读取 GATT 属性的问题
Posted
技术标签:
【中文标题】在 Android 低功耗蓝牙上读取 GATT 属性的问题【英文标题】:Issue with reading GATT Attribute on Android Bluetooth Low Energy 【发布时间】:2015-10-20 07:44:41 【问题描述】:我正在尝试从 GATT 服务器读取一个值并将其显示在我的屏幕上。我已经阅读了各种教程并试图找到各种方法来解决这个问题,但我在某个地方磕磕绊绊!
我们将不胜感激。
布局 XML:
<?xml version="1.0" encoding="utf-8"?>
<TextView android:text="Key : " android:layout_
android:layout_
android:id="@+id/textView2" />
<TextView
android:layout_
android:layout_
android:textAppearance="?android:attr/textAppearanceMedium"
android:text="Medium Text"
android:id="@+id/pubkey"
android:layout_alignTop="@+id/textView2"
android:layout_toEndOf="@+id/textView2"
android:layout_marginStart="30dp" />
</RelativeLayout>
MainActivity.java
public class MainActivity extends Activity implements BluetoothAdapter.LeScanCallback
private static final String TAG = "BluetoothGattActivity";
private static final String DEVICE_NAME = "PUNE\u0005\u0012\b";
/*
SECURITY SERVICE
*/
private static final UUID SECURITY_SERVICE = UUID.fromString("3E099914-293F-11E4-93BD-AFD0FE6D1DFD");
private static final UUID SECURITY_PUBLICKEY = UUID.fromString("3E099915-293F-11E4-93BD-AFD0FE6D1DFD");
private BluetoothAdapter mBluetoothAdapter;
private SparseArray<BluetoothDevice> mDevices;
private BluetoothGatt mConnectedGatt;
private TextView publicKEY;
private ProgressDialog mProgress;
@Override
protected void onCreate(Bundle savedInstanceState)
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
setContentView(R.layout.activity_main);
setProgressBarIndeterminate(true);
publicKEY = (TextView) findViewById(R.id.pubkey);
BluetoothManager manager = (BluetoothManager) getSystemService(BLUETOOTH_SERVICE);
mBluetoothAdapter = manager.getAdapter();
mDevices = new SparseArray<BluetoothDevice>();
mProgress = new ProgressDialog(this);
mProgress.setIndeterminate(true);
mProgress.setCancelable(false);
@Override
protected void onResume()
super.onResume();
/*
* We need to enforce that Bluetooth is first enabled, and take the
* user to settings to enable it if they have not done so.
*/
if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled())
//Bluetooth is disabled
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivity(enableBtIntent);
finish();
return;
/*
* Check for Bluetooth LE Support. In production, our manifest entry will keep this
* from installing on these devices, but this will allow test devices or other
* sideloads to report whether or not the feature exists.
*/
if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE))
Toast.makeText(this, "No LE Support.", Toast.LENGTH_SHORT).show();
finish();
return;
clearDisplayValues();
@Override
protected void onPause()
super.onPause();
//Make sure dialog is hidden
mProgress.dismiss();
//Cancel any scans in progress
mHandler.removeCallbacks(mStopRunnable);
mHandler.removeCallbacks(mStartRunnable);
mBluetoothAdapter.stopLeScan(this);
@Override
protected void onStop()
super.onStop();
//Disconnect from any active tag connection
if (mConnectedGatt != null)
mConnectedGatt.disconnect();
mConnectedGatt = null;
@Override
public boolean onCreateOptionsMenu(Menu menu)
// Add the "scan" option to the menu
getMenuInflater().inflate(R.menu.main, menu);
//Add any device elements we've discovered to the overflow menu
for (int i=0; i < mDevices.size(); i++)
BluetoothDevice device = mDevices.valueAt(i);
menu.add(0, mDevices.keyAt(i), 0, device.getName());
return true;
@Override
public boolean onOptionsItemSelected(MenuItem item)
switch (item.getItemId())
case R.id.action_scan:
mDevices.clear();
startScan();
return true;
default:
//Obtain the discovered device to connect with
BluetoothDevice device = mDevices.get(item.getItemId());
Log.i(TAG, "Connecting to "+device.getName());
/*
* Make a connection with the device using the special LE-specific
* connectGatt() method, passing in a callback for GATT events
*/
mConnectedGatt = device.connectGatt(this, false, mGattCallback);
//Display progress UI
mHandler.sendMessage(Message.obtain(null, MSG_PROGRESS, "Connecting to "+device.getName()+"..."));
return super.onOptionsItemSelected(item);
private void clearDisplayValues()
publicKEY.setText("---");
private Runnable mStopRunnable = new Runnable()
@Override
public void run()
stopScan();
;
private Runnable mStartRunnable = new Runnable()
@Override
public void run()
startScan();
;
private void startScan()
mBluetoothAdapter.startLeScan(this);
setProgressBarIndeterminateVisibility(true);
mHandler.postDelayed(mStopRunnable, 2500);
private void stopScan()
mBluetoothAdapter.stopLeScan(this);
setProgressBarIndeterminateVisibility(false);
我所有的回调都在这里
/* BluetoothAdapter.LeScanCallback */
@Override
public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord)
Log.i(TAG, "New LE Device: " + device.getName() + " @ " + rssi);
/*
* We are looking for SensorTag devices only, so validate the name
* that each device reports before adding it to our collection
*/
if (DEVICE_NAME.equals(device.getName()))
mDevices.put(device.hashCode(), device);
//Update the overflow menu
invalidateOptionsMenu();
private BluetoothGattCallback mGattCallback = new BluetoothGattCallback()
private int mState = 0;
private void reset()
mState= 0;
private void advance()
mState++;
private void enabled(BluetoothGatt gatt)
BluetoothGattCharacteristic characteristic;
switch(mState)
case 0:
Log.d(TAG, "Enable Public Key");
characteristic = gatt.getService(SECURITY_SERVICE).getCharacteristic(SECURITY_PUBLICKEY);
break;
default:
mHandler.sendEmptyMessage(MSG_DISMISS);
Log.i(TAG, "All Sensors Enabled");
return;
private void readData(BluetoothGatt gatt)
BluetoothGattCharacteristic characteristic;
switch (mState)
case 0:
Log.d(TAG, "Reading Public Key");
characteristic = gatt.getService(SECURITY_SERVICE).getCharacteristic(SECURITY_PUBLICKEY);
break;
default:
mHandler.sendEmptyMessage(MSG_DISMISS);
Log.i(TAG, "All Sensors Enabled");
return;
gatt.readCharacteristic(characteristic);
private void enableNotif(BluetoothGatt gatt)
BluetoothGattCharacteristic characteristic;
switch (mState)
case 0:
Log.d(TAG, "Setting Notification Public Key");
characteristic = gatt.getService(SECURITY_SERVICE).getCharacteristic(SECURITY_PUBLICKEY);
break;
default:
mHandler.sendEmptyMessage(MSG_DISMISS);
Log.i(TAG, "All Sensors Enabled");
return;
gatt.setCharacteristicNotification(characteristic, true);
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState)
Log.d(TAG, "Connection State Change: " + status + " -> " + connectionState(newState));
if (status == BluetoothGatt.GATT_SUCCESS && newState == BluetoothProfile.STATE_CONNECTED)
/*
* Once successfully connected, we must next discover all the services on the
* device before we can read and write their characteristics.
*/
gatt.discoverServices();
mHandler.sendMessage(Message.obtain(null, MSG_PROGRESS, "Discovering Services..."));
else if (status == BluetoothGatt.GATT_SUCCESS && newState == BluetoothProfile.STATE_DISCONNECTED)
/*
* If at any point we disconnect, send a message to clear the weather values
* out of the UI
*/
mHandler.sendEmptyMessage(MSG_CLEAR);
else if (status != BluetoothGatt.GATT_SUCCESS)
/*
* If there is a failure at any stage, simply disconnect
*/
gatt.disconnect();
@Override
public void onServicesDiscovered(BluetoothGatt gatt, int status)
Log.d(TAG, "Services Discovered: " + status);
mHandler.sendMessage(Message.obtain(null, MSG_PROGRESS, "Enabling Public Key..."));
reset();
enabled(gatt);
@Override
public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic,int status )
if(SECURITY_PUBLICKEY.equals(characteristic.getUuid()))
mHandler.sendMessage(Message.obtain(null,MSG_PUBLICKEY,characteristic));
enableNotif(gatt);
readData(gatt);
@Override
public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status)
//After writing the enable flag, next we read the initial value
readData(gatt);
@Override
public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status)
Log.d(TAG, "Remote RSSI: " + rssi);
private String connectionState(int status)
switch (status)
case BluetoothProfile.STATE_CONNECTED:
return "Connected";
case BluetoothProfile.STATE_DISCONNECTED:
return "Disconnected";
case BluetoothProfile.STATE_CONNECTING:
return "Connecting";
case BluetoothProfile.STATE_DISCONNECTING:
return "Disconnecting";
default:
return String.valueOf(status);
;
private static final int MSG_PROGRESS = 201;
private static final int MSG_DISMISS = 202;
private static final int MSG_CLEAR = 301;
private static final int MSG_PUBLICKEY = 101;
private Handler mHandler = new Handler()
@Override
public void handleMessage(Message msg)
BluetoothGattCharacteristic characteristic;
switch (msg.what)
case MSG_PUBLICKEY:
characteristic = (BluetoothGattCharacteristic) msg.obj;
if (characteristic.getValue() == null)
Log.w(TAG, "Error obtaining humidity value");
return;
updatePubKey(characteristic);
break;
case MSG_PROGRESS:
mProgress.setMessage((String) msg.obj);
if (!mProgress.isShowing())
mProgress.show();
break;
case MSG_DISMISS:
mProgress.hide();
break;
case MSG_CLEAR:
clearDisplayValues();
break;
;
private void updatePubKey(BluetoothGattCharacteristic characteristic)
byte[] data = characteristic.getValue();
final StringBuilder stringBuilder = new StringBuilder(data.length);
for(byte byteChar : data)
stringBuilder.append(String.format("%02X ", byteChar));
publicKEY.setText(new String(data)+" "+stringBuilder.toString());
这是日志
Timeline: Timeline: Activity_idle id: android.os.BinderProxy@41cc5460 time:10882957
BluetoothAdapter: startLeScan(): null
BluetoothAdapter: onClientRegistered() - status=0 clientIf=5
BluetoothGattActivity: New LE Device: null @ -83
BluetoothAdapter: stopLeScan()
BluetoothAdapter: startLeScan(): null
BluetoothAdapter: onClientRegistered() - status=0 clientIf=5
BluetoothGattActivity: New LE Device: PUNE @ -38
BluetoothAdapter: stopLeScan()
BluetoothGattActivity: Connecting to PUNE
BluetoothGatt: connect() - device: B0:B4:48:BA:40:84, auto: false
BluetoothGatt: registerApp()
BluetoothGatt: registerApp() - UUID=0bdf35a7-d4d0-4048-be15-a6cb030626f5
BluetoothGatt: onClientRegistered() - status=0 clientIf=5
BluetoothGatt: onClientConnectionState() - status=0 clientIf=5 device=B0:B4:48:BA:40:84
BluetoothGattActivity: Connection State Change: 0 -> Connected
BluetoothGatt: discoverServices() - device: B0:B4:48:BA:40:84
我正在寻找的服务在这里..
onGetCharacteristic() - Device=B0:B4:48:BA:40:84 UUID=3e099919-293f-11e4-93bd-afd0fe6d1dfd
onGetCharacteristic() - Device=B0:B4:48:BA:40:84 UUID=3e099915-293f-11e4-93bd-afd0fe6d1dfd
onGetCharacteristic() - Device=B0:B4:48:BA:40:84 UUID=3e099916-293f-11e4-93bd-afd0fe6d1dfd
onGetCharacteristic() - Device=B0:B4:48:BA:40:84 UUID=3e099917-293f-11e4-93bd-afd0fe6d1dfd
这就是问题所在。
D/BluetoothGattActivity: Services Discovered: 0
D/BluetoothGattActivity: Enable Public Key
W/BluetoothGatt: Unhandled exception in callback
W/BluetoothGatt: java.lang.NullPointerException
W/BluetoothGatt: at gune.blegune.MainActivity$3.enabled(MainActivity.java:232)
W/BluetoothGatt: at gune.blegune.MainActivity$3.onServicesDiscovered(MainActivity.java:314)
W/BluetoothGatt: at android.bluetooth.BluetoothGatt$1.onSearchComplete(BluetoothGatt.java:295)
W/BluetoothGatt: at android.bluetooth.IBluetoothGattCallback$Stub.onTransact(IBluetoothGattCallback.java:215)
W/BluetoothGatt: at android.os.Binder.execTransact(Binder.java:404)
W/BluetoothGatt: at dalvik.system.NativeStart.run(Native Method)
D/BluetoothGatt: onClientConnectionState() - status=0 clientIf=5 device=B0:B4:48:BA:40:84
D/BluetoothGattActivity: Connection State Change: 0 -> Disconnected
D/BluetoothAdapter: stopLeScan()
请。一些帮助将不胜感激。非常感谢!
【问题讨论】:
您发现的服务列表是什么样的?您显示的日志表明特征 UUID 是有效的,但没有任何迹象表明您的服务 UUID 是正确的。 【参考方案1】:我猜这是抛出 NPE 的那一行:
characteristic = gatt.getService(SECURITY_SERVICE).getCharacteristic(SECURITY_PUBLICKEY);
所以要么
gatt 为空或
gatt.getService(SECURITY_SERVICE)
正在返回 null。
你能添加一些日志来找出它是什么吗?
我的猜测是第二种情况,在这种情况下我建议您在 BluetoothGatt 对象上调用 getServices() 并在结果列表中记录每个 BluetoothGattService 对象的 UUID,以仔细检查您是否拥有您认为应该拥有的服务 UUID。
getService 的规范显示“如果支持,则为 BluetoothGattService,如果远程设备未提供请求的服务,则为 null。”
【讨论】:
以上是关于在 Android 低功耗蓝牙上读取 GATT 属性的问题的主要内容,如果未能解决你的问题,请参考以下文章
Android BLE低功耗蓝牙开发极简系列(二)之读写操作