前言,因为项目要开发一款BLE的测试工具,写入数据没有问题,但是发现要读取服务器返回消息时,接口返回两种数据,即写入服务器的数据和服务器返回的数据,因为写入服务器数据和返回的数据有可能是一模一样的,所以此时无法分离出服务器的数据。本文主要讲自己遇到的坑,希望能帮助一些人。

以下是解决方法:

一、设置特征值通知。

在BluetoothGattCallback.onServicesDiscovered回调函数中获取到有效的读、写UUID后,设置开启已连接的BluetoothGatt特征值通知,具体代码如下:

// 监听write事件回调,获取的有效UUID
if (write_UUID_service != null && write_UUID_chara != null) {
BluetoothGattService service = bluetoothGatt.getService(write_UUID_service);
	if (service != null) {
		BluetoothGattCharacteristic chara = service.getCharacteristic(write_UUID_chara);
		if (chara != null) {
			if (Build.VERSION.SDK_INT > 30 &&
					ActivityCompat.checkSelfPermission(mContext, Manifest.permission.BLUETOOTH_CONNECT)
							!= PackageManager.PERMISSION_GRANTED) {
				Toast.makeText(mContext, "没有 BLUETOOTH_CONNECT 权限 onServicesDiscovered() 调用失败", Toast.LENGTH_LONG).show();
				return;
			}
			// 就目前项目来看,读、写、通知的UUID都一样,所以设置通知只要设置一个就行。
			// 开启或关闭特征值的通知(第二个参数为true表示开启),就目前项目来看,只要这一句就行。
			boolean b = bluetoothGatt.setCharacteristicNotification(chara, true);
			// 增加设置writeDescriptor会收到更多的数据,保险起见,设置一下更好。
			if (b) {
				List<BluetoothGattDescriptor> descriptorList = chara.getDescriptors();
				if (descriptorList != null && descriptorList.size() > 0) {
					for (BluetoothGattDescriptor descriptor : descriptorList) {
						boolean b1 = descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
						if (b1) {
							Log.d(TAG, "write chara descriptor uuid=" + descriptor.getUuid());
							bluetoothGatt.writeDescriptor(descriptor);
						}
					}
				}
			}
			Log.d(TAG, "onServicesDiscovered 设置写通知 " + b);
		}
	}
}

二、在特征值写回调和读回调中,得到写和读的数据。 

获取写入到服务器的数据。此回调执行后,表示数据已经写入服务器成功,可判断写入是否成功。

代码位置为BluetoothGattCallback.onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status)方法中。

@Override
public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
	super.onCharacteristicWrite(gatt, characteristic, status);
	Log.d(TAG, "onCharacteristicWrite gatt=" + gatt + ", characteristic value="
			+ characteristic.getValue() + ", status=" + status);
	// 写入数据成功
	if (status == BluetoothGatt.GATT_SUCCESS) {
		//postCharacteristicWriteValue(characteristic.getValue());
	}
}

重点:要获取服务器返回的数据,只能在


onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic)中获取,此函数只有两个参数;另一个回调函数:onCharacteristicChanged(@NonNull BluetoothGatt gatt, @NonNull BluetoothGattCharacteristic characteristic, @NonNull byte[] value)不会触发。测试机安卓版本为10/11。


@Override
public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
	super.onCharacteristicChanged(gatt, characteristic);
	Log.d(TAG, "onCharacteristicChanged 2 gatt=" + gatt
			+ ", characteristic=" + characteristic
			+ ", value=" + characteristic.getValue());
	// 记住这是只有两个参数的回调,另一个3参数的回调没有触发
	// postCharacteristicReadValue(characteristic.getValue());
}

三、BLE客户端管理类及其相关代码。

    1)IBleClient Ble客户端管理器的接口类

import android.content.Context;
import androidx.annotation.NonNull;

public interface IBleClient {
    // 初始化
    boolean init(@NonNull Context context);

    // 解注释
    void deInit();

    // 设置蓝牙回调时间
    void setBleCallback(@NonNull BleClientCallback callback);

    // 开始扫描蓝牙设备
    void startScan();

    // 停止扫描蓝牙设备
    void stopScan();

    /**
     * 连接蓝牙设备
     * 如果服务未开启或者地址为空则返回false
     * 如果地址存在,是否连接成功取决于蓝牙底层
     *
     * @param address 蓝牙设备地址
     * @return 是否连接成功
     **/
    boolean connectDevice(String address);

    /**
     * 断开蓝牙连接
     **/
    boolean disconnectDevice();

    /**
     * 向服务器写入数据
     **/
    void sendMessage(byte[] msg);
}

 2)BleClientCallback Ble客户端管理器的回调类,返回给调用者一些信息。

import android.bluetooth.le.ScanResult;

import androidx.annotation.IntDef;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

public interface BleClientCallback {
    int BLE_STATE_UNKNOW = 0;
    // 正在扫描
    int BLE_STATE_SCAN = 1;

    int BLE_STATE_CONNECTION = 3;
    int BLE_STATE_DISCONNECTION = 4;
    int BLE_STATE_ERROR = 99;

    int BLE_STATE_OPENED = 5;

    int BLE_ERR_SUPPORT_NO = 11;
    int BLE_ERR_OPEN_NO = 12;
    int BLE_ERR_SCAN_FAILED = 13;

    @Retention(RetentionPolicy.SOURCE)
    @IntDef({BLE_STATE_UNKNOW, BLE_STATE_SCAN, BLE_STATE_CONNECTION, BLE_STATE_DISCONNECTION,
            BLE_STATE_ERROR, BLE_STATE_OPENED})
    @interface BleState {
    }

    @Retention(RetentionPolicy.SOURCE)
    @IntDef({BLE_ERR_SUPPORT_NO, BLE_ERR_OPEN_NO, BLE_ERR_SCAN_FAILED})
    @interface BleErr {

    }

