1.权限设置
<uses-feature android:name="android.hardware.location.gps" />
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
这里有人可能疑惑,使用蓝牙为啥要定位权限,其实蓝牙技术是可以实现定位的,要使用蓝牙必须要申请定位权限,android 9 之后动态权限申请
private void checkPermission() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
int fine_location = checkSelfPermission(android.Manifest.permission.ACCESS_FINE_LOCATION);
int cross_location = checkSelfPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION);
if (fine_location != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, new String[]{android.Manifest.permission.ACCESS_FINE_LOCATION}, 1);
} else {
finish();
return;
}
if (cross_location != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, new String[]{android.Manifest.permission.ACCESS_COARSE_LOCATION}, 1);
} else {
finish();
return;
}
}
}
动态申请,提示的是申请定位权限,这点个不要奇怪。
2.获取蓝牙设备管理器
private void getBleAdapter() {
if (mAdapter != null) {
return;
}
BluetoothManager bluetoothManager =
(BluetoothManager) context.getSystemService (Context.BLUETOOTH_SERVICE);
mAdapter = bluetoothManager.getAdapter ();
}
这里获取到的是一个蓝牙适配器BluetoothAdapter ,后续通过适配器调用蓝牙适配器。
3.设备搜索
mAdapter.startLeScan (leScanCallback);
BluetoothAdapter.LeScanCallback leScanCallback = new BluetoothAdapter.LeScanCallback () {
@Override
public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) {
//device.getName() 设备名称
//device.getAddress() mac地址
}
};
这个搜索方法在21 已经过期了后续使用
mAdapter.getBluetoothLeScanner ().startScan (new ScanCallback () {
@Override
public void onScanResult(int callbackType, ScanResult result) {
super.onScanResult (callbackType, result);
// BluetoothDevice device= result.getDevice (); 获取设备对象
// result.getRssi () 信号强度,根据这个可以计算出距离服务端蓝牙的距离
}
});
3.1 停止搜索
mAdapter.stopLeScan (leScanCallback);
同样这个方法21后过期了,可以使用新的api
mAdapter.getBluetoothLeScanner ().stopScan(// 搜索接口实例);
在搜到合适设备或者想停止设备搜索时可以调用这个api停止搜索。
- 注意:如果去连接设备时,同时还在搜索设备,建议先调用stopScan 停止搜索,然后再连接,这样可以提高效率。
4.设备连接
一般情况下,根据业务不同有可能我们会将搜索到的BluetoothDevice 保存下来。 再次使用时可以用
BluetoothDevice device = mAdapter.getRemoteDevice (deviceInfo.getAddress ());
如果device 是空的,说明设备可能已经关闭了,后边就没必要链接了。 如果是直接搜索到连接可以直接用搜到的BluetoothDevice 直接连接。
BluetoothGatt currentBindGatt = device.connectGatt (context, true, bluetoothGattCallback);
public BluetoothGatt connectGatt(Context context, boolean autoConnect,
BluetoothGattCallback callback) {
return (connectGatt(context, autoConnect, callback, TRANSPORT_AUTO));
}
- autoConnect 是否重连,如果true 异常断开后下次还会重新连接。
- bluetoothGattCallback 连接回掉状态
BluetoothGattCallback bluetoothGattCallback = new BluetoothGattCallback () {
@Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
// 这个是发现服务,在这里可以注册蓝牙的通知
LogS.d (TAG, "onServicesDiscovered()");
if (gatt == null || gatt.getDevice () == null) {
LogS.d (TAG, "Android GATT invalid!");
return;
}
UUID uuid = BeanBleUtil.parseUUID (BeanCSConstant.PROFILE_FORMAT_SERVER_UUID);
if (status == BluetoothGatt.GATT_SUCCESS) {
//查询支持的Server uuid
List<BluetoothGattService> services = gatt.getServices ();
if (services != null) {
for (BluetoothGattService service : services) {
LogS.d (TAG, "onServicesDiscovered:" + service.getUuid ());
}
}
//查询支持的Characteristic uuid
BluetoothGattService service = gatt.getService (uuid);
if (service == null) {
LogS.d (TAG, "BluetoothGattService == null");
return;
}
List<BluetoothGattCharacteristic> list = service.getCharacteristics ();
if (list != null) {
for (BluetoothGattCharacteristic bluetoothGattCharacteristic : list) {
//这里将需要监听的通知进行注册,如果不需要监听,可以不用管这里
List<UUID> listUUids = NoticeUUidList ();
if (bluetoothGattCharacteristic != null && listUUids.contains (bluetoothGattCharacteristic.getUuid ())) {
LogS.d (TAG, "readData support uuid:" + bluetoothGattCharacteristic.getUuid ().toString ());
enableNotification (true, gatt, bluetoothGattCharacteristic);
}
}
}
}
}
@Override
public void onConnectionStateChange(final BluetoothGatt gatt, int status, int newState) {
LogS.d (TAG, "onConnectionStateChange " + status + " ===> " + newState);
if (gatt == null) {
LogS.d (TAG, "gatt handler null");
return;
}
BaseDeviceInfo cInfo = deviceHashMap.get (gatt.getDevice ().getAddress ());
if (cInfo != null) {
LogS.d (TAG, "onConnectionStateChange 链接成功,开始赋值对象:" + gatt.getDevice ().getAddress ());
}
if (status != BluetoothGatt.GATT_SUCCESS) {
gatt.disconnect ();
gatt.close ();
LogS.d (TAG, "--GATT_SUCCESS");
currentBindGatt = null;
LogS.d (TAG, "GATT_SUCCESS 连接失败操作");
return;
}
}
if (newState == BluetoothGatt.STATE_CONNECTED) {
//连接成功
} else if (newState == BluetoothGatt.STATE_DISCONNECTED) {
//链接失败
}
}
//读取
@Override
public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
//数据的读取结果或者通知会通过这个函数返回
LogS.d (TAG, "onCharacteristicRead:" + gatt.getDevice ().getName () + " address:" + gatt.getDevice ().getAddress () + " status:" + status + "characteristic.getUuid ():" + characteristic.getUuid ());
if (status == BluetoothGatt.GATT_SUCCESS) {
byte[] value = characteristic.getValue ();
if (value != null) {
StringBuilder builder = new StringBuilder ();
for (byte data : value) {
//以十六进制的形式输出
LogS.d (TAG, "---------" + String.format ("%02X ", data));
builder.append (data);
}
LogS.d (TAG, "onCharacteristicRead:(uuid, value)= " + characteristic.getUuid ()
+ "," + builder.toString () + ")");
UUID uuid = characteristic.getUuid ();
if (uuid == null) {
return;
}
//解析 收到的value 值
}
}
}
//写入
@Override
public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
LogS.d (TAG, "onCharacteristicWrite:" + gatt.getDevice ().getName () + " address:" + gatt.getDevice ().getAddress () + " status:" + status);
if (status == BluetoothGatt.GATT_SUCCESS) {
byte[] value = characteristic.getValue ();
if (value != null) {
StringBuilder builder = new StringBuilder ();
for (byte data : value) {
builder.append (data);
}
LogS.d (TAG, "onCharacteristicWrite:" + builder.toString ());
}
} else if (status == BluetoothGatt.GATT_INVALID_ATTRIBUTE_LENGTH) {
LogS.d (TAG, "写入操作超出了属性的最大长度");
}
}
@Override
public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) {
super.onMtuChanged (gatt, mtu, status);
LogS.d (TAG, "----onMtuChanged");
}
//数据变化通知 接收通知消息
@Override
public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
LogS.d (TAG, "通知回调 onCharacteristicChanged:" + gatt.getDevice ().getName () + " address:" + gatt.getDevice ().getAddress () + "通知uuid:" + characteristic.getUuid ().toString ());
byte[] value = characteristic.getValue ();
if (value != null) {
StringBuilder builder = new StringBuilder ();
for (byte data : value) {
builder.append (data);
}
LogS.d (TAG, "接收通知消息:" + builder.toString ());
//处理通知消息
}
}
};
5.设备的重连
在很多时候,如果设备已经连接过了,但是中间断开,再次需要链接的时候是不需要再次调用connectGatt 连接方法的。只要调用重连方法就可以了。 重连的前提是之前已经连接过,获取到了BluetoothGatt 实例对象。
if (currentBindGatt.connect ()) {
//调用重连成功,即使设备不在范围内,等设备在范围内可自动连接成功
}
6.设备的断开与服务关闭
在需要断开设备时可以调用
currentBindGatt.disconnect ();
调用断开后,后续需要链接时,只需重新调用重连方法即可。 如果需要关闭Gatt 服务可以调用
currentBindGatt.close ();
调用close 方法后就不能再调用重连方法了,必须重新调用connectGatt ,
- 在实际的项目中可能会出现重连失败等操作,或者连接失败,这时需要调用下close方法,然后重新connectGatt 。
7.通知的注册与接收
ble支持通知类型的事件,当然,想要收到通知必须提前注册通知的uuid,有些uuid 是蓝牙的标准uuid ,有些则是厂商自定义的uuid .这个要适情况而定。
在Gatt 监听器的 onServicesDiscovered(BluetoothGatt gatt, int status)回掉函数中来注册。
- 查询支持的Server uuid
List services = gatt.getServices (); 这里获取到设备所有的uuid 特服务 - 查询支持的Characteristic uuid
BluetoothGattService service = gatt.getService (uuid); 这里的uuid 用的是181c 一般厂商都会给的,是服务端的uuid 。注意这个uuid 是UUID 类 需要通过: String code = “0000” + 181c+ “-0000-1000-8000-00805f9b34fb”; UUID uuid=UUID.fromString(code); 转为标准的UUID 类。 - 获取这个服务下的所有特征列表
List list = service.getCharacteristics (); 然后比对下是否是我们需要注册的那个uuid 的特征值。
if (list != null) {
for (BluetoothGattCharacteristic bluetoothGattCharacteristic : list) {
List<UUID> listUUids =NoticeUUidList ();
if (bluetoothGattCharacteristic != null && listUUids.contains (bluetoothGattCharacteristic.getUuid ())) {
LogS.d (TAG, "readData support uuid:" + bluetoothGattCharacteristic.getUuid ().toString ());
enableNotification (true, gatt, bluetoothGattCharacteristic);
}
}
}
NoticeUUidList 是一个UUID 的list 是我想要监听的通知的uuid ,然后通过BluetoothGattCharacteristic 特征值来注册通知
/**
* 设置是否开启通知
*/
private boolean enableNotification(boolean enable, BluetoothGatt
gatt, BluetoothGattCharacteristic characteristic) {
if (gatt == null || characteristic == null) return false;
boolean setNotification = false;
try {
String uuid =“2902”;
BluetoothGattDescriptor characteristicDescriptor = characteristic.getDescriptor (BeanBleUtil.parseUUID (uuid));
if (characteristicDescriptor != null) {
byte[] value = enable ? BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE : BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE;
characteristicDescriptor.setValue (value);
gatt.writeDescriptor (characteristicDescriptor);
}
setNotification = gatt.setCharacteristicNotification (characteristic, enable);
LogS.d (TAG, "设置通知" + characteristic.getUuid ().toString () + "结果:" + setNotification);
} catch (Exception e) {
LogS.d (TAG, "enableNotification 统一设置通知 失败" + e.getMessage () + "--uuid:" + characteristic.getUuid ().toString ());
e.printStackTrace ();
}
return setNotification;
}
2902 是蓝牙特征值的配置uuid ,如果没有配置这个uuid ,设置通知是没有效果的。 BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE 是用于启动通知的描述符,必须配置。 然后就是通过 gatt.setCharacteristicNotification 来设置通知的特征值,设置成功后,服务端数据变化发起的通知,客户端就可以收到通知了,通知的接收是 onCharacteristicChanged函数
public static UUID parseUUID(String name) {
if (TextUtils.isEmpty(name)) {
return null;
}
String code = "0000" + name + "-0000-1000-8000-00805f9b34fb";
if (!TextUtils.isEmpty(code)) {
return UUID.fromString(code);
}
return null;
}
8.数据的主动读取
通过蓝牙可以读取设备服务端的值,但是必须通过uuid 来完成。
public boolean readDataByUuid(String uuid) {
LogS.d (TAG, "开始读取数据:readData() uuid: " + uuid);
boolean ret = false;
LogS.d (TAG, "readData:" + BeanBleUtil.parseUUID (uuid).toString ());
if (currentBindGatt != null) {
BluetoothGattService bleService = currentBindGatt.getService (BeanBleUtil.parseUUID (“181c));
if (bleService == null) {
Log.e (TAG, "readDataByUuid: BluetoothGattService==null");
return false;
}
BluetoothGattCharacteristic characteristic = bleService.getCharacteristic (BeanBleUtil.parseUUID (uuid));
if (characteristic != null) {
ret = currentBindGatt.readCharacteristic (characteristic);
}
LogS.d (TAG, "readData(" + uuid + ") ret: " + ret);
} else {
LogS.d (TAG, "readData currentBindGatt == null!");
}
return ret;
}
读取完成后,数据是通过onCharacteristicRead 函数回掉回来。
9.数据的写入
写入和读取类似,都是先获取gatt服务,然后通过服务和uuid 获取特征值,然后写入数据。
public boolean writeByte(byte[] sendValue, String uuid1) {
UUID uuid = BeanBleUtil.parseUUID (uuid1);
boolean su = false;
LogS.d (TAG, "writeByte() sendValue: " + sendValue.toString () + ", uuid: " + uuid);
if (currentBindGatt != null) {
BluetoothGattService bleService = currentBindGatt.getService (BeanBleUtil.parseUUID (”181c"));
BluetoothGattCharacteristic characteristic = bleService.getCharacteristic (uuid);
if (characteristic != null) {
characteristic.setValue (sendValue);
su = currentBindGatt.writeCharacteristic (characteristic);
}
}
return su;
}
如果写入成功onCharacteristicWrite 回掉函数会执行。
9.关于UUID
UUID 本身指的是一个类UUID ,他包含uuid ,我们所说的特征值id。 uuid 是一串十六进制组成的数字,提示唯一识别码,就像身份证一样,只不过ble中它代表服务,特征的号码,uuid 是128bit 的,因为太长所有SIG将uuid 进行预设,预设过后uuid 的一些部分就变成了固定部分,就比如我们本次服务的uuid 是: “0000” + 181c+ “-0000-1000-8000-00805f9b34fb”; 除了181c ,其他部分都是固定的。
最后:对于很对没有开发过蓝牙的同学来说需要搞明白,ble 是不需要去配对的,需要配对的是传统蓝牙,这点不要傻傻分不清楚,其次,ble 蓝牙只需要搜索到设备,可以直接连接了,过程相对简单,但是任何设备在连接的过程中都可能发生异常,所以我建议在连接的时候设置超时机制,可以有效减少程序故障。
|