前言,因为项目要开发一款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 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 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 gattServiceList = bluetoothGatt.getServices();
for (BluetoothGattService gattService : gattServiceList) {
boolean findIt = false;
List 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 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));
}
}
}