    void onBleStateChanged(@BleClientCallback.BleState int state);

    void onBleError(@BleClientCallback.BleErr int code);

    // 收到扫描结果
    void onReceiveScanResult(ScanResult result);

    // 收到返回的BluetoothGattCharacteristic消息
    void onCharacteristicValueRead(byte[] value);

    // 客户端的消息已写入服务端
    void onCharacteristicWriteSuccess(String value);
}

    3) ByteUtils.java 字节相关的工具类

//package xxx

import android.text.TextUtils;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Locale;

/**
 * Byte转换工具
 *
 * @author yangle
 */
public class ByteUtils {
    private final static char[] digits = "0123456789ABCDEF".toCharArray();
    // 判断字符串是否是16进制的
    private final static String hexRegex = "^[A-Fa-f0-9]+$";

    /**
     * byte[]转十六进制字符串
     *
     * @param array byte[]
     * @return 十六进制字符?
     */
    public static String byteArrayToHexString(byte[] array) {
        if (array == null) {
            return "";
        }
        StringBuilder buffer = new StringBuilder();
        for (byte b : array) {
            buffer.append(byteToHex(b));
        }
        return buffer.toString();
    }

    /**
     * byte转十六进制字
     *
     * @param b byte:0-255,无符号
     * @return 十六进制字符, 长度=2
     */
    public static String byteToHex(byte b) {
        String hex = Integer.toHexString(b & 0xFF);
        if (hex.length() == 1) {
            hex = '0' + hex;
        }
        return hex.toUpperCase(Locale.getDefault());
    }

    /**
     * 高效写法 16进制字符串转成byte数组
     *
     * @param hex 16进制字符串,支持大小写
     * @return byte数组
     */
    public static byte[] hexStringToBytes(String hex) {
        if (TextUtils.isEmpty(hex)) {
            return null;
        }
        byte[] result = new byte[hex.length() / 2];
        char[] chars = hex.toCharArray();
        for (int i = 0, j = 0; i < result.length; i++) {
            result[i] = (byte) (toByte(chars[j++]) << 4 | toByte(chars[j++]));
        }
        return result;
    }

    private static int toByte(char c) {
        if (c >= '0' && c <= '9')
            return (c - '0');
        if (c >= 'A' && c <= 'F')
            return (c - 'A' + 0x0A);
        if (c >= 'a' && c <= 'f')
            return (c - 'a' + 0x0a);
        throw new RuntimeException("invalid hex char '" + c + "'");
    }

    /**
     * 高效写法 byte数组转成16进制字符串
     *
     * @param bytes byte数组
     * @return 16进制字符串
     */
    public static String bytesToHexString(byte[] bytes) {
        char[] buf = new char[bytes.length * 2];
        int c = 0;
        for (byte b : bytes) {
            buf[c++] = digits[(b >> 4) & 0x0F];
            buf[c++] = digits[b & 0x0F];
        }
        return new String(buf);
    }

    /**
     * 判断字符串是否是16进制的, 空字符串也返回true
     **/
    public static boolean isHexStringAllowEmpty(String str) {
        if (str != null) {
            if (str.length() > 0) {
                return str.matches(hexRegex);
            } else {
                return true;
            }
        }
        return false;
    }

    // 从byte数组的index处的连续两个字节获得一个short,大端字节数组
    public static short getShortFromBigEndian(byte[] arr, int index) {
        if (arr == null || arr.length <= 0 || index < 0 || (index + 1) >= arr.length) {
            return 0;
        }
        return (short) (0xff00 & arr[index] << 8 | (0xff & arr[index + 1]));
    }

    // 从byte数组的index处的连续两个字节获得一个short,大端字节数组
    public static short getShortFromBigEndian(byte[] arr) {
        if (arr == null || arr.length <= 0) {
            return 0;
        }
        return ByteBuffer.wrap(arr).order(ByteOrder.BIG_ENDIAN).getShort();
    }

