安卓BLE蓝牙通讯

蓝牙测试demo
简介
  Android手机间通过蓝牙方式进行通信,有两种常见的方式,一种是socket方式(传统蓝牙),另一种是通过GATT(BLE蓝牙)。与传统蓝牙相比,BLE 旨在大幅降低功耗。这样一来,应用就可以与功率要求更严格的 BLE 设备(如近程传感器、心率监测器和健身设备)进行通信。

实现
1.权限
  如需使用BLE蓝牙 API,需要在AndroidManifest.xml清单文件中声明多项权限,并动态获取相应权限。一旦应用获得使用蓝牙的权限,应用就需要访问 BluetoothAdapter 并确定设备是否支持蓝牙。如果蓝牙可用,设备将扫描附近的 BLE 设备。发现设备后,系统会连接到 BLE 设备上的 GATT 服务器来发现 BLE 设备的功能。建立连接后,可以根据可用的服务和特征通过已连接的设备传输数据。
  在高版本的安卓设备中,除蓝牙基本权限外,还需用到位置权限,否则会扫描不到蓝牙。
  AndroidManifest.xml文件如下:

    <uses-permission android:name="android.permission.BLUETOOTH" />
    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
    <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
    <uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />

动态权限申请如下,可放在MainActivity中。

    // todo 动态申请权限
    private void initPermission() {
        List<String> 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<ScanFilter> 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<ScanResult> 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<ScanFilter> 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<BleDevice> 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<BluetoothGattService> 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<BluetoothGattCharacteristic> 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<ScanFilter> mScanFilterList = new ArrayList<>();

    private final ScanSettings mScanSettings = new ScanSettings.Builder().build();

    private BleCurrentInfo mBleCurrentInfo = new BleCurrentInfo();  // 已保存的蓝牙设备信息

    private List<BluetoothGattCharacteristic> 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<BleDeviceBean> 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<ScanResult> 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<BluetoothGattService> 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<BluetoothGattCharacteristic> 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<BleDevice> 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<BleDevice> getConnectedBleDeviceList() {
        return mConnectedBleDeviceList;
    }

    public void setConnectedBleDeviceList(ArrayList<BleDevice> 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<ScanFilter> 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<BleDevice> 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<BleDeviceBean> 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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/879545.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

iKuai使用及设置流程

iKuai使用及设置流程 iKuai安装步骤 一、配置主机 1.电脑连接ETH0网口 2.ETH1网口连接猫上面的千兆口 3.手动配置pc的IP地址和192.168.1.1./24在同一网段 3.浏览器输入192.168.1.1 admin admin 二、外网设置 1.直接联通电信网络设置 2.点击 网络设置-内外网设置-点击接…

Python “字符串操作” ——Python面试100道实战题目练习,巩固知识、检查技术、成功就业

本文主要是作为Python中列表的一些题目&#xff0c;方便学习完Python的元组之后进行一些知识检验&#xff0c;感兴趣的小伙伴可以试一试&#xff0c;含选择题、判断题、实战题、填空题&#xff0c;答案在第五章。 在做题之前可以先学习或者温习一下Python的列表&#xff0c;推荐…

食品检测与分类系统源码分享

食品检测与分类检测系统源码分享 [一条龙教学YOLOV8标注好的数据集一键训练_70全套改进创新点发刊_Web前端展示] 1.研究背景与意义 项目参考AAAI Association for the Advancement of Artificial Intelligence 项目来源AACV Association for the Advancement of Computer V…

推荐10款最佳的电脑监控软件,知名电脑监控软件推荐

随着互联网和科技的飞速发展&#xff0c;电脑监控软件成为企业和个人用户管理和保护信息安全的必备工具。这些软件可以帮助你实时了解电脑的使用情况、保护隐私、优化工作效率&#xff0c;甚至防止潜在的安全威胁。在这篇文章中&#xff0c;我们将为你推荐10款最佳的电脑监控软…

iPhone 16系列:摄影艺术的全新演绎,探索影像新境界

在科技的浪潮中&#xff0c;智能手机摄影功能的进化从未停歇。 苹果公司即将推出的iPhone 16系列&#xff0c;以其卓越的相机升级和创新特性&#xff0c;再次站在了手机摄影的前沿。 从硬件到软件&#xff0c;从拍照体验到图像处理&#xff0c;iPhone 16系列都展现了其在移动…

1×4矩阵键盘详解(STM32)

目录 一、介绍 二、传感器原理 工作原理介绍 三、程序设计 main.c文件 1x4key.h文件 1x4key.c文件 四、实验效果 五、资料获取 项目分享 一、介绍 矩阵键盘是单片机外部设备中所使用排布类似于矩阵键盘组&#xff0c;矩阵式结构的键盘会比独立键盘复杂一点&#xff…

国内外ChatGPT网站集合,无限制使用【2024-09最新】~

经过我一年多以来&#xff0c;使用各种AI工具的体验&#xff0c;我收集了一批AI工具和站点 这些工具都是使用的最强最主流的模型&#xff0c;也都在各个领域里都独领风骚的产品。 而且&#xff0c;这些工具你都可以无限制地使用。 无论你是打工人、科研工作者、学生、文案写…

Python 数学建模——傅里叶变换时间序列分析

文章目录 前言原理Python 库函数实现单周期函数多周期函数真实数据挑战 前言 在数学建模过程中&#xff0c;得到一个序列 x 1 , ⋯ , x n x_1,\cdots,x_n x1​,⋯,xn​&#xff0c;我们首先要进行数据分析&#xff0c;其中就包括分析数据的周期性。这里的周期性不是数学上严格…

逆向学习系列(三)adb的使用

由于是记录学习&#xff0c;我就用结合自己的理解&#xff0c;用最通俗的语言进行讲解。 adb是android debug bridge的简写&#xff0c;其作用就是将电脑和手机相连接&#xff0c;用电脑控制手机。 一、adb哪里来 我使用的adb一般都是安装模拟器的时候&#xff0c;模拟器自带…

深入探索Android开发之Java核心技术学习大全

Android作为全球最流行的移动操作系统之一&#xff0c;其开发技能的需求日益增长。本文将为您介绍一套专为Android开发者设计的Java核心技术学习资料&#xff0c;包括详细的学习大纲、PDF文档、源代码以及配套视频教程&#xff0c;帮助您从Java基础到高级特性&#xff0c;再到A…

Basler 相机与LabVIEW进行集成

Basler 提供的相机驱动和 SDK (Software Development Kit) 允许用户通过 LabVIEW 对相机进行控制和图像采集。以下是 Basler 相机与 LabVIEW 集成的几种方式&#xff1a; 1. Baslers Pylon SDK Basler 提供的 Pylon SDK 是一套用于控制 Basler 相机的开发工具包&#xff0c;支…

13 Midjourney从零到商用·实战篇:漫画设计一条龙

大家好&#xff0c;经过前面十三篇文章,相信大家已经对Midjourney的使用非常熟悉了&#xff0c;那么现在我们开始进行实际的项目操作啦&#xff0c;想想是不是有点小激动呀&#xff0c;本篇文章为大家带来Midjourney在漫画制作领域的使用流程&#xff0c;同样也适用于现在短视频…

2024.9.12(k8s环境搭建2)

一、接9.11 19、部署calico的pod 4. 查看容器和节点状态 异常处理&#xff1a; 出现Init:0/3&#xff0c;查看node节点 /var/log/messages是否有除网络异常之外的报错信息 三台机器执行&#xff1a;&#xff08;更新版本&#xff09; yum list kernel yum update kernel reb…

基于JavaWeb开发的java+Springboot操作系统教学交流平台详细设计实现

基于JavaWeb开发的javaSpringboot操作系统教学交流平台详细设计实现 &#x1f345; 作者主页 网顺技术团队 &#x1f345; 欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; &#x1f345; 文末获取源码联系方式 &#x1f4dd; &#x1f345; 查看下方微信号获取联系方式 承接…

云计算实训50——Kubernetes基础命令、常用指令

一、Kubernetes 自动补齐 # 安装自动补齐软件 [rootmaster ~]# yum -y install bash-completion # 临时开启自动补齐功能 [rootmaster ~]# source # 永 久开启自动补齐功能 [rootmaster ~]# echo "source > ~/.bashrc 二、Kubernetes 基础命令 kubectl [command] …

国产化中间件正在侵蚀开源中间件

开源中间件的发展趋势表明&#xff0c;它们将继续在技术创新和生态建设中发挥重要作用&#xff0c;尤其是在云计算、大数据等新兴技术领域。开源中间件如Apache Kafka、RabbitMQ、ActiveMQ和RocketMQ等在市场上有着广泛的应用。它们在技术社区中得到了良好的支持&#xff0c;并…

计算机网络30——Linux-gdb调试命令makefile

1、开始调试 编译时带-g为调试&#xff0c;带调试信息编译后的可执行文件更大 2、进入调试 使用gdb 可执行文件名——进入调试 失败版&#xff1a; 成功版&#xff1a; 3、l命令 l什么都不加——列出10行代码 l 行号——行号的行在中间&#xff0c;向上向下展示10行 4、st…

【经典文献】双边曲面去噪

文章目录 2003 TOG基本思想效果 2003 TOG 2003年&#xff0c;Fleishman等人在TOG上&#xff0c;基于图像双边滤波的思想&#xff0c;将其改造成了可以用在曲面上的双边滤波算法。 Fleishman S, Drori I, Cohen-Or D. Bilateral mesh denoising[M]//ACM SIGGRAPH 2003 Papers.…

Docker本地部署Chatbot Ollama搭建AI聊天机器人并实现远程交互

文章目录 前言1. 拉取相关的Docker镜像2. 运行Ollama 镜像3. 运行Chatbot Ollama镜像4. 本地访问5. 群晖安装Cpolar6. 配置公网地址7. 公网访问8. 固定公网地址 前言 本文主要分享如何在群晖NAS本地部署并运行一个基于大语言模型Llama 2的个人本地聊天机器人并结合内网穿透工具…

大模型教程:使用 Milvus、vLLM 和 Llama 3.1 搭建 RAG 应用

vLLM 是一个简单易用的 LLM 推理服务库。加州大学伯克利分校于 2024 年 7 月将 vLLM 作为孵化项目正式捐赠给 LF AI & Data Foundation 基金会。欢迎 vLLM 加入 LF AI & Data 大家庭&#xff01;&#x1f389; 在主流的 AI 应用架构中&#xff0c;大语言模型&#xff…