一、相关API详解(API>=18)
- BluetoothAdapter
本地蓝牙的适配器,蓝牙交互入口,使用已知的MAC地址来实例化一个BluetoothDevice对象,支持Android4.3(API18)及以上版本 - BuletoothDevice
代表一个远程的蓝牙设备,通过这个类可以查询远程设备的物理地址, 名称, 连接状态等信息;
对象获取途径 :
- 调用BluetoothAdapter的getRemoteDevice(address)方法获取物理地址对应的该类对象;
- 调用BluetoothAdapter的getBoundedDevices()方法, 可以获取已经配对的蓝牙设备集合;
二、需要权限
- android.permission.BLUETOOTH : 允许程序连接到已配对的蓝牙设备, 请求连接/接收连接/传输数据 需要改权限,主要用于对配对后进行操作;
- android.permission.BLUETOOTH_ADMIN : 允许程序发现和配对蓝牙设备, 该权限用来管理蓝牙设备, 有了这个权限, 应用才能使用本机的蓝牙设备, 主要用于对配对前的操作;
- android.permission.ACCESS_FINE_LOCATION:获取精确位置 ,Android6.0以上需动态获取该权限。
三、蓝牙连接
- 使用蓝牙API完成建立蓝牙连接的必要四步:
- 开启蓝牙;
- 查找附近已配对或可用的设备;
- 连接设备;
- 设备间数据交换。
1、 开启蓝牙
判断设备是否支持BLE
getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE);
开启蓝牙
BluetoothManager bluetoothManager =BluetoothManager)getSystemService(Context.BLUETOOTH_SERVICE);
bluetoothAdapter = bluetoothManager.getAdapter();
bluetoothAdapter.enable();
2、查找附近已配对或可用的设备
谷歌在4.3之后发布了低功耗蓝牙(BLE)的API,在安卓5.0之后又引入了新的API,原来的API已经被废弃。在新的系统里采用旧API开发的APP仍可使用,但采用新API开发的APP只能在LOLLIPOP即安卓5.0及其以后的版本使用。
扫描设备:
API<21
if (bluetoothAdapter.isEnabled()) { bluetoothAdapter.startLeScan(leScanCallback); } else { Toast.makeText(this, "请开启蓝牙!", Toast.LENGTH_SHORT).show(); bluetoothAdapter.enable(); }
leScanCallback是开启扫描后的回调函数,所有扫描出的设备信息(设备名称name、设备mac地址 address 、设备信号强度rssi)全部包含在里面:
/** * 搜索蓝牙 API 21以下版本 */ private BluetoothAdapter.LeScanCallback leScanCallback = new BluetoothAdapter.LeScanCallback() { @Override public void onLeScan(final BluetoothDevice device, final int rssi, final byte[] scanRecord) { runOnMainThread(new Runnable() { @Override public void run() { BluetoothBean bluetoothBean = new BluetoothBean(); bluetoothBean.setName(device.getName()); bluetoothBean.setAddress(device.getAddress()); bluetoothBean.setRssi(rssi + ""); EventBus.getDefault().post(new ScanEvent(bluetoothBean)); } }); } };
当扫描到对应设备后可使用bluetoothAdapter.stopLeScan()方法停止搜索。
API>=21
if(bluetoothAdapter.isEnabled()) { bluetoothLeScanner=bluetoothAdapter.getBluetoothLeScanner(); bluetoothLeScanner.startScan(scanCallback); }else{ Toast.makeText(this,"请开启蓝牙!", Toast.LENGTH_SHORT).show(); bluetoothAdapter.enable(); }
scanCallback同leScanCallback
/** * 搜索蓝牙 API 21(包含)以上版本 */ private ScanCallback scanCallback = new ScanCallback() { @Override public void onScanResult(int callbackType, final ScanResult result) { super.onScanResult(callbackType, result); runOnMainThread(new Runnable() { @Override public void run() { BluetoothBean bluetoothBean = new BluetoothBean(); bluetoothBean.setName(result.getDevice().getName()); bluetoothBean.setAddress(result.getDevice().getAddress()); bluetoothBean.setRssi(result.getRssi() + ""); EventBus.getDefault().post(new ScanEvent(bluetoothBean)); } }); } @Override public void onBatchScanResults(List<ScanResult> results) { super.onBatchScanResults(results); } @Override public void onScanFailed(int errorCode) { super.onScanFailed(errorCode); } };
其中onBatchScanResults为批量搜索结果,
当扫描到对应设备后可使用bluetoothLeScanner.stopScan()方法停止搜索。
3、 连接设备
使用 bluetoothAdapter.getRemoteDevice()方法获取BluetoothDevice,其中的address参数为需要连接的设备的MAC地址,该值在扫描回调中可以获取到 ,然后调用BluetoothDevice 的connectGatt方法连接设备:
private boolean connectDevice(String address) {
if (bluetoothAdapter == null || address == null) {
return false;
}
final BluetoothDevice device = bluetoothAdapter.getRemoteDevice(address);
if (device == null) {
return false;
}
if (bluetoothGatt != null) {
bluetoothGatt.close();
}
bluetoothGatt = device.connectGatt(this, true, gattCallback);
return true;
}
其中gattCallback为设备连接回调,获取连接状态、读写操作均在此回调方法中进行。
在BluetoothGattCallback回调中,onConnectionStateChange为设备连接状态的回调方法,当设备开始连接、连接成功、断开连接中、断开连接后都会调用次方法:
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
if (newState == BluetoothProfile.STATE_CONNECTED) {
Log.i("info", "bluetooth is connected");
runOnMainThread(new Runnable() {
@Override
public void run() {
EventBus.getDefault().post(new ConnectSuccessEvent("ConnectSuccess"));
}
});
bluetoothGatt.discoverServices();
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
Log.i("info", "bluetooth is disconnected");
runOnMainThread(new Runnable() {
@Override
public void run() {
EventBus.getDefault().post(new DisConnectedEvent("DisConnected"));
}
});
}
}
3、 设备间数据交换
当设备连接成功后,调用discoveryServices()发现服务,随后会进入onServicesDiscovered方法中,不调用则无法进入onServicesDiscovered方法,将无法获取到BluetoothGattService。
通过 bluetoothGatt.getServices()该方法可以获取到连接设备的所有服务(BluetoothGattService)的集合,通过遍历该集合可以获取到对应服务的特征BluetoothGattCharacteristic。
通过bluetoothGattCharacteristic.getProperties()可以获取可以获取当前特征的属性,该返回值为int类型:
@Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
List<BluetoothGattService> supportedGattServices = bluetoothGatt.getServices();
for (BluetoothGattService supportedGattService : supportedGattServices) {
for (BluetoothGattCharacteristic bluetoothGattCharacteristic : supportedGattService.getCharacteristics()) {
int properties = bluetoothGattCharacteristic.getProperties();
if (BluetoothGattCharacteristic.PROPERTY_NOTIFY == properties) {
//具备通知属性
UUID characteristicUuid = bluetoothGattCharacteristic.getUuid();
UUID gattServiceUuid = supportedGattService.getUuid();
}
}
}
}
当该值为BluetoothGattCharacteristic.PROPERTY_NOTIFY 则说明该特征具备通知属性。
当该值为BluetoothGattCharacteristic.PROPERTY_WRITE 则说明该值具备写入属性。
当该值为BluetoothGattCharacteristic.PROPERTY_READ 则说明该值具备读取属性。
此三种属性较为常用,当获取当对应的属性时,将serviceuuid和characteristicuuid保存起来即可。
例如当我们连接的蓝牙设备类似超市扫码枪,需要将扫描的条码传输到手机上时,此时则需要使用BluetoothGattCharacteristic特征的通知属性:
@Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
BluetoothGattService service = bluetoothGatt.getService(UUID.fromString(UUID_SERVICE));
BluetoothGattCharacteristic characteristic = service.getCharacteristic(UUID.fromString(UUID_NOTIFY));
//收到蓝牙模块的数据后会触发onCharacteristicChanged方法
bluetoothGatt.setCharacteristicNotification(characteristic, true);
}
当收到蓝牙模块的数据后会触发onCharacteristicChanged方法,在onCharacteristicChanged方法中则可以获取到扫码枪返回的数据:
@Override
public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
final byte[] data = characteristic.getValue();
Log.e("info", "读取成功" + new String(data));
runOnMainThread(new Runnable() {
@Override
public void run() {
EventBus.getDefault().post(new BluetoothScanResultEvent(new String(data)));
}
});
}
该返回数据可能是原样数据,也可能返回的是16进制数据,具体根据硬件供应商的标准转换。
需要往设备里面写入数据时,将BluetoothGattCharacteristic换成具有写入属性BluetoothGattCharacteristic的,写入数据即可:
//设置数据内容
characteristic.setValue("send data->");
//往蓝牙模块写入数据
bluetoothGatt.writeCharacteristic(characteristic);
写入成功后会调用onCharacteristicWrite方法。
最后,附上该Demo的传送门:https://github.com/yfwang0810/Bluetooth
如有不足之处,欢迎大佬们指点一二。