    // 小端字节数组转short
    public static short getShortFromLittleEndian(byte[] bytes) {
        if (bytes == null || bytes.length <= 0) {
            return 0;
        }
        return ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN).getShort();
    }

    /**
     * int转十六进制字符串
     *
     * @param i 整数
     * @return 十六进制字符,长度为8,大端
     */
    public static String intToHexBig(int i) {
        String hex = Integer.toHexString(i);
        // 补0,补成偶数位
        String hexPrefix = "";
        if (hex.length() % 2 != 0) {
            hexPrefix = "0";
        }
        return (hexPrefix + hex).toUpperCase(Locale.getDefault());
    }

    /**
     * int转十六进制字符串
     *
     * @param i 整数
     * @return 十六进制字符,长度为8,小端
     */
    public static String intToHexLittle(int i) {
        byte[] result = intToByte4Little(i);
        return byteArrayToHexString(result);
    }

    /**
     * byte数组转换为int
     * 大端方式将4字节转整型
     *
     * @param bytes byte数组
     * @param off   开始位置
     * @return int 整数
     */
    public static int byte4ToIntBig(byte[] bytes, int off) {
        if (bytes == null || bytes.length <= 0 || off < 0 || (off + 3) >= bytes.length) {
            return 0;
        }
        int b0 = bytes[off] & 0xFF;
        int b1 = bytes[off + 1] & 0xFF;
        int b2 = bytes[off + 2] & 0xFF;
        int b3 = bytes[off + 3] & 0xFF;
        return (b0 << 24) | (b1 << 16) | (b2 << 8) | b3;
    }

    /**
     * byte数组转换为int
     * 小端方式将4字节转整型
     *
     * @param bytes byte数组
     * @param off   开始位置
     * @return int 整数
     */
    public static int byte4ToIntLittle(byte[] bytes, int off) {
        if (bytes == null || bytes.length <= 0 || off < 0 || (off + 3) >= bytes.length) {
            return 0;
        }
        int b0 = bytes[off] & 0xFF;
        int b1 = bytes[off + 1] & 0xFF;
        int b2 = bytes[off + 2] & 0xFF;
        int b3 = bytes[off + 3] & 0xFF;
        return (b3 << 24) | (b2 << 16) | (b1 << 8) | b0;
    }

    /**
     * int整数转换为4字节的byte数组(大端)
     *
     * @param i 整数
     * @return byte数组
     */
    public static byte[] intToByte4Big(int i) {
        byte[] targets = new byte[4];
        targets[3] = (byte) (i & 0xFF);
        targets[2] = (byte) (i >> 8 & 0xFF);
        targets[1] = (byte) (i >> 16 & 0xFF);
        targets[0] = (byte) (i >> 24 & 0xFF);
        return targets;
    }

    /**
     * int整数转换为4字节的byte数组(小端)
     *
     * @param i 整数
     * @return byte数组
     */
    public static byte[] intToByte4Little(int i) {
        byte[] targets = new byte[4];
        targets[0] = (byte) (i & 0xFF);
        targets[1] = (byte) (i >> 8 & 0xFF);
        targets[2] = (byte) (i >> 16 & 0xFF);
        targets[3] = (byte) (i >> 24 & 0xFF);
        return targets;
    }

    private static String toHexUtil(int n) {
        String rt = "";
        switch (n) {
            case 10:
                rt += "A";
                break;
            case 11:
                rt += "B";
                break;
            case 12:
                rt += "C";
                break;
            case 13:
                rt += "D";
                break;
            case 14:
                rt += "E";
                break;
            case 15:
                rt += "F";
                break;
            default:
                rt += n;
        }
        return rt;
    }

    private static String toHex(int n) {
        StringBuilder sb = new StringBuilder();
        if (n / 16 == 0) {
            return toHexUtil(n);
        } else {
            String t = toHex(n / 16);
            int nn = n % 16;
            sb.append(t).append(toHexUtil(nn));
        }
        return sb.toString();
    }

    /**
     * ascii码(字符串,仅限英文字符)转16进制字符串
     **/
    public static String asciiStrToHexStr(String str) {
        StringBuilder sb = new StringBuilder();
        byte[] bs = str.getBytes();
        for (int i = 0; i < bs.length; i++)
            sb.append(toHex(bs[i]));
        return sb.toString();
    }

    /**
     * 16进制字符串转ascii字符串
     **/
    public static String hexStrToAsciiStr(String hex) {
        StringBuilder sb = new StringBuilder();
        StringBuilder temp = new StringBuilder();
        // 49204c6f7665204a617661 split into two characters 49, 20, 4c...
        for (int i = 0; i < hex.length() - 1; i += 2) {
            // grab the hex in pairs
            String output = hex.substring(i, (i + 2));
            // convert hex to decimal
            int decimal = Integer.parseInt(output, 16);
            // convert the decimal to character
            sb.append((char) decimal);
            temp.append(decimal);
        }
        return sb.toString();
    }

    /**
     * ascii字符串转字节数组 ascii to bytes
     **/
    public static byte[] asciiToBytes(String ascii) {
        char[] ch = ascii.toCharArray();
        byte[] tmp = new byte[ch.length];
        for (int i = 0; i < ch.length; i++) {
            tmp[i] = (byte) Integer.valueOf(ch[i]).intValue();
        }
        return tmp;
    }

    /**
     * bytes to ascii
     **/
    public static String bytesToAscii(byte[] bytes) {
        String s = "";
        try {
            s = new String(bytes, "ascii");
        } catch (UnsupportedEncodingException e) {
            System.out.println("bytesToAscii error=" + e.getMessage() + ", bytes=" + bytes);
            e.printStackTrace();
        }
        return s;
    }

    /**
     * 是否是合法的ascii码
     **/
    public static boolean isValidAsciiString(String string) {
        try {
            String hex = asciiStrToHexStr(string);
            ByteUtils.hexStringToBytes(hex);
            return true;
        } catch (Exception e) {
            System.out.println("isValidAsciiString error=" + e.getMessage() + ", string=" + string);
            e.printStackTrace();
        }
        return false;
    }

    // byte 与 int 的相互转换
    public static byte intToByte(int x) {
        return (byte) x;
    }

    // byte 与 int 的相互转换 , byte转无符号整型
    public static int byteToInt(byte b) {
        // Java 总是把 byte 当做有符处理;我们可以通过将其和 0xFF 进行二进制与得到它的无符值
        return b & 0xFF;
    }

    public static byte[] unsignedShortToByte2(int s) {
        byte[] targets = new byte[2];
        targets[0] = (byte) (s >> 8 & 0xFF);
        targets[1] = (byte) (s & 0xFF);
        return targets;
    }

    /**
     * 大端、小端方式, short值转2字节数据
     **/
    public static void putShort(byte b[], short s, boolean isBigEdian) {
        if (b == null || b.length <= 0 || b.length > 2) {
            return;
        }
        if (!isBigEdian) {
            b[1] = (byte) (s >> 8);
            b[0] = (byte) (s >> 0);
        } else {
            b[1] = (byte) (s >> 0);
            b[0] = (byte) (s >> 8);
        }
    }

    /**
     * 大端、小端方式,两字节数据转short值
     * 大端序:高位字节在前,低位字节在后。
     * 小端序:低位字节在前,高位字节在后。
     **/
    public static short getShort(byte[] b, boolean isBigEndian) {
        if (b == null || b.length <= 0 || b.length > 2) {
            return 0;
        }
        if (!isBigEndian) {
            return (short) (((b[1] << 8) | b[0] & 0xff));
        } else {
            return (short) (((b[1] & 0xff) | b[0] << 8));
        }
    }

    /**
     * short类型转成两个字节(大端)的16进制字符串,长度=4
     **/
    public static String shortToHexBig(short s) {
        if (s < Short.MIN_VALUE || s > Short.MAX_VALUE) {
            return "0000";
        }
        String result = Integer.toHexString(s & 0xffff);
        // 不足4位补0
        String prefix = "";
        // short类型的数据,使用16进制表示时的长度
        int shortHexLen = Short.BYTES * 2;
        if (result.length() < shortHexLen) {
            for (int i = 0; i < shortHexLen - result.length(); i++) {
                prefix += "0";
            }
        }
        return prefix + result;
    }

    /**
     * short类型转成两个字节(小端)的16进制字符串,长度=4
     **/
    public static String shortToHexLittle(short s) {
        if (s < Short.MIN_VALUE || s > Short.MAX_VALUE) {
            return "0000";
        }
        byte[] bytes = new byte[2];
        putShort(bytes, s, false);
        return byteArrayToHexString(bytes);
    }

    /**
     * Hex字符串转byte
     *
     * @param inHex 待转换的Hex字符串
     * @return 转换后的byte
     */
    public static byte hexToByte(String inHex) {
        return (byte) Integer.parseInt(inHex, 16);
    }

    /**
     * 拼接两个字节数组
     **/
    public static byte[] concatByteArray(byte[] bytes1, byte[] bytes2) {
        if (bytes1 == null || bytes1.length <= 0 || bytes2 == null || bytes2.length <= 0)
            return null;
        try {
            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
            outputStream.write(bytes1);
            outputStream.write(bytes2);
            return outputStream.toByteArray();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * long类型转byte[] (大端)
     * @param n 长整型
     * @return 8字节数组
     */
    public static byte[] longToBytesBig(long n) {
        byte[] b = new byte[8];
        b[7] = (byte) (n & 0xff);
        b[6] = (byte) (n >> 8  & 0xff);
        b[5] = (byte) (n >> 16 & 0xff);
        b[4] = (byte) (n >> 24 & 0xff);
        b[3] = (byte) (n >> 32 & 0xff);
        b[2] = (byte) (n >> 40 & 0xff);
        b[1] = (byte) (n >> 48 & 0xff);
        b[0] = (byte) (n >> 56 & 0xff);
        return b;
    }

    /**
     * long类型转byte[] (小端)
     *
     * @param n 长整型
     * @return 8字节数组
     */
    public static byte[] longToBytesLittle(long n) {
        byte[] b = new byte[8];
        b[0] = (byte) (n & 0xff);
        b[1] = (byte) (n >> 8 & 0xff);
        b[2] = (byte) (n >> 16 & 0xff);
        b[3] = (byte) (n >> 24 & 0xff);
        b[4] = (byte) (n >> 32 & 0xff);
        b[5] = (byte) (n >> 40 & 0xff);
        b[6] = (byte) (n >> 48 & 0xff);
        b[7] = (byte) (n >> 56 & 0xff);
        return b;
    }

    /**
     * byte[]转long类型(小端)
     * @param array 8字节数组
     * @return 长整型
     */
    public static long bytesToLongLittle(byte[] array) {
        if (array == null || array.length < 8)
            return 0;
        return ((((long) array[0] & 0xff) << 0)
                | (((long) array[1] & 0xff) << 8)
                | (((long) array[2] & 0xff) << 16)
                | (((long) array[3] & 0xff) << 24)
                | (((long) array[4] & 0xff) << 32)
                | (((long) array[5] & 0xff) << 40)
                | (((long) array[6] & 0xff) << 48)
                | (((long) array[7] & 0xff) << 56));
    }

    /**
     * byte[]转long类型(大端)
     * @param array 8字节数组
     * @return 长整型
     */
    public static long bytesToLongBig(byte[] array) {
        if (array == null || array.length < 8)
            return 0;
        return ((((long) array[0] & 0xff) << 56)
                | (((long) array[1] & 0xff) << 48)
                | (((long) array[2] & 0xff) << 40)
                | (((long) array[3] & 0xff) << 32)
                | (((long) array[4] & 0xff) << 24)
                | (((long) array[5] & 0xff) << 16)
                | (((long) array[6] & 0xff) << 8)
                | (((long) array[7] & 0xff) << 0));
    }
}

4) MainHandlerUtils 推送消息到管理类的工具类

