蓝牙测试demo
简介
Android手机间通过蓝牙方式进行通信,有两种常见的方式,一种是socket方式(传统蓝牙),另一种是通过GATT(BLE蓝牙)。与传统蓝牙相比,BLE 旨在大幅降低功耗。这样一来,应用就可以与功率要求更严格的 BLE 设备(如近程传感器、心率监测器和健身设备)进行通信。
实现
1.权限
如需使用BLE蓝牙 API,需要在AndroidManifest.xml清单文件中声明多项权限,并动态获取相应权限。一旦应用获得使用蓝牙的权限,应用就需要访问 BluetoothAdapter 并确定设备是否支持蓝牙。如果蓝牙可用,设备将扫描附近的 BLE 设备。发现设备后,系统会连接到 BLE 设备上的 GATT 服务器来发现 BLE 设备的功能。建立连接后,可以根据可用的服务和特征通过已连接的设备传输数据。
在高版本的安卓设备中,除蓝牙基本权限外,还需用到位置权限,否则会扫描不到蓝牙。
AndroidManifest.xml文件如下:
动态权限申请如下,可放在MainActivity中。
// todo 动态申请权限
private void initPermission() {
List mPermissionList = new ArrayList<>();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
// Android 版本大于等于 Android12 时
mPermissionList.add(android.Manifest.permission.BLUETOOTH_SCAN);
mPermissionList.add(android.Manifest.permission.BLUETOOTH_ADVERTISE);
mPermissionList.add(android.Manifest.permission.BLUETOOTH_CONNECT);
}
mPermissionList.add(android.Manifest.permission.ACCESS_COARSE_LOCATION);
mPermissionList.add(android.Manifest.permission.ACCESS_FINE_LOCATION);
if (mPermissionList.size() > 0) {
ActivityCompat.requestPermissions(this, mPermissionList.toArray(new String[0]), 1001);
}
}
2.扫描蓝牙设备
//默认扫描BLE蓝牙时长10s 可更改
private final Long mScanTime = 10 * 1000L;
private final List mScanFilterList = new ArrayList<>();
private final ScanSettings mScanSettings = new ScanSettings.Builder().build();
/**
* 蓝牙扫描
*/
@SuppressLint("MissingPermission")
public void startScanBle() {
if (!getBleEnable()) {
// openBlueTooth();
TimerUtil.cancelTimer();
ToastUtil.showToast("蓝牙未打开", 2000);
return;
}
if (!GpsAdmin.getInstance().isGpsOn()) {
TimerUtil.cancelTimer();
ToastUtil.showToast("定位未打开", 2000);
return;
}
Log.i(TAG, "开始扫描蓝牙");
mBleCurrentInfo.clearBleResult();
BleCallback.instance.startLeScan(mBluetoothAdapter, new ScanCallback() {
@Override
public void onScanResult(int callbackType, ScanResult result) {
super.onScanResult(callbackType, result);
try {
if (result.getDevice() == null || result.getDevice().getAddress() == null) {
return;
}
if (result.getDevice().getName() == null || Objects.equals(result.getDevice().getName(), "")) {
return;
}
for (BleDeviceBean bean : mBleCurrentInfo.getBleList()) {
if (Objects.equals(bean.getBleAddress(), result.getDevice().getAddress())) {
return;
}
}
Log.i(TAG, "NAME:" + result.getDevice().getName() + ",ADDRESS:" + result.getDevice().getAddress());
BleDeviceBean bean = new BleDeviceBean(result.getDevice().getName(), result.getDevice().getAddress(), false);
mBleCurrentInfo.addBleResult(bean);
} catch (Exception e) {
Log.i(TAG, e.toString());
}
}
@Override
public void onBatchScanResults(List results) {
super.onBatchScanResults(results);
}
@Override
public void onScanFailed(int errorCode) {
super.onScanFailed(errorCode);
}
}, mScanTime, mScanFilterList, mScanSettings);
}
BleCallback中startLeScan()方法如下。
/**
* 开启ble扫描
*/
@RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN)
public void startLeScan(
BluetoothAdapter bluetoothAdapter,
ScanCallback scanCallback,
Long scanTime,
List filters,
ScanSettings scanSettings
) {
try {
if (bluetoothAdapter == null) {
return;
}
if (scanCallback == null) {
return;
}
BluetoothLeScanner bluetoothLeScanner = bluetoothAdapter.getBluetoothLeScanner();
if (bluetoothLeScanner == null) {
return;
}
if (mScanCallback != null && bluetoothAdapter.getState() == BluetoothAdapter.STATE_ON) {
bluetoothLeScanner.stopScan(mScanCallback);
mScanCallback = null;
}
TimerUtil.cancelDialogTimer();
mScanCallback = scanCallback;
bluetoothLeScanner.startScan(
filters,
scanSettings,
mScanCallback
);
TimerUtil.startDialogTask(new TimerTask() {
@RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN)
@Override
public void run() {
if (mScanCallback != null && bluetoothAdapter.getState() == BluetoothAdapter.STATE_ON) {
bluetoothLeScanner.stopScan(mScanCallback);
mScanCallback = null;
}
}
}, scanTime);
} catch (Exception e) {
Log.i(TAG, "蓝牙扫描异常");
}
}
3.连接GATT服务器
public void connect(
Boolean isAutoConnect,
BluetoothDevice bluetoothDevice
) {
bluetoothDevice.connectGatt(Utils.getApp(), isAutoConnect, this, BluetoothDevice.TRANSPORT_LE);
}
在设备连接蓝牙后会触发onConnectionStateChange回调。
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
Log.i(TAG, "onConnectionStateChange,status:" + status +",newState:" + newState);
switch (newState) {
case BluetoothProfile.STATE_CONNECTED:
BleDevice bleDevice = new BleDevice();
bleDevice.setDeviceName(gatt.getDevice().getName());
bleDevice.setMacAddress(gatt.getDevice().getAddress());
bleDevice.setGatt(gatt);
mConnectedBleDeviceList.add(bleDevice);
Log.i(TAG, gatt.getDevice().getAddress() + "已连接");
if (mConnectListener != null) {
mConnectListener.onConnectSuccess(gatt);
}
try {
Thread.sleep(50);
} catch (InterruptedException e) {
Log.i(TAG, e.toString());
}
gatt.discoverServices();
break;
case BluetoothProfile.STATE_DISCONNECTED:
Iterator iterator = mConnectedBleDeviceList.iterator();
while (iterator.hasNext()) {
BleDevice next = iterator.next();
if (Objects.equals(next.getMacAddress(), gatt.getDevice().getAddress())) {
iterator.remove();
}
}
Log.i(TAG, gatt.getDevice().getAddress() + "断开连接");
if (mConnectListener != null) {
mConnectListener.onDisconnect(gatt);
}
gatt.close();
break;
default:
break;
}
}
当newState值为BluetoothProfile.STATE_CONNECTED表示连接外围设备成功,其中gatt.discoverServices()方法尤为关键,如果不调用此方法,就无法触发onServicesDiscovered()回调,就无法发现所连接蓝牙设备的服务UUID;除此之外,如果连接上后立刻调用此方法,会有可能无法触发onServicesDiscovered()方法。
4.绑定UUID
在设备触发onServicesDiscovered回调时,获取到蓝牙所有服务。
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
Log.i(TAG, "onServicesDiscovered,status:" + status);
switch (status) {
case BluetoothGatt.GATT_SUCCESS:
if (!mConnectedBleDeviceList.isEmpty()) {
mConnectedBleDeviceList.get(mConnectedBleDeviceList.size() - 1).setServiceList(gatt.getServices());
if (mConnectListener != null) {
mConnectListener.onServicesDiscovered(gatt, gatt.getServices());
}
}
break;
default:
if (mConnectListener != null) {
mConnectListener.onServicesDiscoverFailed(status);
}
break;
}
}
正常情况下,当触发触发onConnectSuccess回调时,设备已经连上的蓝牙的GATT,此时可认为蓝牙已连接,此时中心设备是无法和外围设备通讯的,还需根据蓝牙协议绑定指定的特征UUID,一般中心设备接收数据的为NOTIFY_CHARACTERISTIC_UUID,向外围设备发送消息的为WRITE_CHARACTERISTIC_UUID,大部分外设备还有描述符,例如接收数据需要的描述符NOTIFY_DESCRIPTOR_UUID等。下面是我的蓝牙设备使用到的UUID。
public class UsedUUID {
public static UUID SERVICE_UUID = UUID.fromString("0783b03e-8535-b5a0-7140-a304d2495cb7");
public static UUID WRITE_CHARACTERISTIC_UUID = UUID.fromString("0783b03e-8535-b5a0-7140-a304d2495cba");
public static UUID NOTIFY_CHARACTERISTIC_UUID = UUID.fromString("0783b03e-8535-b5a0-7140-a304d2495cb8");
public static UUID NOTIFY_DESCRIPTOR_UUID = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb");
}
获取到所有服务后,根据蓝牙协议,绑定指定的特征UUID。此demo中实际以设备找到蓝牙服务指定UUID,并绑定成功视为真正连接成功。
public void onServicesDiscovered(BluetoothGatt gatt, List services) {
Log.i(TAG, "发现服务UUID");
boolean isNext = false;
for (BluetoothGattService service: services) {
if (service.getUuid().toString().equals(UsedUUID.SERVICE_UUID.toString())) {
Log.i(TAG, "找到SERVICE_UUID");
mOperateService = service;
mBleGattCharacteristics.clear();
mBleGattCharacteristics.addAll(service.getCharacteristics());
isNext = true;
break;
}
}
if (!isNext) {
Log.i(TAG, "未找到指定的服务UUID");
disConnectGatt(false);
return;
}
isNext = false;
List bleGattCharacteristics = new ArrayList<>(mBleGattCharacteristics);
for (BluetoothGattCharacteristic bluetoothGattCharacteristic: bleGattCharacteristics) {
if (bluetoothGattCharacteristic.getUuid().toString().equals(UsedUUID.NOTIFY_CHARACTERISTIC_UUID.toString())) {
Log.i(TAG, "找到NOTIFY_CHARACTERISTIC_UUID");
mNotifyCharacteristic = bluetoothGattCharacteristic;
isNext = true;
}
if (bluetoothGattCharacteristic.getUuid().toString().equals(UsedUUID.WRITE_CHARACTERISTIC_UUID.toString())) {
Log.i(TAG, "找到WRITE_CHARACTERISTIC_UUID");
mWriteCharacteristic = bluetoothGattCharacteristic;
}
}
if (isNext) {
try {
Log.i(TAG, "BLE蓝牙连接成功");
if (mBleCurrentInfo.isConnect()) {
mBleCurrentInfo.setConnect(false);
mOperateGatt.close();
}
for (BleDeviceBean bean: mBleCurrentInfo.getBleList()) {
bean.setBleConnect(false);
}
mOperateGatt = gatt;
subscribeNotify();
Log.i(TAG, "BLE蓝牙绑定成功");
mBleCurrentInfo.setConnect(true);
if (mIsFirstConnect) {
mBleCurrentInfo.getBleList().get(0).setBleConnect(true);
}
// if (Objects.equals(mBleCurrentInfo.getBleAddress(), gatt.getDevice().getAddress()) && !mBleCurrentInfo.getBleList().isEmpty()) {
// mBleCurrentInfo.getBleList().get(0).setBleConnect(true);
// }
mBleCurrentInfo.setConnect(true);
mBleCurrentInfo.setBleName(gatt.getDevice().getName());
mBleCurrentInfo.setBleAddress(gatt.getDevice().getAddress());
mBleCurrentInfo.updateScanBean();
if (mDiscoveredListener != null) {
mDiscoveredListener.onServicesDiscovered(gatt);
}
if (mBleListener != null) {
mBleListener.onConnectSuccess(true);
}
} catch (Exception e) {
Log.i(TAG, "BLE蓝牙连接异常");
disConnectGatt(false);
}
} else {
disConnectGatt(false);
}
}
5.接收消息
所有接收的消息都是触发连接蓝牙时传入的BluetoothGattCallback回调中onCharacteristicChanged方法,外围设备会将数据的变更写入属性为NOTIFY的BluetoothGattCharacteristic中,每当BluetoothGattCharacteristic的值发生变化时,中心设备都会收到通知。
public void onCharacteristicChanged(
BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic
) {
if (mNotifyListener != null) {
mNotifyListener.onCharacteristicChange(gatt, characteristic);
}
}
6.发送消息
发送消息时,我们中心设备只需改变属性为WRITED的BluetoothGattCharacteristic值,外围设备就可以收到我们发送的消息。
public void sendMessage(byte[] message) {
if (!mBleCurrentInfo.isConnect()) {
ToastUtil.showToast("蓝牙未连接,请连接蓝牙!", 2000);
return;
}
if (mOperateGatt == null || mWriteCharacteristic == null) {
Log.i(TAG, "发送消息失败,未持有相关服务");
return;
}
mWriteCharacteristic.setValue(message);
mOperateGatt.writeCharacteristic(mWriteCharacteristic);
}
7.效果演示
这里以发送HD+RPC=3,{“paramtype”:1}为例子,将String转为byte数组后发送给外围设备,接收外围设备数据为byte数组,未经过转换。
8.核心代码
① BleAdmin完整代码:
package com.example.bluetooth.util;
import android.annotation.SuppressLint;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattService;
import android.bluetooth.BluetoothManager;
import android.bluetooth.le.ScanCallback;
import android.bluetooth.le.ScanFilter;
import android.bluetooth.le.ScanResult;
import android.bluetooth.le.ScanSettings;
import android.content.Context;
import android.util.Log;
import com.example.bluetooth.bean.BleCurrentInfo;
import com.example.bluetooth.bean.BleDeviceBean;
import com.example.bluetooth.callback.BleCallback;
import com.example.bluetooth.intel.BleListener;
import com.example.bluetooth.intel.ConnectListener;
import com.example.bluetooth.intel.DiscoveredListener;
import com.example.bluetooth.intel.NotifyListener;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.TimerTask;
/**
* @des: 蓝牙管理类
* @date: 2024/9/2
* @author: yanghaifeng
*/
public class BleAdmin implements ConnectListener, NotifyListener {
private final static String TAG = "BleAdmin";
private static BleAdmin bleAdmin;
private BluetoothAdapter mBluetoothAdapter;
private BluetoothGatt mOperateGatt;
private BluetoothGattService mOperateService;
private BluetoothGattCharacteristic mNotifyCharacteristic;
private BluetoothGattCharacteristic mWriteCharacteristic;
private int mScanCount = 0; // 扫描次数
//默认扫描BLE蓝牙时长10s 可更改
private final Long mScanTime = 10 * 1000L;
private final List mScanFilterList = new ArrayList<>();
private final ScanSettings mScanSettings = new ScanSettings.Builder().build();
private BleCurrentInfo mBleCurrentInfo = new BleCurrentInfo(); // 已保存的蓝牙设备信息
private List mBleGattCharacteristics = new ArrayList<>();
private long mStartTime = 0L;
private boolean mIsFirstConnect = true;
private DiscoveredListener mDiscoveredListener = null;
private BleListener mBleListener = null;
private Thread mHeartThread = null; // 心跳线程
private boolean mIsHeart = false; // 心跳线程运行状态
private long mHeartOverTime = 3000L; // 心跳超时时间
private long mHeartTime = 0L;
private Thread mConnectThread = null; // 重连线程
private boolean mIsRunning = false; // 重连线程运行状态
long mConnectOverTime = 20 * 1000L; // 重连扫描超时时间
public static BleAdmin getInstance() {
if (bleAdmin == null) {
bleAdmin = new BleAdmin();
}
return bleAdmin;
}
public BleAdmin() {
BluetoothManager bluetoothManager = (BluetoothManager) Utils.getApp().getSystemService(Context.BLUETOOTH_SERVICE);
mBluetoothAdapter = bluetoothManager.getAdapter();
BleCallback.instance.setConnectListener(this);
BleCallback.instance.setNotifyListener(this);
}
public BluetoothGatt getOperateGatt() {
return mOperateGatt;
}
public void setOperateGatt(BluetoothGatt mOperateGatt) {
this.mOperateGatt = mOperateGatt;
}
public BluetoothGattService getOperateService() {
return mOperateService;
}
public void setOperateService(BluetoothGattService mOperateService) {
this.mOperateService = mOperateService;
}
public BluetoothGattCharacteristic getNotifyCharacteristic() {
return mNotifyCharacteristic;
}
public void setNotifyCharacteristic(BluetoothGattCharacteristic mNotifyCharacteristic) {
this.mNotifyCharacteristic = mNotifyCharacteristic;
}
public BluetoothGattCharacteristic getWriteCharacteristic() {
return mWriteCharacteristic;
}
public void setWriteCharacteristic(BluetoothGattCharacteristic mWriteCharacteristic) {
this.mWriteCharacteristic = mWriteCharacteristic;
}
public BleCurrentInfo getBleCurrentInfo() {
return mBleCurrentInfo;
}
public void setBleCurrentInfo(BleCurrentInfo mBleCurrentInfo) {
this.mBleCurrentInfo = mBleCurrentInfo;
}
public void setDiscoveredListener(DiscoveredListener mDiscoveredListener) {
this.mDiscoveredListener = mDiscoveredListener;
}
public void setBleListener(BleListener mBleListener) {
this.mBleListener = mBleListener;
}
public long getStartTime() {
return mStartTime;
}
public void setStartTime(long mStartTime) {
this.mStartTime = mStartTime;
}
public void setIsFirstConnect(boolean mIsFirstConnect) {
this.mIsFirstConnect = mIsFirstConnect;
}
/**
* 开启心跳线程
*/
public void startHeartThread() {
Log.i(TAG, "开启心跳线程");
getHeardThread();
mHeartThread.start();
}
private void getHeardThread() {
stopHeartThread();
mIsHeart = true;
mHeartThread = new Thread(new Runnable() {
@Override
public void run() {
mHeartTime = System.currentTimeMillis();
while (mIsHeart) {
if (System.currentTimeMillis() - mHeartTime > mHeartOverTime) {
Log.i(TAG, "心跳超时");
disConnectGatt(true);
}
}
}
});
}
/**
* 关闭心跳线程
*/
public void stopHeartThread() {
mIsHeart = false;
try {
if (mHeartThread != null) {
mHeartThread.join();
mHeartThread = null;
}
} catch (InterruptedException i) {
Log.i(TAG, i.toString());
}
}
/**
* 开启重连线程
*/
public void startConnectThread() {
Log.i(TAG, "开启重连线程");
getConnectThread();
mConnectThread.start();
}
private void getConnectThread() {
mIsHeart = false;
stopConnectThread();
mIsRunning = true;
mConnectThread = new Thread(new Runnable() {
@Override
public void run() {
startScanBleTask();
long time = System.currentTimeMillis();
Log.i(TAG, "开始尝试重连");
while (mIsRunning && System.currentTimeMillis() - time < mConnectOverTime) {
List beans = new ArrayList<>(mBleCurrentInfo.getBleList());
for (BleDeviceBean bean: beans) {
try {
if (Objects.equals(bean.getBleAddress(), mBleCurrentInfo.getBleAddress())) {
Log.i(TAG, "找到重连address:" + mBleCurrentInfo.getBleAddress());
int count = 0;
Log.i(TAG, "第${count + 1}次尝试连接");
connectGatt(getRemoteDevice(bean.getBleAddress()));
count++;
long currentTime = System.currentTimeMillis();
while (count < 3) {
if (!mIsRunning) {
return;
}
if (mBleCurrentInfo.isConnect()) {
break;
}
if (System.currentTimeMillis() - currentTime < 5000) {
continue;
}
Log.i(TAG, "第${count + 1}次尝试连接");
connectGatt(getRemoteDevice(bean.getBleAddress()));
currentTime = System.currentTimeMillis();
count++;
}
mIsRunning = false;
break;
}
} catch (Exception e) {
Log.i(TAG, e.toString());
}
}
}
}
});
}
/**
* 关闭重连线程
*/
public void stopConnectThread() {
mIsRunning = false;
try {
if (mConnectThread != null) {
mConnectThread.join();
mConnectThread = null;
}
} catch (InterruptedException i) {
Log.i(TAG, i.toString());
} finally {
stopScanBle();
}
}
/**
* 开启手机蓝牙
*
*/
@SuppressLint("MissingPermission")
public boolean openBlueTooth() {
return mBluetoothAdapter.enable();
}
/**
* 获取蓝牙状态
*
*/
public boolean getBleEnable() {
return mBluetoothAdapter.isEnabled();
}
/**
* 关闭手机蓝牙
*
*/
@SuppressLint("MissingPermission")
public void closeBlueTooth() {
mBluetoothAdapter.disable();
}
/**
* 获取外围设备广播信息
*
* @param address 外围设备ble地址Mac
* @return 通过MAC获取到的外围设备
*/
public BluetoothDevice getRemoteDevice(String address) {
return mBluetoothAdapter.getRemoteDevice(address);
}
/**
* 识别特征功能
*
* @param characteristic service中的特征
* @return
*/
public String detectCharacteristic(BluetoothGattCharacteristic characteristic) {
StringBuilder propSb = new StringBuilder();
if (characteristic.getProperties() > 0 && BluetoothGattCharacteristic.PROPERTY_READ > 0) {
propSb.append("Read");
}
if (characteristic.getProperties() > 0 && BluetoothGattCharacteristic.PROPERTY_WRITE > 0) {
if (propSb.length() > 0) {
propSb.append(" ");
}
propSb.append("Write");
}
if (characteristic.getProperties() > 0 && BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE > 0) {
if (propSb.length() > 0) {
propSb.append(" ");
}
propSb.append("Write No Response");
}
if (characteristic.getProperties() > 0 && BluetoothGattCharacteristic.PROPERTY_NOTIFY > 0) {
if (propSb.length() > 0) {
propSb.append(" ");
}
propSb.append("Notify");
}
if (characteristic.getProperties() > 0 && BluetoothGattCharacteristic.PROPERTY_INDICATE > 0) {
if (propSb.length() > 0) {
propSb.append(" ");
}
propSb.append("Indicate");
}
return propSb.toString();
}
/**
* 蓝牙扫描
*/
@SuppressLint("MissingPermission")
public void startScanBle() {
if (!getBleEnable()) {
// openBlueTooth();
TimerUtil.cancelTimer();
HandlerUtil.post(()-> ToastUtil.showToast("蓝牙未打开", 2000));
return;
}
if (!GpsAdmin.getInstance().isGpsOn()) {
TimerUtil.cancelTimer();
HandlerUtil.post(()-> ToastUtil.showToast("定位未打开", 2000));
return;
}
Log.i(TAG, "开始扫描蓝牙");
mBleCurrentInfo.clearBleResult();
BleCallback.instance.startLeScan(mBluetoothAdapter, new ScanCallback() {
@Override
public void onScanResult(int callbackType, ScanResult result) {
super.onScanResult(callbackType, result);
try {
if (result.getDevice() == null || result.getDevice().getAddress() == null) {
return;
}
if (result.getDevice().getName() == null || Objects.equals(result.getDevice().getName(), "")) {
return;
}
for (BleDeviceBean bean : mBleCurrentInfo.getBleList()) {
if (Objects.equals(bean.getBleAddress(), result.getDevice().getAddress())) {
return;
}
}
Log.i(TAG, "NAME:" + result.getDevice().getName() + ",ADDRESS:" + result.getDevice().getAddress());
BleDeviceBean bean = new BleDeviceBean(result.getDevice().getName(), result.getDevice().getAddress(), false);
mBleCurrentInfo.addBleResult(bean);
} catch (Exception e) {
Log.i(TAG, e.toString());
}
}
@Override
public void onBatchScanResults(List results) {
super.onBatchScanResults(results);
}
@Override
public void onScanFailed(int errorCode) {
super.onScanFailed(errorCode);
}
}, mScanTime, mScanFilterList, mScanSettings);
}
public void startScanBleTask() {
stopScanBle();
mBleCurrentInfo.clearBleResult();
mScanCount = 0;
TimerUtil.startTask(new TimerTask() {
@Override
public void run() {
if (!mBleCurrentInfo.getBleList().isEmpty() || mScanCount > 0) {
TimerUtil.cancelTimer();
return;
}
startScanBle();
mScanCount++;
}
}, 0L, 3000L);
}
/**
* 开始扫描蓝牙
*/
public void startScanBleFirst() {
if (!mBleCurrentInfo.getHisBleList().isEmpty()) {
mStartTime = System.currentTimeMillis();
startScanBleTask();
}
}
@SuppressLint("MissingPermission")
public void stopScanBle() {
TimerUtil.cancelTimer();
BleCallback.instance.stopScan(mBluetoothAdapter);
}
/**
* 连接GATT
*/
public void connectGatt(BluetoothDevice bluetoothDevice) {
BleCallback.instance.connect(false, bluetoothDevice);
}
/**
* 断开GATT
*/
@SuppressLint("MissingPermission")
public void disConnectGatt(boolean isReconnect) {
Log.i(TAG, "断开GATT,是否重连:" + isReconnect);
try {
if (mBleCurrentInfo.isConnect()) {
if (mOperateGatt != null) {
unsubscribeNotify();
mOperateGatt.close();
}
}
} catch (Exception e) {
Log.i(TAG, e.toString());
} finally {
for (BleDeviceBean bean: mBleCurrentInfo.getBleList()) {
bean.setBleConnect(false);
}
mBleCurrentInfo.setConnect(false);
mOperateGatt = null;
mNotifyCharacteristic = null;
mWriteCharacteristic = null;
if (mBleListener != null) {
mBleListener.onConnectSuccess(false);
}
mIsHeart = false;
if (isReconnect) {
startConnectThread();
}
}
}
/**
* 发送消息
*/
@SuppressLint("MissingPermission")
public void sendMessage(byte[] message) {
if (!mBleCurrentInfo.isConnect()) {
ToastUtil.showToast("蓝牙未连接,请连接蓝牙!", 2000);
return;
}
if (mOperateGatt == null || mWriteCharacteristic == null) {
Log.i(TAG, "发送消息失败,未持有相关服务");
return;
}
mWriteCharacteristic.setValue(message);
mOperateGatt.writeCharacteristic(mWriteCharacteristic);
}
public void subscribeNotify() {
if (mOperateGatt != null && mNotifyCharacteristic != null) {
BleCallback.instance.subscribeNotify(
mOperateGatt,
mNotifyCharacteristic,
UsedUUID.NOTIFY_DESCRIPTOR_UUID
);
} else {
Log.i(TAG, "绑定失败,未持有相关服务");
ToastUtil.showToast("绑定失败", 2000);
}
}
public void unsubscribeNotify() {
if (mOperateGatt != null && mNotifyCharacteristic != null) {
BleCallback.instance.unsubscribeNotify(
mOperateGatt, mNotifyCharacteristic, UsedUUID.NOTIFY_DESCRIPTOR_UUID
);
} else {
Log.i(TAG, "解绑失败,未持有相关服务");
}
}
/**
* GATT连接成功
*/
@Override
public void onConnectSuccess(BluetoothGatt gatt) {
Log.i(TAG, "GATT连接成功");
}
/**
* GATT断开连接
*/
@Override
public void onDisconnect(BluetoothGatt gatt) {
if (mOperateGatt != null || !Objects.equals(gatt.getDevice().getAddress(), mOperateGatt.getDevice().getAddress())) {
return;
}
Log.i(TAG, "GATT断开连接");
boolean isReconnect = false;
if (!mBleCurrentInfo.getBleList().isEmpty() && Objects.equals(gatt.getDevice().getAddress(), mBleCurrentInfo.getBleList().get(0).getBleAddress())) {
isReconnect = true;
}
disConnectGatt(isReconnect);
}
/**
* 发现服务
*/
@SuppressLint("MissingPermission")
@Override
public void onServicesDiscovered(BluetoothGatt gatt, List services) {
Log.i(TAG, "发现服务UUID");
boolean isNext = false;
for (BluetoothGattService service: services) {
if (service.getUuid().toString().equals(UsedUUID.SERVICE_UUID.toString())) {
Log.i(TAG, "找到SERVICE_UUID");
mOperateService = service;
mBleGattCharacteristics.clear();
mBleGattCharacteristics.addAll(service.getCharacteristics());
isNext = true;
break;
}
}
if (!isNext) {
Log.i(TAG, "未找到指定的服务UUID");
disConnectGatt(false);
return;
}
isNext = false;
List bleGattCharacteristics = new ArrayList<>(mBleGattCharacteristics);
for (BluetoothGattCharacteristic bluetoothGattCharacteristic: bleGattCharacteristics) {
if (bluetoothGattCharacteristic.getUuid().toString().equals(UsedUUID.NOTIFY_CHARACTERISTIC_UUID.toString())) {
Log.i(TAG, "找到NOTIFY_CHARACTERISTIC_UUID");
mNotifyCharacteristic = bluetoothGattCharacteristic;
isNext = true;
}
if (bluetoothGattCharacteristic.getUuid().toString().equals(UsedUUID.WRITE_CHARACTERISTIC_UUID.toString())) {
Log.i(TAG, "找到WRITE_CHARACTERISTIC_UUID");
mWriteCharacteristic = bluetoothGattCharacteristic;
}
}
if (isNext) {
try {
Log.i(TAG, "BLE蓝牙连接成功");
if (mBleCurrentInfo.isConnect()) {
mBleCurrentInfo.setConnect(false);
mOperateGatt.close();
}
for (BleDeviceBean bean: mBleCurrentInfo.getBleList()) {
bean.setBleConnect(false);
}
mOperateGatt = gatt;
subscribeNotify();
Log.i(TAG, "BLE蓝牙绑定成功");
mBleCurrentInfo.setConnect(true);
if (mIsFirstConnect) {
mBleCurrentInfo.getBleList().get(0).setBleConnect(true);
}
// if (Objects.equals(mBleCurrentInfo.getBleAddress(), gatt.getDevice().getAddress()) && !mBleCurrentInfo.getBleList().isEmpty()) {
// mBleCurrentInfo.getBleList().get(0).setBleConnect(true);
// }
mBleCurrentInfo.setConnect(true);
mBleCurrentInfo.setBleName(gatt.getDevice().getName());
mBleCurrentInfo.setBleAddress(gatt.getDevice().getAddress());
mBleCurrentInfo.updateScanBean();
if (mDiscoveredListener != null) {
mDiscoveredListener.onServicesDiscovered(gatt);
}
if (mBleListener != null) {
mBleListener.onConnectSuccess(true);
}
} catch (Exception e) {
Log.i(TAG, "BLE蓝牙连接异常");
disConnectGatt(false);
}
} else {
disConnectGatt(false);
}
}
@Override
public void onServicesDiscoverFailed(int status) {
Log.i(TAG, "onServicesDiscoverFailed");
}
@Override
public void onServicesChange(BluetoothGatt gatt) {
Log.i(TAG, "onServicesChange");
}
@Override
public void onCharacteristicChange(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
mHeartTime = System.currentTimeMillis();
Log.i(TAG, Arrays.toString(characteristic.getValue()));
mBleListener.onCharacteristicChange(characteristic.getValue());
}
}
该蓝牙管理类中采用了心跳来保证蓝牙通讯的稳定性,每当超时未接收到外围设备心跳时,都会开启重连线程,若没有相关心跳协议,可将心跳部分代码去掉。
② BleCallback完整代码:
package com.example.bluetooth.callback;
import android.annotation.SuppressLint;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCallback;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattDescriptor;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.le.BluetoothLeScanner;
import android.bluetooth.le.ScanCallback;
import android.bluetooth.le.ScanFilter;
import android.bluetooth.le.ScanSettings;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresPermission;
import com.example.bluetooth.bean.BleDevice;
import com.example.bluetooth.intel.CharacteristicListener;
import com.example.bluetooth.intel.ConnectListener;
import com.example.bluetooth.intel.DescriptorListener;
import com.example.bluetooth.intel.MtuListener;
import com.example.bluetooth.intel.NotifyListener;
import com.example.bluetooth.intel.PhyListener;
import com.example.bluetooth.intel.ReliableListener;
import com.example.bluetooth.intel.RssiListener;
import com.example.bluetooth.util.TimerUtil;
import com.example.bluetooth.util.Utils;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.TimerTask;
import java.util.UUID;
/**
* @des: 蓝牙回调类
* @date: 2024/9/4
* @author: yanghaifeng
*/
public class BleCallback extends BluetoothGattCallback {
private final static String TAG = "BleCallback";
public static BleCallback instance = new BleCallback();
private ArrayList mConnectedBleDeviceList = new ArrayList<>();
private ConnectListener mConnectListener = null;
private MtuListener mMtuListener = null;
private RssiListener mRssiListener = null;
private NotifyListener mNotifyListener = null;
private CharacteristicListener mCharacteristicListener = null;
private PhyListener mPhyListener = null;
private ReliableListener mReliableListener = null;
private DescriptorListener mDescriptorListener = null;
private ScanCallback mScanCallback = null;
public ArrayList getConnectedBleDeviceList() {
return mConnectedBleDeviceList;
}
public void setConnectedBleDeviceList(ArrayList mConnectedBleDeviceList) {
this.mConnectedBleDeviceList = mConnectedBleDeviceList;
}
public ConnectListener getConnectListener() {
return mConnectListener;
}
public void setConnectListener(ConnectListener mConnectListener) {
this.mConnectListener = mConnectListener;
}
public MtuListener getMtuListener() {
return mMtuListener;
}
public void setMtuListener(MtuListener mMtuListener) {
this.mMtuListener = mMtuListener;
}
public RssiListener getRssiListener() {
return mRssiListener;
}
public void setRssiListener(RssiListener mRssiListener) {
this.mRssiListener = mRssiListener;
}
public NotifyListener getNotifyListener() {
return mNotifyListener;
}
public void setNotifyListener(NotifyListener mNotifyListener) {
this.mNotifyListener = mNotifyListener;
}
public CharacteristicListener getCharacteristicListener() {
return mCharacteristicListener;
}
public void setCharacteristicListener(CharacteristicListener mCharacteristicListener) {
this.mCharacteristicListener = mCharacteristicListener;
}
public PhyListener getPhyListener() {
return mPhyListener;
}
public void setPhyListener(PhyListener mPhyListener) {
this.mPhyListener = mPhyListener;
}
public ReliableListener getReliableListener() {
return mReliableListener;
}
public void setReliableListener(ReliableListener mReliableListener) {
this.mReliableListener = mReliableListener;
}
public DescriptorListener getDescriptorListener() {
return mDescriptorListener;
}
public void setDescriptorListener(DescriptorListener mDescriptorListener) {
this.mDescriptorListener = mDescriptorListener;
}
public ScanCallback getScanCallback() {
return mScanCallback;
}
public void setScanCallback(ScanCallback mScanCallback) {
this.mScanCallback = mScanCallback;
}
/**
* 开启ble扫描
*/
@RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN)
public void startLeScan(
BluetoothAdapter bluetoothAdapter,
ScanCallback scanCallback,
Long scanTime,
List filters,
ScanSettings scanSettings
) {
try {
if (bluetoothAdapter == null) {
return;
}
if (scanCallback == null) {
return;
}
BluetoothLeScanner bluetoothLeScanner = bluetoothAdapter.getBluetoothLeScanner();
if (bluetoothLeScanner == null) {
return;
}
if (mScanCallback != null && bluetoothAdapter.getState() == BluetoothAdapter.STATE_ON) {
bluetoothLeScanner.stopScan(mScanCallback);
mScanCallback = null;
}
TimerUtil.cancelDialogTimer();
mScanCallback = scanCallback;
bluetoothLeScanner.startScan(
filters,
scanSettings,
mScanCallback
);
TimerUtil.startDialogTask(new TimerTask() {
@RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN)
@Override
public void run() {
if (mScanCallback != null && bluetoothAdapter.getState() == BluetoothAdapter.STATE_ON) {
bluetoothLeScanner.stopScan(mScanCallback);
mScanCallback = null;
}
}
}, scanTime);
} catch (Exception e) {
Log.i(TAG, "蓝牙扫描异常");
}
}
@RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN)
public void stopScan(BluetoothAdapter bluetoothAdapter) {
if (bluetoothAdapter.getBluetoothLeScanner() != null && mScanCallback != null
&& bluetoothAdapter.getState() == BluetoothAdapter.STATE_ON) {
bluetoothAdapter.getBluetoothLeScanner().stopScan(mScanCallback);
}
}
@SuppressLint("MissingPermission")
public void disconnect(BluetoothGatt bluetoothGatt) {
bluetoothGatt.disconnect();
}
@SuppressLint("MissingPermission")
public void connect(
Boolean isAutoConnect,
BluetoothDevice bluetoothDevice
) {
bluetoothDevice.connectGatt(Utils.getApp(), isAutoConnect, this, BluetoothDevice.TRANSPORT_LE);
}
@SuppressLint("MissingPermission")
public void subscribeNotify(
BluetoothGatt bleGatt,
BluetoothGattCharacteristic characteristic,
UUID descriptorUUID
) {
bleGatt.setCharacteristicNotification(characteristic, true);
BluetoothGattDescriptor clientConfig = characteristic.getDescriptor(descriptorUUID);
if (clientConfig != null) {
clientConfig.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
bleGatt.writeDescriptor(clientConfig);
}
}
@SuppressLint("MissingPermission")
public void unsubscribeNotify(
BluetoothGatt bleGatt,
BluetoothGattCharacteristic characteristic,
UUID descriptorUUID
) {
try {
bleGatt.setCharacteristicNotification(characteristic, false);
BluetoothGattDescriptor clientConfig = characteristic.getDescriptor(descriptorUUID);
if (clientConfig != null) {
clientConfig.setValue(BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE);
bleGatt.writeDescriptor(clientConfig);
}
} catch (Exception e) {
Log.i(TAG, e.toString());
}
}
@Override
public void onPhyUpdate(BluetoothGatt gatt, int txPhy, int rxPhy, int status) {
if (mPhyListener != null) {
mPhyListener.onPhyUpdate(gatt, txPhy, rxPhy, status);
}
}
@Override
public void onPhyRead(BluetoothGatt gatt, int txPhy, int rxPhy, int status) {
if (mPhyListener != null) {
mPhyListener.onPhyRead(gatt, txPhy, rxPhy, status);
}
}
@SuppressLint("MissingPermission")
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
Log.i(TAG, "onConnectionStateChange,status:" + status +",newState:" + newState);
switch (newState) {
case BluetoothProfile.STATE_CONNECTED:
BleDevice bleDevice = new BleDevice();
bleDevice.setDeviceName(gatt.getDevice().getName());
bleDevice.setMacAddress(gatt.getDevice().getAddress());
bleDevice.setGatt(gatt);
mConnectedBleDeviceList.add(bleDevice);
Log.i(TAG, gatt.getDevice().getAddress() + "已连接");
if (mConnectListener != null) {
mConnectListener.onConnectSuccess(gatt);
}
try {
Thread.sleep(50);
} catch (InterruptedException e) {
Log.i(TAG, e.toString());
}
gatt.discoverServices();
break;
case BluetoothProfile.STATE_DISCONNECTED:
Iterator iterator = mConnectedBleDeviceList.iterator();
while (iterator.hasNext()) {
BleDevice next = iterator.next();
if (Objects.equals(next.getMacAddress(), gatt.getDevice().getAddress())) {
iterator.remove();
}
}
Log.i(TAG, gatt.getDevice().getAddress() + "断开连接");
if (mConnectListener != null) {
mConnectListener.onDisconnect(gatt);
}
gatt.close();
break;
default:
break;
}
}
@Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
Log.i(TAG, "onServicesDiscovered,status:" + status);
switch (status) {
case BluetoothGatt.GATT_SUCCESS:
if (!mConnectedBleDeviceList.isEmpty()) {
mConnectedBleDeviceList.get(mConnectedBleDeviceList.size() - 1).setServiceList(gatt.getServices());
if (mConnectListener != null) {
mConnectListener.onServicesDiscovered(gatt, gatt.getServices());
}
}
break;
default:
if (mConnectListener != null) {
mConnectListener.onServicesDiscoverFailed(status);
}
break;
}
}
@Override
public void onCharacteristicRead(
BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic,
int status
) {
if (mCharacteristicListener != null) {
mCharacteristicListener.onCharacteristicRead(gatt, characteristic, status);
}
}
@Override
public void onCharacteristicWrite(
BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic,
int status
) {
if (mCharacteristicListener != null) {
mCharacteristicListener.onCharacteristicWrite(gatt, characteristic, status);
}
}
@Override
public void onCharacteristicChanged(
BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic
) {
if (mNotifyListener != null) {
mNotifyListener.onCharacteristicChange(gatt, characteristic);
}
}
@Override
public void onDescriptorRead(
BluetoothGatt gatt,
BluetoothGattDescriptor descriptor,
int status
) {
if (mDescriptorListener != null) {
mDescriptorListener.onDescriptorRead(gatt, descriptor, status);
}
}
@Override
public void onDescriptorWrite(
BluetoothGatt gatt,
BluetoothGattDescriptor descriptor,
int status
) {
if (mDescriptorListener != null) {
mDescriptorListener.onDescriptorWrite(gatt, descriptor, status);
}
}
@Override
public void onReliableWriteCompleted(BluetoothGatt gatt, int status) {
if (mReliableListener != null) {
mReliableListener.onReliableWriteCompleted(gatt, status);
}
}
@Override
public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) {
if (mRssiListener != null) {
mRssiListener.onReadRemoteRssi(gatt, rssi, status);
}
}
@SuppressLint("MissingPermission")
@Override
public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) {
gatt.discoverServices();
if (mMtuListener != null) {
mMtuListener.onMtuChange(gatt, mtu, status);
}
}
@Override
public void onServiceChanged(@NonNull BluetoothGatt gatt) {
if (mConnectListener != null) {
mConnectListener.onServicesChange(gatt);
}
}
}
遇到的问题
1.
问题:
蓝牙扫描有时候会发现不了设备,多次调用BluetoothLeScanner.startScan后可正常找到。
解决方案:
为了友好型考虑,开启一个循环Timer,每3秒开启一次蓝牙扫描,如找到设备或扫描次数超过3次,则停止循环。
private int mScanCount = 0; // 扫描次数
public void startScanBleTask() {
stopScanBle();
mBleCurrentInfo.clearBleResult();
mScanCount = 0;
TimerUtil.startTask(new TimerTask() {
@Override
public void run() {
if (!mBleCurrentInfo.getBleList().isEmpty() || mScanCount > 0) {
TimerUtil.cancelTimer();
return;
}
startScanBle();
mScanCount++;
}
}, 0L, 3000L);
}
问题:
蓝牙连接一段时间后,中心设备Notify属性接收不到外围设备发送的消息,即便断线重连也无法再次受到。
解决方案:
断开设备后,进行重新扫描,再次连接时,可接收到Notify的消息。若设备需保持长期通讯,可与外围设备建立心跳协议,当中心设备超时未接收到心跳时,进行断线
->扫描->连接操作。
private Thread mHeartThread = null; // 心跳线程
private boolean mIsHeart = false; // 心跳线程运行状态
private long mHeartOverTime = 3000L; // 心跳超时时间
private long mHeartTime = 0L;
private Thread mConnectThread = null; // 重连线程
private boolean mIsRunning = false; // 重连线程运行状态
long mConnectOverTime = 20 * 1000L; // 重连扫描超时时间
/**
* 开启心跳线程
*/
public void startHeartThread() {
Log.i(TAG, "开启心跳线程");
getHeardThread();
mHeartThread.start();
}
private void getHeardThread() {
stopHeartThread();
mIsHeart = true;
mHeartThread = new Thread(new Runnable() {
@Override
public void run() {
mHeartTime = System.currentTimeMillis();
while (mIsHeart) {
if (System.currentTimeMillis() - mHeartTime > mHeartOverTime) {
Log.i(TAG, "心跳超时");
disConnectGatt(true);
}
}
}
});
}
/**
* 关闭心跳线程
*/
public void stopHeartThread() {
mIsHeart = false;
try {
if (mHeartThread != null) {
mHeartThread.join();
mHeartThread = null;
}
} catch (InterruptedException i) {
Log.i(TAG, i.toString());
}
}
/**
* 开启重连线程
*/
public void startConnectThread() {
Log.i(TAG, "开启重连线程");
getConnectThread();
mConnectThread.start();
}
private void getConnectThread() {
mIsHeart = false;
stopConnectThread();
mIsRunning = true;
mConnectThread = new Thread(new Runnable() {
@Override
public void run() {
startScanBleTask();
long time = System.currentTimeMillis();
Log.i(TAG, "开始尝试重连");
while (mIsRunning && System.currentTimeMillis() - time < mConnectOverTime) {
List beans = new ArrayList<>(mBleCurrentInfo.getBleList());
for (BleDeviceBean bean: beans) {
try {
if (Objects.equals(bean.getBleAddress(), mBleCurrentInfo.getBleAddress())) {
Log.i(TAG, "找到重连address:" + mBleCurrentInfo.getBleAddress());
int count = 0;
Log.i(TAG, "第${count + 1}次尝试连接");
connectGatt(getRemoteDevice(bean.getBleAddress()));
count++;
long currentTime = System.currentTimeMillis();
while (count < 3) {
if (!mIsRunning) {
return;
}
if (mBleCurrentInfo.isConnect()) {
break;
}
if (System.currentTimeMillis() - currentTime < 5000) {
continue;
}
Log.i(TAG, "第${count + 1}次尝试连接");
connectGatt(getRemoteDevice(bean.getBleAddress()));
currentTime = System.currentTimeMillis();
count++;
}
mIsRunning = false;
break;
}
} catch (Exception e) {
Log.i(TAG, e.toString());
}
}
}
}
});
}
/**
* 关闭重连线程
*/
public void stopConnectThread() {
mIsRunning = false;
try {
if (mConnectThread != null) {
mConnectThread.join();
mConnectThread = null;
}
} catch (InterruptedException i) {
Log.i(TAG, i.toString());
} finally {
stopScanBle();
}
}