• 安卓BLE蓝牙通讯


    蓝牙测试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();
            }
        }
    

    蓝牙测试demo

  • 相关阅读:
    备战数学建模43-决策树&随机森林&Logistic模型(攻坚站7)
    ps制作花朵形状
    C# 实现 Linux 视频聊天、远程桌面(源码,支持信创国产化环境,银河麒麟,统信UOS)
    SourceTree 的使用
    Spring-@Value用法介绍
    使用Node构建私人代理池
    mac 安装java1.8
    Android子线程可以更新UI
    Python 中的 Elias Delta 编码
    微调 TrOCR – 训练 TrOCR 识别弯曲文本
  • 原文地址:https://blog.csdn.net/weixin_41119184/article/details/142101652