import android.os.Handler;
import android.os.Looper;

import androidx.annotation.NonNull;

public class MainHandlerUtils {
    private static MainHandlerUtils mInstance;
    private final Handler mHandler;

    private MainHandlerUtils() {
        mHandler = new Handler(Looper.getMainLooper());
    }

    public static MainHandlerUtils getInstance() {
        if (mInstance == null) {
            synchronized (MainHandlerUtils.class) {
                if (mInstance == null) {
                    mInstance = new MainHandlerUtils();
                }
            }
        }
        return mInstance;
    }

    public void post(@NonNull Runnable runnable) {
        if (mHandler != null) {
            mHandler.post(runnable);
        }
    }

    public void postDelay(@NonNull Runnable runnable, long mill) {
        if (mHandler != null) {
            mHandler.postDelayed(runnable, mill);
        }
    }
}

5) BleClientHelper 客户端管理器类

import android.Manifest;
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.BluetoothGattService;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.le.BluetoothLeScanner;
import android.bluetooth.le.ScanCallback;
import android.bluetooth.le.ScanResult;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Build;
import android.text.TextUtils;
import android.util.Log;
import android.widget.Toast;
import java.util.List;
import java.util.UUID;
import androidx.annotation.NonNull;
import androidx.core.app.ActivityCompat;

