前言:
从Android 6.0之后,想要扫描低功率蓝牙设备除了蓝牙权限还需要拥有访问设备位置的ACCESS_COARSE_LOCATION和ACCESS_FINE_LOCATION权限,另外,使用6.0的API可以不注册广播!以下方法和接口均为自己封装,需要拷贝对应的文件。
效果:
示例:
一、打开手机里的蓝牙
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.le.BluetoothLeScanner;
import android.bluetooth.le.ScanCallback;
import android.bluetooth.le.ScanFilter;
import android.bluetooth.le.ScanResult;
import android.bluetooth.le.ScanSettings;
BluetoothAdapter表示本地蓝牙适配器,通过它来调起手机里面的蓝牙开关,扫描、停止扫描里都需要对它做判断,判断手机里的蓝牙开关处于打开状态才能开始、停止扫描,不然异常直接导致闪退。
BluetoothDevice表示扫描到的远程蓝牙设备,该类不可继承。提它是因为它里面包含了设备名称、地址、连接状态、设备类型等,但偏偏没有rssi(信号强度)这个字段,如果你刚好有信号强度相关的需求就有点尴尬了。
BluetoothLeScanner是6.0API中的扫描器,它提供了对Bluetooth BLE设备扫描相关的操作,通过BluetoothAdapter的getBluetoothLeScanner()得到实例。使用它的时候记得判断适配器是否打开,原因上面说了。
ScanCallback是扫描BLE设备的回调,在获取设备的时候记得去重,不然设备列表里一堆重复的设备。
ScanFilter为BLE设备扫描结果的过滤条件,目前支持:UUID、设备名、设备的mac地址、服务数据和制造商数据。
ScanResult即为扫描器扫描出来的结果,BluetoothDevice就是从这里得到的。同时还有信号强度、设备类型、发送功率等属性。
ScanSettings是扫描时的参数设置,比如设置速度优先或者省电优先又或者是特殊的扫描模式。
关于ScanResult和ScanSettings我不推荐使用,而是自己手写实现的过滤!原因:API提供的过滤太“死板”,比如只扫出名字叫“A1B2C3”的设备那么就必须写上全限定名,错/漏一个字都不行!自己实现的话就可以稍微灵活点,但是效率赶人家的API还是差点儿,大家根据业务自行取舍吧。
/**
* 检查蓝牙适配器是否打开
* 在开始扫描和取消扫描的都时候都需要判断适配器的状态以及是否获取到扫描器,否则将抛出异常IllegalStateException: BT Adapter is not turned
* ON.
*
* @return
*/
private static boolean IsBluetoothAvailable() {
return (_bluetoothLeScanner != null && _bluetoothAdapter != null && _bluetoothAdapter.isEnabled() && _bluetoothAdapter.getState() == BluetoothAdapter.STATE_ON);
}
/**
* 开始扫描BLE设备
*
* @return 蓝牙适配器是否打开、扫描器是否已获取到
*/
public static int StartScanLe() {
if (IsBluetoothAvailable()) {
_bluetoothLeScanner.stopScan(_leScanCallback);
// _bluetoothLeScanner.startScan(_scanFilterList, _scanSettings, _leScanCallback);
_bluetoothLeScanner.startScan(_leScanCallback);
return 1;
} else {
return -1;
}
}
二、扫描/取消
很简单,使用扫描器的startScan()和stopScan()就可以了,开始前先判断是否已经有扫描,我这里没有判断直接先停止再开始。
/**
* 停止扫描BLE设备
*
* @return 蓝牙适配器是否打开、扫描器是否已获取到
*/
public static int StopScanLe() {
if (IsBluetoothAvailable()) {
_bluetoothLeScanner.stopScan(_leScanCallback);
return 1;
} else {
return -1;
}
}
三、获取扫描结果
里面的MyBluetoothDevice为自定义的,为了方便按信号强度排序以及手动实现按信号强度过滤,就将BluetoothDevice和rssi封装进去了。如果你不需要信号强度的话直接用BluetoothDevice就够了。
/**
* 扫描到BLE设备的回调
*/
private static final ScanCallback _leScanCallback = new ScanCallback() {
@Override
public void onScanResult(int callbackType, ScanResult result) {
super.onScanResult(callbackType, result);
_scanListener.OnScanLeResult(result);
}
@Override
public void onBatchScanResults(List<ScanResult> results) {
super.onBatchScanResults(results);
}
/**
* 扫描失败
*
* errorCode=1:已启动具有相同设置的BLE扫描
* errorCode=2:应用未注册
* errorCode=3:内部错误
* errorCode=4:设备不支持低功耗蓝牙
*
* @param errorCode
*/
@Override
public void onScanFailed(int errorCode) {
super.onScanFailed(errorCode);
}
};
四、连接BLE设备
之前的API有人说在连接成功后不停止扫描再次扫描会发生阻塞导致扫描不到BLE设备,关于这点6.0里暂时没有这种情况,连接BLE设备成功后停不停止扫描看你业务需要。另外,在处理业务的过程中要保证你的设备和适配器对象不被销毁,之所以说这个是因为有时候因为业务需要要进行页面跳转,跳转过程中要注意生命周期。
import java.util.UUID;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCallback;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattDescriptor;
import android.bluetooth.BluetoothGattService;
UUID不管是传统的蓝牙(BT)还是低功耗蓝牙(BLE)在连接的时候都需要这个通用唯一标识符,它用于标识应用程序的蓝牙服务。比如发现、读、写服务都有它们对应的UUID,这些UUID由硬件工程师或者开发文档里提供,要是都没有只能通过第三方工具查看了,这里推荐nRF Connect这款工具。
BluetoothGatt它定义了Service(服务)和Characteristic(特征),是一个ATT服务框架,官方释义:蓝牙GATT配置文件的公共API吧啦吧啦吧啦……反正就是它提供了蓝牙的通信功能,发现BLE设备和读写数据都要通过它获取服务,再通过UUID获取到特征才能实现,并把相应的结果返回到BluetoothGattCallback。
BluetoothGattCallback设备连接的过程、结果都在这个类里。
BluetoothGattCharacteristic简称特征,用于构造BluetoothGattService。
BluetoothGattDescriptor对特征的描述和控制特征的行为,比如订阅读取通知时使用它的ENABLE_NOTIFICATION_VALUE属性来启用设备通知。
BluetoothGattService代表蓝牙GATT的服务,一个服Service包含多BluetoothGattCharacteristic。
发现设备并根据UUID找到对应的特征后才算是真正与BLE设备建立了连接,现在的BLE设备基本都是以notify的方式返回数据,所以需要在找到读取的特征那里订阅读取通知,不然收不到数据。
/**
* 连接设备
*
* @param map
* @param device
*/
public static void ConnectDevice(BluetoothDevice device, Map<String, UUID> map) {
SERVICE_UUID = map.get("SERVICE_UUID");
WRITE_UUID = map.get("WRITE_UUID");
READ_UUID = map.get("READ_UUID");
CONFIG_UUID = map.get("CONFIG_UUID");
_bluetoothGatt = device.connectGatt(_context, false, _bluetoothGattCallback);
//设备正在连接中,如果连接成功会执行回调函数discoverServices()
_connectListener.OnConnecting();
}
六、读写数据
BLE传输每包最多20字节,超过需要分包处理。读取不到数据参考我这篇博客:Android6.0的BLE以Notify的方式接收数据_谁把我名字用了!的博客-CSDN博客_android ble接收数据
/**
* 连接状态以及读写状态的回调
*/
public static BluetoothGattCallback _bluetoothGattCallback = new BluetoothGattCallback() {
/**
* 连接状态改变时回调
* @param gatt
* @param status Gatt的状态
* @param newState 连接状态
*/
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
if (status == BluetoothGatt.GATT_SUCCESS && newState == BluetoothGatt.STATE_CONNECTED) {
//启用发现服务
_bluetoothGatt.discoverServices();
}
if (newState == BluetoothGatt.STATE_DISCONNECTED) {
if (_bluetoothGatt != null)
_bluetoothGatt.close();
_connectListener.OnDisConnected();
}
}
/**
* 发现设备(真正建立连接)后回调
* @param gatt
* @param status
*/
@Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
//通过UUID找到服务,直到这里才是真正建立了可通信的连接
_bluetoothGattService = _bluetoothGatt.getService(SERVICE_UUID);
if (_bluetoothGattService != null) {
//读写数据的服务和特征
_bluetoothGattCharacteristic_write = _bluetoothGattService.getCharacteristic(WRITE_UUID);
_bluetoothGattCharacteristic_read = _bluetoothGattService.getCharacteristic(READ_UUID);
if (_bluetoothGattCharacteristic_read != null) {
//订阅读取通知
_bluetoothGatt.setCharacteristicNotification(_bluetoothGattCharacteristic_read, true);
BluetoothGattDescriptor descriptor = _bluetoothGattCharacteristic_read.getDescriptor(CONFIG_UUID);
descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
_bluetoothGatt.writeDescriptor(descriptor);
_connectListener.OnConnected();
}
}
}
/**
* 写入成功后回调
*
* @param gatt
* @param characteristic
* @param status
*/
@Override
public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
byte[] byteArray = characteristic.getValue();
_messageListener.OnWriteSuccess(byteArray);
}
/**
* 收到硬件返回的数据时回调,如果是Notify的方式
* @param gatt
* @param characteristic
*/
@Override
public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
byte[] byteArray = characteristic.getValue();
_messageListener.OnReadSuccess(byteArray);
}
};
发送的话也比较简单,如果想知道数据是否发送成功可以在BluetoothGattCallback类的onCharacteristicWrite()里看到。
/**
* 发送数据
*
* @param data
* @return
*/
public static int SendComm(String data) {
if (_bluetoothGatt != null && _bluetoothGattCharacteristic_write != null && data != null && !data.equals("")) {
byte[] byteArray = MyConvertUtil.HexStrToByteArray(data);
_bluetoothGattCharacteristic_write.setValue(byteArray);
_bluetoothGatt.writeCharacteristic(_bluetoothGattCharacteristic_write);
return 1;
} else {
return -1;
}
}
……差不多就先写这么多吧,代码涉及到业务,全部贴上来也没什么用,关注MyBleUtil这一个类就可以了。
Gitee地址:LiWei/BLE
如果碰巧对你有帮助还望给个star,谢谢~