public class BleClientHelper implements IBleClient {
    private final String TAG = BleClientHelper.class.getSimpleName();

    @SuppressLint("StaticFieldLeak")
    private static BleClientHelper mInstance;
    private Context mContext;

    private BleClientCallback mCallback = null;
    private BluetoothAdapter mBluetoothAdapter;

    // 发送消息的handler
    private final MainHandlerUtils mHandler;

    private BluetoothGatt bluetoothGatt;

    // 已连接的设备地址,删除,有时候重连无法成功,所以不直接重连
//    String bleAddress = "";

    UUID read_UUID_chara = null;
    UUID read_UUID_service = null;
    UUID write_UUID_chara = null;
    UUID write_UUID_service = null;
    UUID notify_UUID_chara = null;
    UUID notify_UUID_service = null;
    UUID indicate_UUID_chara = null;
    UUID indicate_UUID_service = null;

    // 定时检测服务端返回消息的定时器,直接通过回调函数获取,故舍弃
//    private Timer readServerValueTimer;
    // 读取数据时间间隔
//    private final int repeatTime = 500;

    private BleClientHelper() {
        mHandler = MainHandlerUtils.getInstance();
    }

    public static BleClientHelper getInstance() {
        if (mInstance == null) {
            synchronized (BleClientHelper.class) {
                if (mInstance == null) {
                    mInstance = new BleClientHelper();
                }
            }
        }
        return mInstance;
    }

    @Override
    public boolean init(@NonNull Context context) {
        mContext = context;

        // check support ble ?
        boolean supportBle = context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE);
        if (!supportBle) {
            Log.d(TAG, "Devices can not support BLE");
            postError(BleClientCallback.BLE_ERR_SUPPORT_NO);
            return false;
        }

        // check open ble ?
        mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
        if (mBluetoothAdapter != null) {
            if (!mBluetoothAdapter.isEnabled()) {
                Log.d(TAG, "Ble is enable!");
                postError(BleClientCallback.BLE_ERR_OPEN_NO);
                return false;
            } else {
                postCallback(BleClientCallback.BLE_STATE_OPENED);
            }
            return true;
        }
        return false;
    }

    @Override
    public void deInit() {
//        stopReadServerMsgTimer();
        stopScan();
        disconnectDevice();
    }

    @Override
    public void startScan() {
        postCallback(BleClientCallback.BLE_STATE_SCAN);
        //注意: BLE设备地址是动态变化(每隔一段时间都会变化),而经典蓝牙设备是出厂就固定不变了
        if (mBluetoothAdapter != null) {
            if (Build.VERSION.SDK_INT > 30 &&
                    ActivityCompat.checkSelfPermission(mContext, Manifest.permission.BLUETOOTH_SCAN) != PackageManager.PERMISSION_GRANTED) {
                Toast.makeText(mContext, "没有 BLUETOOTH_SCAN 权限 startScan() 调用失败", Toast.LENGTH_LONG).show();
                return;
            }
            mBluetoothAdapter.getBluetoothLeScanner().startScan(bleScanCallback);
        }
    }

    @Override
    public void stopScan() {
        if (mBluetoothAdapter != null) {
            if (Build.VERSION.SDK_INT > 30 &&
                    ActivityCompat.checkSelfPermission(mContext, Manifest.permission.BLUETOOTH_SCAN) != PackageManager.PERMISSION_GRANTED) {
                Toast.makeText(mContext, "没有 BLUETOOTH_SCAN 权限 stopScan() 调用失败", Toast.LENGTH_LONG).show();
                return;
            }
            BluetoothLeScanner scanner = mBluetoothAdapter.getBluetoothLeScanner();
            if (scanner != null)
                scanner.stopScan(bleScanCallback);
        }
    }

    @Override
    public boolean connectDevice(String address) {
        if (mBluetoothAdapter == null || TextUtils.isEmpty(address))
            return false;

        BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(address);
        if (device == null)
            return false;
        if (Build.VERSION.SDK_INT > 30 &&
                ActivityCompat.checkSelfPermission(mContext, Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED) {
            Toast.makeText(mContext, "没有 BLUETOOTH_CONNECT 权限 connectDevice() 调用失败", Toast.LENGTH_LONG).show();
            return false;
        }
        // 之前连接过,直接重连
//        if (address.equals(bleAddress) && bluetoothGatt != null)
//            return bluetoothGatt.connect();

        bluetoothGatt = device.connectGatt(mContext, false, gattCallback);
//        bleAddress = address;
        return true;
    }

    @Override
    public boolean disconnectDevice() {
        if (Build.VERSION.SDK_INT > 30 &&
                ActivityCompat.checkSelfPermission(mContext, Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED) {
            Toast.makeText(mContext, "没有 BLUETOOTH_CONNECT 权限 disconnectDevice() 调用失败", Toast.LENGTH_LONG).show();
            return false;
        }
        if (bluetoothGatt != null/* && !TextUtils.isEmpty(bleAddress)*/) {
            bluetoothGatt.disconnect();
            // 必须关闭,防止再次连接后,onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic)
            // 同一条数据会触发两次
            bluetoothGatt.close();

            bluetoothGatt = null;
            return true;
        }
        return false;
    }

    @Override
    public void sendMessage(byte[] msg) {
        if (bluetoothGatt != null && write_UUID_service != null) {
            try {
				if (Build.VERSION.SDK_INT > 30 &&
						ActivityCompat.checkSelfPermission(mContext, Manifest.permission.BLUETOOTH_CONNECT)
								!= PackageManager.PERMISSION_GRANTED) {
					Toast.makeText(mContext, "没有 BLUETOOTH_CONNECT 权限 sendMessage() 调用失败", Toast.LENGTH_LONG).show();
					return;
				}
				BluetoothGattService service = bluetoothGatt.getService(write_UUID_service);
				if (service != null) {
					BluetoothGattCharacteristic characteristic = service.getCharacteristic(write_UUID_chara);
					if (characteristic != null) {
						characteristic.setValue(msg); //默认单次最多20个字节,需要改MTU更改发送和接收的长度
						// 调用后,触发BluetoothGattCallback#onCharacteristicWrite
						bluetoothGatt.writeCharacteristic(characteristic);
					}
				}
            } catch (Exception e) {
                Log.e(TAG, "sendMessage ERROR=" + e.getMessage());
            }
        }
    }
	
	/** 切换特征值通知开关 **/
	private void switchCharacteristicNotification(boolean enable, UUID uuidService, UUID uuidChara) {
		if (bluetoothGatt != null && uuidService != null && uuidChara != null) {
			BluetoothGattService service = bluetoothGatt.getService(uuidService);
			if (service != null) {
				BluetoothGattCharacteristic chara = service.getCharacteristic(uuidChara);
				if (chara != null) {
					if (Build.VERSION.SDK_INT > 30 &&
							ActivityCompat.checkSelfPermission(mContext, Manifest.permission.BLUETOOTH_CONNECT)
									!= PackageManager.PERMISSION_GRANTED) {
						Toast.makeText(mContext, "没有 BLUETOOTH_CONNECT 权限 onServicesDiscovered() 调用失败", Toast.LENGTH_LONG).show();
						return;
					}
					// 就目前项目来看,读、写、通知的UUID都一样,所以设置通知只要设置一个就行。
					// 开启或关闭特征值的通知(第二个参数为true表示开启),就目前项目来看,只要这一句就行。
					boolean b = bluetoothGatt.setCharacteristicNotification(chara, enable);
					// 增加设置writeDescriptor会收到更多的数据,保险起见,设置一下更好。
					if (b) {
						List<BluetoothGattDescriptor> descriptorList = chara.getDescriptors();
						if (descriptorList != null && descriptorList.size() > 0) {
							byte[] descriptorValue = enable ?
									BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE :
									BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE;
							for (BluetoothGattDescriptor descriptor : descriptorList) {
								boolean b1 = descriptor.setValue(descriptorValue);
								if (b1) {
									Log.d(TAG, "switchCharacteristicNotification write chara descriptor uuid=" + descriptor.getUuid());
									bluetoothGatt.writeDescriptor(descriptor);
								}
							}
						}
					}
					Log.d(TAG, "switchCharacteristicNotification onServicesDiscovered 设置写通知 " + b);
				}
			}
		}
	}

    private final BluetoothGattCallback gattCallback = new BluetoothGattCallback() {
        @Override
        public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) {
            super.onMtuChanged(gatt, mtu, status);
            if (status == BluetoothGatt.GATT_SUCCESS) {
                if (Build.VERSION.SDK_INT > 30 &&
                        ActivityCompat.checkSelfPermission(mContext, Manifest.permission.BLUETOOTH_CONNECT)
                                != PackageManager.PERMISSION_GRANTED) {
                    Toast.makeText(mContext, "没有 BLUETOOTH_CONNECT 权限 onConnectionStateChange() 调用失败", Toast.LENGTH_LONG).show();
                    return;
                }
                // 注意:MTU设置成功后,再去搜索服务
                // 开始发现服务之后,才有onServicesDiscovered回调
                gatt.discoverServices();
            } else {
                Toast.makeText(mContext, "onMtuChanged 设置MTU失败", Toast.LENGTH_LONG).show();
            }
        }

        @Override
        public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
            super.onConnectionStateChange(gatt, status, newState);
            if (newState == BluetoothProfile.STATE_CONNECTED) {
                postCallback(BleClientCallback.BLE_STATE_CONNECTION);
                if (Build.VERSION.SDK_INT > 30 &&
                        ActivityCompat.checkSelfPermission(mContext, Manifest.permission.BLUETOOTH_CONNECT)
                                != PackageManager.PERMISSION_GRANTED) {
                    Toast.makeText(mContext, "没有 BLUETOOTH_CONNECT 权限 onConnectionStateChange() 调用失败", Toast.LENGTH_LONG).show();
                    return;
                }
                // 注意:连接成功后设置MTU
                // 设置收发数据的最大字节数,默认20字节
                gatt.requestMtu(260);
            } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
//                stopReadServerMsgTimer();
                postCallback(BleClientCallback.BLE_STATE_DISCONNECTION);
            }
        }

        @Override
        public void onServicesDiscovered(BluetoothGatt gatt, int status) {
            super.onServicesDiscovered(gatt, status);
            // 成功后可通信
            if (status == BluetoothGatt.GATT_SUCCESS) {
                // 获得特征值的第二种办法:通过特征属性的匹配关系,寻找对应的各路特征值
                List<BluetoothGattService> gattServiceList = bluetoothGatt.getServices();
                for (BluetoothGattService gattService : gattServiceList) {
                    boolean findIt = false;
                    List<BluetoothGattCharacteristic> charaList = gattService.getCharacteristics();
                    for (BluetoothGattCharacteristic chara : charaList) {
                        read_UUID_chara = null;
                        read_UUID_service = null;
                        write_UUID_chara = null;
                        write_UUID_service = null;
                        notify_UUID_chara = null;
                        notify_UUID_service = null;
                        indicate_UUID_chara = null;
                        indicate_UUID_service = null;
                        int charaProp = chara.getProperties(); // 获取该特征的属性
                        if ((charaProp & BluetoothGattCharacteristic.PROPERTY_READ) > 0) {
                            read_UUID_chara = chara.getUuid();
                            read_UUID_service = gattService.getUuid();
                            Log.d(TAG, "read_chara=" + read_UUID_chara + ", read_service=" + read_UUID_service);
                        }
                        if ((charaProp & BluetoothGattCharacteristic.PROPERTY_WRITE) > 0) {
                            write_UUID_chara = chara.getUuid();
                            write_UUID_service = gattService.getUuid();
                            Log.d(TAG, "write_chara=" + write_UUID_chara + ", write_service=" + write_UUID_service);
                        }
                        if ((charaProp & BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE) > 0) {
                            write_UUID_chara = chara.getUuid();
                            write_UUID_service = gattService.getUuid();
                            Log.d(TAG, "no_response write_chara=" + write_UUID_chara + ", write_service=" + write_UUID_service);
                        }
                        if ((charaProp & BluetoothGattCharacteristic.PROPERTY_NOTIFY) > 0) {
                            notify_UUID_chara = chara.getUuid();
                            notify_UUID_service = gattService.getUuid();
                            Log.d(TAG, "notify_chara=" + notify_UUID_chara + ", notify_service=" + notify_UUID_service);
                        }
                        if ((charaProp & BluetoothGattCharacteristic.PROPERTY_INDICATE) > 0) {
                            indicate_UUID_chara = chara.getUuid();
                            indicate_UUID_service = gattService.getUuid();
                            Log.d(TAG, "indicate_chara=" + indicate_UUID_chara + ", indicate_service=" + indicate_UUID_service);
                        }

                        if (read_UUID_chara != null && write_UUID_chara != null && notify_UUID_chara != null) {
                            findIt = true;
                            break;
                        }
                    }
                    if (findIt)
                        break;
                }
                // 监听write事件回调
                switchCharacteristicNotification(true, write_UUID_service, write_UUID_chara);
//                // 设置读监听
//				switchCharacteristicNotification(true, read_UUID_service, read_UUID_chara);
//                if (read_UUID_service != null && read_UUID_chara != null) {
//                    // 开始读数据的定时器,只有调用readCharacteristic,再设置以上通知,才能触发onCharacteristicChanged事件
//                    startReadServerMsgTimer();
//                }
            }

            Log.d(TAG, "onServicesDiscovered gatt=" + gatt + ", status=" + status);
        }

        // 此方法不会触发,不知为何,只触发同名,3个参数的函数。
        @Override
        public void onCharacteristicRead(@NonNull BluetoothGatt gatt, @NonNull BluetoothGattCharacteristic characteristic, @NonNull byte[] value, int status) {
            super.onCharacteristicRead(gatt, characteristic, value, status);
            Log.d(TAG, "onCharacteristicRead gatt=" + gatt + ", characteristic hex value="
                    + ByteUtils.byteArrayToHexString(characteristic.getValue()) + ", status=" + status);
        }

        // 收到BLE服务端的数据写入时回调
        @Override
        public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
            super.onCharacteristicWrite(gatt, characteristic, status);
            Log.d(TAG, "onCharacteristicWrite gatt=" + gatt + ", characteristic hex value="
                    + ByteUtils.byteArrayToHexString(characteristic.getValue()) + ", status=" + status);
            if (status == BluetoothGatt.GATT_SUCCESS) {
                String writeValue = ByteUtils.byteArrayToHexString(characteristic.getValue());
                postCharacteristicWriteValue(writeValue);
            }
        }

        @Override
        public void onPhyUpdate(BluetoothGatt gatt, int txPhy, int rxPhy, int status) {
            super.onPhyUpdate(gatt, txPhy, rxPhy, status);
            Log.d(TAG, "onPhyUpdate gatt=" + gatt
                    + ", txPhy=" + txPhy
                    + ", rxPhy=" + rxPhy
                    + ", status=" + status);
        }

        @Override
        public void onPhyRead(BluetoothGatt gatt, int txPhy, int rxPhy, int status) {
            super.onPhyRead(gatt, txPhy, rxPhy, status);
            Log.d(TAG, "onPhyRead gatt=" + gatt
                    + ", txPhy=" + txPhy
                    + ", rxPhy=" + rxPhy
                    + ", status=" + status);
        }

        // 这个方法在调用bluetoothGatt.readCharacteristic方法后才有回调,且数据中有写入服务器的数据,也有服务器返回的数据
        @Override
        public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
            super.onCharacteristicRead(gatt, characteristic, status);
            Log.d(TAG, "onCharacteristicRead 2 gatt=" + gatt
                    + ", characteristic=" + characteristic
                    + ", value=" + ByteUtils.byteArrayToHexString(characteristic.getValue())
                    + ", status=" + status);
        }

        @Override
        public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
            super.onCharacteristicChanged(gatt, characteristic);
            Log.d(TAG, "onCharacteristicChanged 2 gatt=" + gatt
                    + ", characteristic=" + characteristic
                    + ", value=" + ByteUtils.byteArrayToHexString(characteristic.getValue()));
            // 记住这是只有两个参数的回调,另一个3参数的回调没有触发
            postCharacteristicReadValue(characteristic.getValue());
        }

        @Override
        public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
            super.onDescriptorRead(gatt, descriptor, status);
            Log.d(TAG, "onDescriptorRead gatt=" + gatt + ", descriptor=" + descriptor + ", status=" + status);
        }

        @Override
        public void onReliableWriteCompleted(BluetoothGatt gatt, int status) {
            super.onReliableWriteCompleted(gatt, status);
            Log.d(TAG, "onReliableWriteCompleted gatt=" + gatt + ", status=" + status);
        }

        @Override
        public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) {
            super.onReadRemoteRssi(gatt, rssi, status);
            Log.d(TAG, "onReadRemoteRssi gatt=" + gatt + ", rssi=" + rssi + ", status=" + status);
        }

        // 此方法不会触发,不知为何,只触发同名,2个参数的函数。
        @Override
        public void onCharacteristicChanged(@NonNull BluetoothGatt gatt, @NonNull BluetoothGattCharacteristic characteristic, @NonNull byte[] value) {
            super.onCharacteristicChanged(gatt, characteristic, value);
            Log.d(TAG, "onCharacteristicChanged gatt=" + gatt + ", characteristic=" + characteristic);
            String message = ByteUtils.byteArrayToHexString(characteristic.getValue()); // 把服务端返回的数据转成字符串
            Log.d(TAG, "onCharacteristicChanged characteristic hex value=" + message
                    + ", characteristic.getProperties=" + characteristic.getProperties());
//            postCharacteristicReadValue(characteristic.getValue());
        }

        @Override
        public void onDescriptorRead(@NonNull BluetoothGatt gatt, @NonNull BluetoothGattDescriptor descriptor, int status, @NonNull byte[] value) {
            super.onDescriptorRead(gatt, descriptor, status, value);
            Log.d(TAG, "onDescriptorRead gatt=" + gatt + ", status=" + status);
        }

        @Override
        public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
            super.onDescriptorWrite(gatt, descriptor, status);
            Log.d(TAG, "onDescriptorWrite gatt=" + gatt + ", status=" + status
                + ", gatt.getService(write_UUID_service).getType()" + gatt.getService(write_UUID_service).getType());
        }

        @Override
        public void onServiceChanged(@NonNull BluetoothGatt gatt) {
            super.onServiceChanged(gatt);
            Log.d(TAG, "onDescriptorWrite gatt=" + gatt);
        }
    };

//    /**
//     * 开始读服务器数据的定时器,舍弃,因为同时会读取到写入服务器和从服务器返回的两种数据
//     */
//    private void startReadServerMsgTimer() {
//        stopReadServerMsgTimer();
//        TimerTask readServerValueTimerTask = new TimerTask() {
//            @Override
//            public void run() {
//                if (Build.VERSION.SDK_INT > 30 &&
//                        ActivityCompat.checkSelfPermission(mContext, Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED) {
//                    Toast.makeText(mContext, "没有 BLUETOOTH_CONNECT 权限 startReadServerMsgTimer() 调用失败", Toast.LENGTH_LONG).show();
//                    return;
//                }
//                if (bluetoothGatt == null || read_UUID_service == null || read_UUID_chara == null)
//                    return;
//
//                BluetoothGattService service = bluetoothGatt.getService(read_UUID_service);
//                BluetoothGattCharacteristic characteristic = null;
//                if (service != null) {
//                    characteristic = service.getCharacteristic(read_UUID_chara);
//                }
//                String readHexStr = "";
//                if (characteristic != null) {
//                    // 调用后触发BluetoothGattCallback#onCharacteristicRead(3个参数的回调,四个参数的不触发),原因未知
//                    bluetoothGatt.readCharacteristic(characteristic);
//                    readHexStr = ByteUtils.byteArrayToHexString(characteristic.getValue());
//                }
//            }
//        };
//        // 第一次开始时延迟delay毫秒,且定时器每period毫秒触发一次
//        readServerValueTimer = new Timer();
//        readServerValueTimer.schedule(readServerValueTimerTask, 0, repeatTime);
//    }

    /**
     * 停止读服务器数据的定时器
     */
//    private void stopReadServerMsgTimer() {
//        if (readServerValueTimer != null) {
//            readServerValueTimer.cancel();
//        }
//        readServerValueTimer = null;
//    }

    private final ScanCallback bleScanCallback = new ScanCallback() {
        @Override
        public void onScanResult(int callbackType, ScanResult result) {
            super.onScanResult(callbackType, result);
            if (Build.VERSION.SDK_INT > 30 &&
                    ActivityCompat.checkSelfPermission(mContext, Manifest.permission.BLUETOOTH_CONNECT)
                            != PackageManager.PERMISSION_GRANTED) {
                Toast.makeText(mContext, "没有 BLUETOOTH_CONNECT 权限 onScanResult() 调用失败", Toast.LENGTH_LONG).show();
                return;
            }
            Log.d(TAG, "bleScanCallback onScanResult blue name=" + result.getDevice().getName()
                    + ", addr=" + result.getDevice().getAddress());
            postScanResult(result);
        }

        @Override
        public void onBatchScanResults(List<ScanResult> results) {
            Log.d(TAG, "bleScanCallback onBatchScanResults");
            super.onBatchScanResults(results);
        }

        @Override
        public void onScanFailed(int errorCode) {
            Log.d(TAG, "bleScanCallback onScanFailed errorCode=" + errorCode);
            super.onScanFailed(errorCode);
            postError(BleClientCallback.BLE_ERR_SCAN_FAILED);
        }
    };

    @Override
    public void setBleCallback(@NonNull BleClientCallback callback) {
        this.mCallback = callback;
    }

    private void postCallback(@BleClientCallback.BleState final int state) {
        if (mCallback != null && mHandler != null) {
            mHandler.post(() -> mCallback.onBleStateChanged(state));
        }
    }

    private void postError(@BleClientCallback.BleErr final int err) {
        if (mCallback != null && mHandler != null) {
            mHandler.post(() -> mCallback.onBleError(err));
        }
    }

    private void postCharacteristicReadValue(byte[] value) {
        if (mCallback != null && mHandler != null) {
            mHandler.post(() -> mCallback.onCharacteristicValueRead(value));
        }
    }

    private void postCharacteristicWriteValue(String value) {
        if (mCallback != null && mHandler != null) {
            mHandler.post(() -> mCallback.onCharacteristicWriteSuccess(value));
        }
    }

    private void postScanResult(ScanResult result) {
        if (mCallback != null && mHandler != null) {
            mHandler.post(() -> mCallback.onReceiveScanResult(result));
        }
    }
}