Android蓝牙——手机与蓝牙设备连接及通信
前序
- 笔记基于蓝牙4.0,即低功耗蓝牙BLE。因Google在android 4.3(API Level 18)的android版本中引入了蓝牙4.0核心API,故该笔记仅支持Android4.3以上的版本。
- 蓝牙4.0是双模的,既包括经典蓝牙又包括低能耗蓝牙
- 笔记讲述手机与设备之间的连接以及通信
- 一般情况下,手机是客户端(中央设备),蓝牙(远程)设备是服务端(外围设备)。Android4.3和4.4,手机只能当客户端;Android5.0以后手机也可以当服务端,但是绝大多数还是手机当客户端,蓝牙设备是服务端。
- 笔记中的远程设备无需密钥配对
- 基本上每个公司都还会有自己的一套协议,笔记介绍的连接成功和通信成功相当于建成了一座可以通行的桥,公司的那套协议相当于在桥两头建立了收费站
目录
一.Android蓝牙BLE关键类
1.1. BluetoothAdapter
1.2. BluetoothLeScanner
1.3. BluetoothDevice
1.4. BluetoothGatt
1.5. BluetoothGattCallback
1.6. BluetoothGattService
1.7. BluetoothGattCharacteristic
1.8. BluetoothGattDescriptor
二. 蓝牙连接
2.1. 蓝牙连接——手机app开始蓝牙前的准备工作
2.2. 蓝牙连接——第一步:获取设备BluetoothDevice
2.3. 蓝牙连接——第二步:把BluetoothGattCallback对象作为connectGatt方法的参数获取BluetoothGatt
2.4 蓝牙连接——第三步:BluetoothGattCallback主要回调方法处理分析
三. 注意事项
四.完整代码
一. Android 蓝牙BLE关键类
在蓝牙4.0BLE的API里需要熟悉以下8个关键类:
BluetoothAdapter,
BluetoothLeScanner,
BluetoothDevice,
BluetoothGatt,
BluetoothGattCallback,
BluetoothGattService,
BluetoothGattCharacteristic,
BluetoothGattDescriptor
1.1.BluetoothAdapter:这个类映射了设备的蓝牙模块,蓝牙功能的使用将从它开始。
获取方式:
BluetoothAdapter.getDefaultAdapter()
(即使通过BluetoothManager来获取,但其最终还是调用BluetoothAdapter.getDefaultAdapter()获取的)
常用方法 | 介绍 |
---|---|
static boolean checkBluetoothAddress(String address) | (静态方法)检查Mac地址是否是一个有效的蓝牙地址 |
static synchronized BluetoothAdapter getDefaultAdapter() | (静态方法)获取一个默认的BluetoothAdapter对象 |
BluetoothDevice getRemoteDevice(String address) | 通过Mac地址获取到蓝牙设备BluetoothDevice |
BluetoothLeScanner getBluetoothLeScanner() | 获取BluetoothLeScanner(Android5.0以上扫描和停止扫描需要用到BluetoothLeScanner) |
boolean isEnabled() | 判断蓝牙的开启状态 |
boolean enable() | 弹出一个系统的是否打开/关闭蓝牙的对话框,选择禁止或者未处理返回false,选择允许返回true |
boolean disable() | 关闭蓝牙 |
boolean startLeScan(LeScanCallback callback) | 扫描蓝牙(仅限Android4.3和4.4) |
void stopLeScan(LeScanCallback callback) | 停止扫描(仅限Android4.3和4.4) |
...... | ...... |
BluetoothAdapter类更多方法请详见
https://developer.android.google.cn/reference/android/bluetooth/BluetoothAdapter?hl=en
1.2. BluetoothLeScanner:Android5.0以上用来扫描和停止扫描的类
其获取方式:
BluetoothAdapter.getDefaultAdapter().getBluetoothLeScanner()
常用方法 | 介绍 |
---|---|
void startScan(final ScanCallback callback) | 开始扫描 |
void stopScan(ScanCallback callback) | 停止扫描 |
void flushPendingScanResults(ScanCallback callback) | 刷新存储在蓝牙控制器中的等待批扫描结果。 |
...... | ...... |
BluetoothLeScanner类更多方法请详见https://developer.android.google.cn/reference/android/bluetooth/le/BluetoothLeScanner?hl=en
1.3. BluetoothDevice:蓝牙设备(即外围设备)
其获取方式有两种:
1.3.1 Mac地址直连
BluetoothAdapter.getDefaultAdapter().getRemoteDevice(mMacAdress);
1.3.2. 蓝牙扫描获取
Android4.3和4.4:
BluetoothAdapter#startLeScan(BluetoothAdapter.LeScanCallback)
Android5.0以上:
BluetoothLeScanner#startScan(ScanCallback)
BluetoothLeScanner#startScan(List<ScanFilter>, ScanSettings, ScanCallback)。
常用方法 | Added in API level | 介绍 |
---|---|---|
BluetoothGatt connectGatt(Context context, boolean autoConnect,BluetoothGattCallback callback) | 18(Android4.3) | 连接Gatt并返回一个BluetoothGatt,该方法是BLE连接的核心方法 |
BluetoothGatt connectGatt(Context context, boolean autoConnect, BluetoothGattCallback callback, int transport, int phy, Handler handler) | 26 Android(8.0) | 同上 |
BluetoothGatt connectGatt(Context context, boolean autoConnect, BluetoothGattCallback callback, int transport, int phy) | 26 Android(8.0) | 同上 |
BluetoothGatt connectGatt(Context context, boolean autoConnect, BluetoothGattCallback callback, int transport) | 23 Android(6.0) | 同上 |
String getName() | 获取蓝牙设备的名字 | |
ParcelUuid[] getUuids() | 获取蓝牙设备的uuid | |
String getAddress() | 获取蓝int牙设备的Mac地址 | |
int getBondState() | 获取蓝牙设备的绑定状态 | |
int getType() | 获取蓝牙设备的类型 | |
...... | ...... |
BluetoothLeScanner类更多方法请详见
https://developer.android.google.cn/reference/android/bluetooth/BluetoothDevice?hl=en
1.4.BluetoothGatt:该类是核心类
其获取方式(请看1.3 BluetoothDevice的常用方法4种connectGatt)
autoConnect 参数表示是否断后自动重连,一般都设为false
此对象是对GATT协议的封装,布尔类型参数表示是否断后重连。由于是从远程设备处获取信息,所以蓝牙设备是服务端而手机是客户端。BluetoothGatt对象可对客户端进行相关操作。
常用方法 | 介绍 | 是否必要 |
---|---|---|
boolean discoverServices() | 蓝牙连接成功之后(还不能通信)调用该方法:发现远程设备提供的服务及其特征和描述符。 | 必要 |
boolean connect() | 重新连接回远程设备,设备不在范围内后回来就可以使用该方法进行连接,true表示连接成功 | \ |
void disconnect() | 断开连接 | 必要 |
boolean writeDescriptor(BluetoothGattDescriptor descriptor) | 把描述符的值写入到关联的远程设备中,写入成功则返回true | 必要 |
boolean readDescriptor(BluetoothGattDescriptor descriptor) | 从关联的远程设备读取给定描述符的值,读取成功则返回true | \ |
boolean readCharacteristic(BluetoothGattCharacteristic characteristic) | 从关联的远程设备读取请求的特征。读取操作成功启动则为true(该方法是主动向远程设备读取设备特征值,得看远程设备允不允许,一般不用该方法) | \ |
boolean writeCharacteristic(BluetoothGattCharacteristic characteristic) | 将给定的特征及其值写入关联的远程设备。写入操作成功启动则为true (手机发送数据给远程设备,必须调用该方法) | 必要 |
boolean setCharacteristicNotification(BluetoothGattCharacteristic characteristic,boolean enable) | 设置特征值通知,enable为true则启用。设置成功返回true。(一般都使用该方法,然后远程设备数据有变化就会发出该特征通知) | 必要 |
BluetoothGattService getService(UUID uuid) | 获取Gatt中指定UUID的service | 必要 |
List<BluetoothGattService> getServices() | 获取Gatt的service列表 | \ |
BluetoothDevice getDevice() | 获取Gatt的远程设备device | \ |
void close() | 关闭BluetoothGatt,需要先调用disconnect()方法,再在BluetoothGattCallback#onConnectionStateChange里调用close()方法,再把BluetoothGatt置为null | 必要 |
...... | ...... |
BluetoothGatt类更多方法请详见
https://developer.android.google.cn/reference/android/bluetooth/BluetoothGatt?hl=en
1.5. BluetoothGattCallback:作为connectGatt方法的参数,实现BluetoothGatt的回调,非常重要
该类所有回调方法中的status都是指操作是否成功
常用方法 | 介绍 | 是否必要 |
---|---|---|
void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) | 连接状态的改变 | 必要 |
void onServicesDiscovered(BluetoothGatt gatt, int status) | 发现服务成功后回调该方法。服务的获取,特征的读取和写入,描述符的读取和写入,设置特征通知等等都在该方法里写 | 必要 |
void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) | 描述符值写入远程设备操作的结果的回调,写入成功则蓝牙可以开始通信啦 | 必要 |
void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) | 描述符读取操作的结果的回调 | \ |
void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) | 由于远程特征通知而触发的回调。远程设备特征(数据)改变就会回调该方法 | 必要 |
void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) | 指示特征写操作的结果。即手机发送数据后的回调 | 可选 |
void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic,int status) | 回调报告特征读取操作的结果。 | \ |
...... | ...... | \ |
BluetoothGattCallback类更多方法请详见
https://developer.android.google.cn/reference/android/bluetooth/BluetoothGattCallback?hl=en
1.6. BluetoothGattService:蓝牙Gatt服务,核心类
获取方式:
1.6.1. BluetoothGatt#getService(UUID uuid)
1.6.2. BluetoothGatt#getServices()
1.6.3. BluetoothGattCharacteristic#getService()
常用方法 | 介绍 | 是否必要 |
---|---|---|
BluetoothGattCharacteristic getCharacteristic(UUID uuid) | 获取此服务提供的特征列表之外具有给定UUID的特征。 | 必要 |
List<BluetoothGattCharacteristic> getCharacteristics() | 获取此服务中包含的特征列表。 | \ |
UUID getUuid() | 获取此服务的UUID | \ |
...... | ...... | \ |
BluetoothGattService类更多方法请详见
https://developer.android.google.cn/reference/android/bluetooth/BluetoothGattService?hl=en
1.7. BluetoothGattCharacteristic:特征,核心类
获取方式:
1.7.1. BluetoothGattDescriptor#getCharacteristic()
1.7.2. BluetoothGattService#getCharacteristic(UUID uuid)BluetoothGattDescriptor
常用方法 | 介绍 | 是否必要 |
---|---|---|
BluetoothGattDescriptor getDescriptor(UUID uuid) | 返回此特性的描述符列表之外具有给定UUID的描述符。 | 必要 |
List<BluetoothGattDescriptor> getDescriptors() | 返回此特征的描述符列表。 | \ |
UUID getUuid() | 返回此特征的UUID | \ |
byte[] getValue() | 获取该特性的存储值。 | 必要 |
boolean setValue(String value) | 设置该特性的本地存储值。 | \ |
...... | ...... | \ |
BluetoothGattCharacteristic类更多方法请详见
https://developer.android.google.cn/reference/android/bluetooth/BluetoothGattCharacteristic?hl=en
1.8. BluetoothGattDescriptor:描述符,核心类
获取方式:
BluetoothGattCharacteristic#getDescriptor(UUID uuid)
常用方法 | 介绍 | 是否必要 |
---|---|---|
boolean setValue(byte[] value) | 更新此描述符的本地存储值。 | 必要 |
BluetoothGattCharacteristic getCharacteristic() | 返回此描述符所属的特征。 | \ |
UUID getUuid() | 返回此描述符的UUID。 | \ |
...... | ...... | \ |
BluetoothGattDescriptor类更多方法请详见
https://developer.android.google.cn/reference/android/bluetooth/BluetoothGattDescriptor?hl=en
一个Gatt包含多个服务;一个服务包含多个特征;一个特征包含多个描述符;
一个描述符对应一个特征;一个特征对应一个服务;一个服务对应一个Gatt
二. 蓝牙连接
2.1 蓝牙连接——手机app开始蓝牙前的准备工作
2.1.1.清单文件声明权限
<!--蓝牙权限-->
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
<!-- LE Beacons位置相关权限-->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<!--蓝牙模块 设置为true表示只有支持蓝牙的手机才能安装-->
<uses-feature
android:name="android.hardware.bluetooth_le"
android:required="true" />
2.1.2.动态申请定位权限(代码里一定要检查GPS是否打开)
动态申请权限我用了一个第三方的库,这里就不放代码了,这里放的是判断GPS是否打开的代码
/**
* 判断GPS是否开启,GPS或者AGPS开启一个就认为是开启的
*
* @return true 表示开启
*/
private boolean isOPenGps() {
LocationManager locationManager
= (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
// 通过GPS卫星定位,定位级别可以精确到街(通过24颗卫星定位,在室外和空旷的地方定位准确、速度快)
boolean gps = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER);
// 通过WLAN或移动网络(3G/2G)确定的位置(也称作AGPS,辅助GPS定位。主要用于在室内或遮盖物(建筑群或茂密的深林等)密集的地方定位)
boolean network = locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER);
if (gps || network) {
Log.d(TAG, "GPS状态:打开");
return true;
}
Log.e(TAG, "GPS状态:关闭");
return false;
}
2.1.3.蓝牙是否打开
这里写了一个判断蓝牙是否打开(包含手动打开关闭)的方法。里面先是判断当前蓝牙状态,如果处于关闭状态,则调用enable方法,则会弹出一个系统的是否打开/关闭蓝牙的对话框,禁止或者未处理返回false,允许返回true,然后再将其结果返回
/**
* 打开手机蓝牙
*
* @return true 表示打开成功
*/
public boolean enable() {
if (!getBluetoothAdapter().isEnabled()) {
//若未打开手机蓝牙,则会弹出一个系统的是否打开/关闭蓝牙的对话框,禁止或者未处理返回false,允许返回true
//若已打开手机蓝牙,直接返回true
boolean enableState = getBluetoothAdapter().enable();
Log.d(TAG, "(用户操作)手机蓝牙是否打开成功:" + enableState);
return enableState;
} else return true;
}
2.1.4.设备的uuid。
设备的蓝牙服务uuid,特征值uuid(一个或多个),描述uuid等等有关的uuid。这些uuid表找设备硬件工程师要。
设备的Mac地址正常情况下也由硬件工程师提供,这样更方便。
2.1.5.准备一个子线程,到时耗时操作操作都塞给该子线程
//子线程的HandlerThread,为子线程提供Looper
private HandlerThread workHandlerThread;
//子线程
private Handler workHandler;
private void initWorkHandler() {
workHandlerThread = new HandlerThread("BleWorkHandlerThread");
workHandlerThread.start();
workHandler = new Handler(workHandlerThread.getLooper()) {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
}
};
}
2.2.蓝牙连接——第一步:获取设备BluetoothDevice
当手机app都给了定位权限,gps打开了,蓝牙打开了,uuid啥的准备好了,子线程准备好了,我们就可以开始进行蓝牙连接了。
蓝牙连接第一步:获取目标蓝牙设备BluetoothDevice。 获取目标BluetoothDevice有两种方法:
-
Mac地址直连 获取BluetoothDevice
BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(mMacAdress);
-
扫描周围蓝牙设备(用目标Mac地址来过滤) 获取BluetoothDevice
2.2.1. 在android 4.3 和 android 4.4进行蓝牙扫描,可使用Api
//进行蓝牙扫描。
BluetoothAdapter#startLeScan(BluetoothAdapter.LeScanCallback)
//关闭蓝牙扫描。
BluetoothAdapter#stopLeScan(BluetoothAdapter.LeScanCallback)
2.2.2. 在 android 5.0之后的版本(包括 5.0)建议使用新的Api进行蓝牙扫描(Android8.0以上建议加后台允许扫描):
//进行蓝牙扫描。
BluetoothLeScanner#startScan(ScanCallback)
BluetoothLeScanner#startScan(List<ScanFilter>, ScanSettings, ScanCallback) Android8.0后台继续扫描需要使用该方法
//关闭蓝牙扫描。
BluetoothLeScanner#stopScan(ScanCallback)
2.2.3.扫描获取BluetoothDevice的API的详细使用
//打开扫描
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {//Android5.0(包括)以上扫描
BluetoothLeScanner bluetoothLeScanner = BluetoothAdapter.getDefaultAdapter().getBluetoothLeScanner();
if (bluetoothLeScanner != null)
bluetoothLeScanner.startScan(highScanCallback);
} else {//Android4.3,4.4扫描
BluetoothAdapter.getDefaultAdapter().startLeScan(lowScanCallback);
}
-----------------------------------------------------------------------------------------------------
//关闭扫描
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {//Android5.0(包括)以上关闭扫描
BluetoothLeScanner bluetoothLeScanner = BluetoothAdapter.getDefaultAdapter().getBluetoothLeScanner();
if (bluetoothLeScanner != null)
bluetoothLeScanner.stopScan(highScanCallback);
} else{//Android4.3,4.4关闭扫描
BluetoothAdapter.getDefaultAdapter().stopLeScan(lowScanCallback);
}
-----------------------------------------------------------------------------------------------------
/**
* 高版本扫描回调
* Android 5.0(API 21)(包含)以上的蓝牙回调
*/
private ScanCallback highScanCallback = new ScanCallback() {
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
@Override
public void onScanResult(int callbackType, ScanResult result) {
super.onScanResult(callbackType, result);
//根据目标蓝牙Mac地址去过滤
if (mMacAdress.equals(result.getDevice().getAddress())) {
//关闭扫描(放子线程)
//doing...
Log.d(TAG, "LeScanCallback-->" + "蓝牙扫描已找到设备,即将开始连接");
mBluetoothDevice = result.getDevice();
//开始连接(放子线程)
//doing...
}
}
@Override
public void onBatchScanResults(List<ScanResult> results) {
super.onBatchScanResults(results);
Log.d(TAG, "ScanCallback: onBatchScanResults");
}
@Override
public void onScanFailed(int errorCode) {
super.onScanFailed(errorCode);
Log.e(TAG, "ScanCallback: onScanFailed");
}
};
-----------------------------------------------------------------------------------------------------
/**
* 低版本扫描回调
* Android 4.3(API 18)(包含)以上,Android 5.0(API 21)(不包含)以下的蓝牙回调
*/
private BluetoothAdapter.LeScanCallback lowScanCallback = new BluetoothAdapter.LeScanCallback() {
@Override
public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) {
//根据目标蓝牙Mac地址去过滤
if (mMacAdress.equals(device.getAddress())) {
//关闭扫描(放子线程)
//doing...
Log.d(TAG, "LeScanCallback-->" + "蓝牙扫描已找到设备,即将开始连接");
mBluetoothDevice = result.getDevice();
//开始连接(放子线程)
//doing...
}
}
};
注意:1. 扫描到目标设备后,要关闭蓝牙扫描,耗电。2. 扫描和关闭扫描都是耗时操作,需要放在子线程中
2.3. 蓝牙连接——第二步:把BluetoothGattCallback对象作为connectGatt方法的参数获取BluetoothGatt
通过目标BluetoothDevice的connectGatt方法进行连接。并将BluetoothGattCallback作为connectGatt方法的参数
BluetoothDevice#connectGatt方法:连接到由该设备托管的GATT服务器,该方法的返回类型为BluetoothGatt。
connect()和connectGatt区别:都是连接BLE设备的方法,但二者用法不同。
connectGatt是BluetoothDevice类下的方法,功能是向BLE设备发起连接,然后得到一个BluetoothGatt类型的返回值,利用这个返回值可以进行下一步操作。
connect是BluetoothGatt类下的方法,功能是re-connect,重新连接。如果BLE设备和APP已经连接过,但是因为设备超出了蓝牙的连接范围而断掉,那么当设备重新回到连接范围内时,可以通过connect()重新连接。
2.3.1. 新建一个BluetoothGattCallback,将其作为connectGatt的回调参数,非常核心
Android18以上所有版本通用:
BluetoothDevice#connectGatt(Context context, boolean autoConnect,BluetoothGattCallback callback)
Android23以上版本建议:
BluetoothDevice#connectGatt(Context context, boolean autoConnect, BluetoothGattCallback callback, int transport)
2.4 蓝牙连接——第三步:BluetoothGattCallback主要回调方法处理分析
方法:
2.4.1.1 status为GATT_SUCCESS时:说明操作成功(如连接成功这个操作)。然后判断newState取值(四种:连接中,已连接,断开连接中,已断开连接a),我们监听已连接和已断开两种状态就可以了,
2.4.1.1.1 newState为已连接时,我们就开g始g发现服务即调用gatt的discoverServices方法。
2.4.1.2 status为错误的133时:调用gatt的disconnect方法,然后在onConnectionStateChange方法里调用Gatt的close方法并置空gatt并重新开始使用connectGatt方法进行连接
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
super.onConnectionStateChange(gatt, status, newState);
if (status == BluetoothGatt.GATT_SUCCESS) {
switch (newState) {
case BluetoothProfile.STATE_DISCONNECTED:
mBluetoothGatt.close();
mBluetoothGatt = null;
break;
case BluetoothProfile.STATE_CONNECTED:
//发现服务
mBluetoothGatt.discoverServices();
break;
}
return;
}
if (status == 133) {
//1.需要清除Gatt缓存 2.断开连接 3.关闭Gatt 4.重新连接
//doing something...
}
}
方法
2.4.2.1 status为GATT_SUCCESS时,调用gatt的的getService(UUID uuid)获取一个BluetoothGattService对象,或者gatt的getServices()获取服务列表拿到想要的BluetoothGattService对象。
然后通过service的getCharacteristic(UUID uuid)获取一个BluetoothGattCharacteristic特征A对象(同样可以getCharacteristics()获取),我们特征对象可以为1个或者多个,这个特征个数得看硬件工程师那边。然后给特征A设置通知即gatt的setCharacteristicNotification(BluetoothGattCharacteristic characteristic,boolean enable),这样远程设备数据发生改变,就会直接回调onCharacteristicChanged方法从而获取远程设备发送的数据。我们通过特征A的getDescriptor(UUID uuid)方法获取特征A的BluetoothGattDescriptor指定描述符对象,然后给特征A的指定描述符设置值setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);在把描述符作为参数,调用gatt的writeDescriptor(BluetoothGattDescriptor descriptor),将描述符写入gatt,成功了将会在onDescriptorWrite回调
2.4.1.2 status为错误的133时:调用gatt的disconnect方法,然后在onConnectionStateChange方法里调用Gatt的close方法并置空gatt并重新开始使用connectGatt方法进行连接
//发现服务成功后,会触发该回调方法。status:远程设备探索是否成功
@Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
super.onServicesDiscovered(gatt, status);
if (status == BluetoothGatt.GATT_SUCCESS) {
Log.d(TAG, "onServicesDiscovered-->" + "status:" + status + "操作成功");
//根据指定的服务uuid获取指定的服务
BluetoothGattService gattService = gatt.getService(UUID.fromString(mServiceUUID));
//根据指定特征值uuid获取指定的特征值A
mGattCharacteristicA = gattService.getCharacteristic(UUID.fromString(mReadCharacteristicUUID));
//设置特征A通知,即设备的值有变化时会通知该特征A,即回调方法onCharacteristicChanged会有该通知
mBluetoothGatt.setCharacteristicNotification(mGattCharacteristicA , true);
//获取特征值其对应的通知Descriptor
BluetoothGattDescriptor descriptor = mGattCharacteristicA .getDescriptor(UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"));
//写入你需要传递给外设的特征的描述值(即传递给外设的信息)
descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
//通过GATT实体类将,特征值写入到外设中。成功则在 onDescriptorWrite 回调
mBluetoothGatt.writeDescriptor(descriptor);
}
}
上面方法里的总结:1.获取Gatt的服务S。2.获取S服务的特征A。3.给Gatt设置特征A通知。4.获取特征A的描述符D。5.把描述符D写入Gatt
注意:一般远程设备硬件工程师会对一个服务给予多个特征,比如给予2个特征,一个用来读取数据,一个用来写入数据。
方法
如果status为GATT_SUCCESS,那么恭喜该服务以及该服务下的特征可以通信啦(但并不代表双方可以互相发送数据通信,因为需要确认是否设置特征通知成功,并且手机发送数据的特征是否也是成功的,如果这两个都是成功的,那么恭喜可以互相成功发送数据啦)。最好等待个200ms再发送命令。如果没有回调该方法,说明还不能通信,请检查uuid之类的是否正确,如果uuid之类的没问题,看是否硬件工程师那边有问题。
//设置Descriptor后回调
@Override
public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
super.onDescriptorWrite(gatt, descriptor, status);
if (status == BluetoothGatt.GATT_SUCCESS) {
Log.d(TAG, "onDescriptorWrite-->" + "描述符写入操作成功,蓝牙连接成功并通信桥梁成功打通!" );
//如果设置特征通知是成功的,手机发送数据的特征也是成功的,那么就可以互相成功发送数据了,那么到这里就连接到通信整个过程都已完成,可以互相收发数据了
//等待个200ms,使其通信通道更稳定
//doing...
}
}
方法
当设置了特征通知,那么在设备对应的特征值有变化时会调用该方法
//设备的值有变化时会主动返回
@Override
public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
super.onCharacteristicChanged(gatt, characteristic);
Log.d(TAG, "onCharacteristicChanged-->" + characteristic.getUuid());
//过滤,判断是否是目标特征值
if (!mReadCharacteristicUUID.equals(characteristic.getUuid().toString())) return;
if (bleCallback != null){//通过自己写的一个接口回调传出去
bleCallback.getDeviceReturnData(characteristic.getValue());
}
}
2.4.5 和. onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status)
手机发送数据成功会回调该方法,我们可以对比发送的数据是否正确
//发送数据后的回调,可以在此检测发送的数据包是否有异常
@Override
public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
super.onCharacteristicWrite(gatt, characteristic, status);
if (status == BluetoothGatt.GATT_SUCCESS) {
Log.d(TAG, "onCharacteristicWrite:发送数据成功:" + binaryToHexString(characteristic.getValue()));
}
}
其他的回调方法一般都可以不用了,用上面四个回调方法就够了,甚至只要前三个回调方法就可以了。其他的回调方法请看 1.5. BluetoothGattCallback:实现BluetoothGatt的回调
2.4 蓝牙连接——第四步:手机发送数据:
注意:
- 写入数据的特征并不一定就是设置特征通知的那个特征,这个得看远程设备的硬件工程师的那边的定义
- 发送数据最多20字节,超过20字节则需要切割分包,发送一次数据后,需要睡一会儿,防止下一条数据发送过快
- 发送成功后会回调onCharacteristicWrite方法,在该方法里可以检查所发送的数据
private void sendData(byte[] data) {
try {
if (data.length <= 20) {
if (mGattCharacteristicA == null) {
Log.e(TAG, "mGattCharacteristicA 为空,发送数据失败");
return;
}
if (mBluetoothGatt == null) {
Log.e(TAG, "mBluetoothGatt为空,发送数据包失败");
return;
}
mGattCharacteristicA .setValue(data);
mBluetoothGatt.writeCharacteristic(mGattCharacteristicA );
} else {
Log.i(TAG, "数据包分割");
byte[] b1 = new byte[20];
byte[] b2 = new byte[data.length - 20];
for (int i = 0; i < 20; i++) {
b1[i] = data[i];
}
for (int i = 20; i < data.length; i++) {
b2[i - 20] = data[i];
}
sendData(b1);
sleep();
sendData(b2);
sleep();//防止下一条数据发送过快
}
} catch (Exception e) {
e.printStackTrace();
Log.e(TAG, "发送数据包异常" + e.toString());
}
}
注意:至于手机发送数据给远程设备,也是需要特征对象的,其特征可以和上面的特征A一样,也有可能不一样,这个是要看远程设备的硬件工程师那边怎么处理的。而且硬件工程师可以对一个服务设置很多个特征,也可以对一个gatt设置很多个服务,比如新加一个功能,硬件工程师就可以新开一个服务等等
三.注意事项:
- connectGatt方法,Android6.0以上建议使用connectGatt(Context context, boolean autoConnect, BluetoothGattCallback callback, int transport)方法,transport设为TRANSPORT_LE。
- 扫描/关闭蓝牙 30s内不能超过5次,Android7.0以上Google为了防止BLE扫描滥用
- 调用disconnect()后不能立即调用close(),需要在onConnectionStateChange回调里调用close()
- 使用完后要把gatt给断掉并且关掉并置空,不然连接成功个六七次后就连接不上了
- 出现133错误,或者连接以及通信有问题时,应清除gatt缓存,断开并关闭和置空gatt,然后重新连接
- 手机发送数据最多20字节,如果手机发送数据超过20字节,则需要进行分包
- 有的地方需要等一会儿,保证执行完毕,如停止搜索需要时间,不能马上调用connectGatt,应等一会儿使停止搜索能执行完毕。发送完一条数据,需要等一会儿,防止下一条发送过快。在onDescriptorWrite回调里如果成功了,务必等一会儿再进行发送指令之类的
- connectGatt(context, autoConnect, callback);autoConnect务必设置成false
- 服务uuid和特征uuid不能搞错,而且硬件工程师可能会给予多个特征uuid,比如会有两个特征uuid,一个用来读取,一个用来发送。描述符uuid一般为“00002902-0000-1000-8000-00805f9b34fb”
- 要检查GPS是否打开,Android6.0以上要动态申请定位权限
- Android8.0以上退到后台或息屏后需要继续扫描(谷歌在8.0以上为了省电默认退到后台和息屏后无法扫描),请使用BluetoothLeScanner#startScan(List<ScanFilter>, ScanSettings, ScanCallback)方法,限于篇幅具体请大家百度相关内容。
四.完整代码
- 代码是基于android包的。androidx包需要自行修改一下代码
- 需要引用第三方权限库 implementation 'com.github.tbruyelle:rxpermissions:0.10.2'
- AndroidManifest.xml需要的权限
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />- BleHelp.java的使用:调用init方法,再调用setMacAndUuids方法,再调用start方法即可。退出需要调用disConnect方法。其中的远程设备的服务uuid,特征uuid还有Mac地址需要其硬件工程师提供,代码中的特征uuid看硬件工程师给几个,然后我们在代码里自行修改一下便是了
BleHelp.java文件
package ble.hsj.pri.ble;
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.location.LocationManager;
import android.os.Build;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Message;
import android.support.annotation.RequiresApi;
import android.support.v4.app.FragmentActivity;
import android.util.Log;
import android.widget.Toast;
import com.tbruyelle.rxpermissions2.RxPermissions;
import java.lang.reflect.Method;
import java.util.List;
import java.util.UUID;
import io.reactivex.functions.Consumer;
/**
* author : 何送军
* date : 2020/3/22 21:18
* desc :仅支持Android4.3(API 18)以上版本
* version: 1.0
*/
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2)
public class BleHelp {
private FragmentActivity context;
private static final String TAG = "BleHelp-->";
//UUID和Mac地址
private String mServiceUUID;
private String mReadCharacteristicUUID;//特征uuid
private String mWriteCharacteristicUUID;//特征uuid
private String mMacAdress;
//蓝牙设备
private BluetoothDevice mBluetoothDevice;
//蓝牙服务
private BluetoothGatt mBluetoothGatt;
//子线程的HandlerThread,为子线程提供Looper
private HandlerThread workHandlerThread;
//子线程
private Handler workHandler;
//蓝牙读取特征值
BluetoothGattCharacteristic mReadGattCharacteristic;
//蓝牙写出特征值
BluetoothGattCharacteristic mWriteGattCharacteristic;
private static final int LINK_TIME_OUT = 1000;
private static final int START_SCAN = 1001;
private static final int STOP_SCAN = 1002;
private static final int CONNECT_GATT = 1003;
private static final int DISCOVER_SERVICES = 1004;
private static final int DISCONNECT_GATT = 1005;
private static final int CLOSE_GATT = 1006;
private static final int SEND_DATA = 1007;
//调用disConnect()方法后是否需要调用close方法
private boolean isDisConnectNeedClose = true;
//Android8.0以上,退到后台或者息屏后,是否还需要扫描(谷歌为省电8.0以上默认关闭)
private boolean isAllowSacnHomeSuperM = false;
//默认连接时间25秒
private int linkTime = 25000;
private BleCallback bleCallback;
private BleHelp() {
}
public static BleHelp getInstance() {
return SingleInstance.sInstance;
}
/**
* 静态内部类,单例
*/
private static class SingleInstance {
private static final BleHelp sInstance = new BleHelp();
}
public void init(FragmentActivity activity, BleCallback bleCallback) {
this.context = activity;
this.bleCallback = bleCallback;
}
private boolean checkAllUUID(FragmentActivity activity) {
this.context = activity;
if (this.context == null) {
Log.e(TAG, "BleHelp初始化失败:" + "context为空......");
Toast.makeText(context, "蓝牙模块初始化失败,请联系开发商...", Toast.LENGTH_LONG).show();
return false;
}
if (this.bleCallback == null) {
Log.e(TAG, "BleHelp初始化失败:" + "bleCallback为空......");
Toast.makeText(context, "蓝牙模块初始化失败,请联系开发商...", Toast.LENGTH_LONG).show();
return false;
}
if (!enable()) {
Log.e(TAG, "BleHelp初始化失败:" + "(用户操作)未打开手机蓝牙,蓝牙功能无法使用......");
Toast.makeText(context, "未打开手机蓝牙,蓝牙功能无法使用...", Toast.LENGTH_LONG).show();
return false;
}
if (!isOPenGps()) {
Log.e(TAG, "BleHelp初始化失败:" + "(用户操作)GPS未打开,蓝牙功能无法使用...");
Toast.makeText(context, "GPS未打开,蓝牙功能无法使用", Toast.LENGTH_LONG).show();
return false;
}
if (!BluetoothAdapter.checkBluetoothAddress(mMacAdress)) {
Log.e(TAG, "BleHelp初始化失败:" + "不是一个有效的蓝牙MAC地址,蓝牙功能无法使用...");
Toast.makeText(context, "不是一个有效的蓝牙MAC地址,蓝牙功能无法使用", Toast.LENGTH_LONG).show();
return false;
}
if (mServiceUUID == null) {
Log.e(TAG, "BleHelp初始化失败:" + "gattServiceUUID为空,蓝牙功能无法使用...");
Toast.makeText(context, "gattServiceUUID为空,蓝牙功能无法使用", Toast.LENGTH_LONG).show();
return false;
}
if (mReadCharacteristicUUID == null) {
Log.e(TAG, "BleHelp初始化失败:" + "mReadCharacteristicUUID为空,蓝牙功能无法使用...");
Toast.makeText(context, "mReadCharacteristicUUID为空,蓝牙功能无法使用", Toast.LENGTH_LONG).show();
return false;
}
if (mWriteCharacteristicUUID == null) {
Log.e(TAG, "BleHelp初始化失败:" + "mWriteCharacteristicUUID为空,蓝牙功能无法使用...");
Toast.makeText(context, "mWriteCharacteristicUUID为空,蓝牙功能无法使用", Toast.LENGTH_LONG).show();
return false;
}
return true;
}
public void setMacAndUuids(String macAdress, String gattServiceUUID,
String readGattCharacteristicUUID, String writeGattCharacteristicUUID) {
this.mMacAdress = macAdress;
this.mServiceUUID = gattServiceUUID;
this.mReadCharacteristicUUID = readGattCharacteristicUUID;
this.mWriteCharacteristicUUID = writeGattCharacteristicUUID;
}
public void setLinkTime(int linkTime) {
this.linkTime = linkTime;
}
public void start() {
if (!checkAllUUID(context)) return;
initWorkHandler();
permissionLocation();
}
private void initWorkHandler() {
workHandlerThread = new HandlerThread("BleWorkHandlerThread");
workHandlerThread.start();
workHandler = new Handler(workHandlerThread.getLooper()) {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case LINK_TIME_OUT:
removeMessages(LINK_TIME_OUT);
sendEmptyMessage(STOP_SCAN);
case START_SCAN:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
BluetoothLeScanner bluetoothLeScanner = getBluetoothAdapter().getBluetoothLeScanner();
if (bluetoothLeScanner == null) return;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {//允许8.0以上退到后台能继续扫描
if (isAllowSacnHomeSuperM) {//Android8.0以上退到后台或息屏后是否还要扫描。我们将其默认为false
//doing....
return;
}
}
bluetoothLeScanner.startScan(highScanCallback);
return;
}
getBluetoothAdapter().startLeScan(lowScanCallback);
break;
case STOP_SCAN:
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
BluetoothLeScanner bluetoothLeScanner = getBluetoothAdapter().getBluetoothLeScanner();
if (bluetoothLeScanner != null)
bluetoothLeScanner.stopScan(highScanCallback);
} else
getBluetoothAdapter().stopLeScan(lowScanCallback);
//停止搜索需要一定的时间来完成,建议加以100ms的延时,保证系统能够完全停止搜索蓝牙设备。
sleep();
break;
case CONNECT_GATT:
mBluetoothGatt = mBluetoothDevice.connectGatt(context, false, bluetoothGattCallback);
break;
case DISCOVER_SERVICES:
mBluetoothGatt.discoverServices();
break;
case DISCONNECT_GATT:
boolean isRefreshSuccess = refreshDeviceCache(mBluetoothGatt);
if (isRefreshSuccess) mBluetoothGatt.disconnect();
else
Log.e(TAG, "bluetoothGatt断开连接失败:因清除bluetoothGatt缓存失败,故未调用disconnect()方法");
break;
case CLOSE_GATT://需要disconnect()方法后回调onConnectionStateChange,再调用close(),
mBluetoothGatt.close();
mBluetoothGatt = null;
Log.d(TAG, "bluetoothGatt关闭成功并置为null");
break;
case SEND_DATA:
sendData((byte[]) msg.obj);
break;
}
}
};
}
/**
* 是否手机蓝牙状态
*
* @return true 表示处于打开状态,false表示处于关闭状态
*/
public boolean isEnabled() {
boolean isEnabled = getBluetoothAdapter().isEnabled();
Log.d(TAG, "手机蓝牙是否打开:" + isEnabled);
return isEnabled;
}
/**
* 打开手机蓝牙
*
* @return true 表示打开成功
*/
public boolean enable() {
if (!getBluetoothAdapter().isEnabled()) {
//若未打开手机蓝牙,则会弹出一个系统的是否打开/关闭蓝牙的对话框,禁止或者未处理返回false,允许返回true
//若已打开手机蓝牙,直接返回true
boolean enableState = getBluetoothAdapter().enable();
Log.d(TAG, "(用户操作)手机蓝牙是否打开成功:" + enableState);
return enableState;
} else return true;
}
/**
* 关闭手机蓝牙
*
* @return true 表示关闭成功
*/
public boolean disable() {
if (getBluetoothAdapter().isEnabled()) {
boolean disabledState = getBluetoothAdapter().disable();
Log.d(TAG, "(用户操作)手机蓝牙是否关闭成功:" + disabledState);
return disabledState;
} else return true;
}
/**
* 判断是否可以通过Mac地址直连
* 判断通过Mac地址获取到的Device的name是否为空来确定是否可以直连
* 该方式不是绝对的,仅供参考,需具体情况具体分析
*/
private boolean isDirectConnect() {
BluetoothDevice device = getBluetoothAdapter().getRemoteDevice(mMacAdress);
if (device.getName() != null) {
mBluetoothDevice = null;
mBluetoothDevice = device;
return true;
} else return false;
}
/**
* 断开连接
*
* @param isNeedClose 执行mBluetoothGatt.disconnect方法后是否需要执行mBluetoothGatt.close方法
* 执行
*/
public void disConnect(boolean isNeedClose) {
if (mBluetoothGatt == null) return;
isDisConnectNeedClose = isNeedClose;
workHandler.sendEmptyMessage(DISCONNECT_GATT);
}
/**
* 该方法作为扩展方法,暂时设为private
* 8.0以上退到后台或者息屏后,在没有停止扫描的情况下是否还能继续扫描,谷歌默认不扫描
*/
private void allowSacnHomeSuperM(boolean isAllow) {
this.isAllowSacnHomeSuperM = isAllow;
}
public void sendDataToDevice(byte[] data) {
Message message = new Message();
message.what = SEND_DATA;
message.obj = data;
workHandler.sendMessage(message);
}
private void sendData(byte[] data) {
try {
if (data.length <= 20) {
if (mWriteGattCharacteristic == null) {
Log.e(TAG, "mWriteGattCharacteristic为空,发送数据失败");
return;
}
if (mBluetoothGatt == null) {
Log.e(TAG, "mBluetoothGatt为空,发送数据包失败");
return;
}
mWriteGattCharacteristic.setValue(data);
mBluetoothGatt.writeCharacteristic(mWriteGattCharacteristic);
} else {
Log.i(TAG, "数据包分割");
byte[] b1 = new byte[20];
byte[] b2 = new byte[data.length - 20];
for (int i = 0; i < 20; i++) {
b1[i] = data[i];
}
for (int i = 20; i < data.length; i++) {
b2[i - 20] = data[i];
}
sendData(b1);
sleep();
sendData(b2);
sleep();//防止下一条数据发送过快
}
} catch (Exception e) {
e.printStackTrace();
Log.e(TAG, "发送数据包异常" + e.toString());
}
}
/**
* 高版本扫描回调
* Android 5.0(API 21)(包含)以上的蓝牙回调
*/
private ScanCallback highScanCallback = new ScanCallback() {
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
@Override
public void onScanResult(int callbackType, ScanResult result) {
super.onScanResult(callbackType, result);
Log.d(TAG, "LeScanCallback-->" + "扫描啊啊" + result.getDevice().getAddress());
if (mMacAdress.equals(result.getDevice().getAddress())) {
workHandler.removeMessages(LINK_TIME_OUT);
workHandler.sendEmptyMessage(STOP_SCAN);
Log.d(TAG, "LeScanCallback-->" + "蓝牙扫描已找到设备,即将开始连接");
mBluetoothDevice = null;
mBluetoothDevice = result.getDevice();
workHandler.sendEmptyMessage(CONNECT_GATT);
}
}
@Override
public void onBatchScanResults(List<ScanResult> results) {
super.onBatchScanResults(results);
Log.d(TAG, "ScanCallback: onBatchScanResults");
}
@Override
public void onScanFailed(int errorCode) {
super.onScanFailed(errorCode);
Log.e(TAG, "ScanCallback: onScanFailed");
}
};
/**
* 低版本扫描回调
* Android 4.3(API 18)(包含)以上,Android 5.0(API 21)(不包含)以下的蓝牙回调
*/
private BluetoothAdapter.LeScanCallback lowScanCallback = new BluetoothAdapter.LeScanCallback() {
@Override
public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) {
if (mMacAdress.equals(device.getAddress())) {
workHandler.removeMessages(LINK_TIME_OUT);
workHandler.sendEmptyMessage(STOP_SCAN);
Log.d(TAG, "LeScanCallback-->" + "蓝牙扫描已找到设备,即将开始连接");
mBluetoothDevice = null;
mBluetoothDevice = device;
workHandler.sendEmptyMessage(CONNECT_GATT);
}
}
};
/**
* 回调都是在子线程中,不可做更新 UI 操作
*/
private BluetoothGattCallback bluetoothGattCallback = new BluetoothGattCallback() {
@Override
public void onPhyUpdate(BluetoothGatt gatt, int txPhy, int rxPhy, int status) {
super.onPhyUpdate(gatt, txPhy, rxPhy, status);
Log.d(TAG, "onPhyUpdate");
}
@Override
public void onPhyRead(BluetoothGatt gatt, int txPhy, int rxPhy, int status) {
super.onPhyRead(gatt, txPhy, rxPhy, status);
Log.d(TAG, "onPhyRead");
}
//
//status-->操作是否成功,如连接成功这个操作是否成功。会返回异常码
//newState-->新的连接的状态。共四种:STATE_DISCONNECTED,STATE_CONNECTING,STATE_CONNECTED,STATE_DISCONNECTING
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
super.onConnectionStateChange(gatt, status, newState);
if (status == BluetoothGatt.GATT_SUCCESS) {
switch (newState) {
case BluetoothProfile.STATE_DISCONNECTED:
Log.d(TAG, "BluetoothGattCallback:onConnectionStateChange-->" + "status:" + status + "操作成功;" + " newState:" + newState + " 已断开连接状态");
if (isDisConnectNeedClose) workHandler.sendEmptyMessage(CLOSE_GATT);
break;
case BluetoothProfile.STATE_CONNECTED:
Log.d(TAG, "BluetoothGattCallback:onConnectionStateChange-->" + "status:" + status + "操作成功;" + " newState:" + newState + " 已连接状态,可进行发现服务");
//发现服务
workHandler.sendEmptyMessage(DISCOVER_SERVICES);
break;
}
return;
}
Log.e(TAG, "BluetoothGattCallback:onConnectionStateChange-->" + "status:" + status + "操作失败;" + " newState:" + newState);
if (status == 133) {//需要清除Gatt缓存并断开连接和关闭Gatt,然后重新连接
gattError133("onConnectionStateChange");
}
}
//发现服务成功后,会触发该回调方法。status:远程设备探索是否成功
@Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
super.onServicesDiscovered(gatt, status);
for (int i = 0; i < gatt.getServices().size(); i++) {
Log.d(TAG, "onServicesDiscovered-->" + "status:" + status + "操作成功急啊急啊" + gatt.getServices().get(i).getUuid());
}
if (status == BluetoothGatt.GATT_SUCCESS) {
Log.d(TAG, "onServicesDiscovered-->" + "status:" + status + "操作成功");
//根据指定的服务uuid获取指定的服务
BluetoothGattService gattService = gatt.getService(UUID.fromString(mServiceUUID));
if (gattService == null) {
Log.e(TAG, "onServicesDiscovered-->" + "获取服务指定uuid:" + mServiceUUID + "的BluetoothGattService为空,请联系外设设备开发商确认uuid是否正确");
return;
}
//根据指定特征值uuid获取指定的特征值一
mReadGattCharacteristic = gattService.getCharacteristic(UUID.fromString(mReadCharacteristicUUID));
if (mReadGattCharacteristic == null) {
Log.e(TAG, "onServicesDiscovered-->" + "获取指定特征值的uuid:" + mReadCharacteristicUUID + "的BluetoothGattCharacteristic为空,请联系外设设备开发商确认特征值uuid是否正确");
return;
}
//根据指定特征值uuid获取指定的特征值二
mWriteGattCharacteristic = gattService.getCharacteristic(UUID.fromString(mWriteCharacteristicUUID));
if (mWriteGattCharacteristic == null) {
Log.e(TAG, "onServicesDiscovered-->" + "获取指定特征值的uuid:" + mReadCharacteristicUUID + "的BluetoothGattCharacteristic为空,请联系外设设备开发商确认特征值uuid是否正确");
return;
}
//设置特征值通知,即设备的值有变化时会通知该特征值,即回调方法onCharacteristicChanged会有该通知
mBluetoothGatt.setCharacteristicNotification(mReadGattCharacteristic, true);
//获取特征值其对应的通知Descriptor
BluetoothGattDescriptor descriptor = mReadGattCharacteristic.getDescriptor(UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"));
//写入你需要传递给外设的特征的描述值(即传递给外设的信息)
descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
//通过GATT实体类将,特征值写入到外设中。在 onDescriptorWrite 回调里面发送握手
boolean isSuccessWriteDescriptor = mBluetoothGatt.writeDescriptor(descriptor);
if (!isSuccessWriteDescriptor) {
Log.e(TAG, "onServicesDiscovered-->" + "bluetoothGatt将特征值BluetoothGattDescriptor写入外设失败");
}
//通过Gatt对象读取特定特征(Characteristic)的特征值。从外设读取特征值,这个可有可无,一般远程设备的硬件工程师可能不会给该权限
boolean isSuccessReadCharacteristic = mBluetoothGatt.readCharacteristic(mReadGattCharacteristic);
if (!isSuccessReadCharacteristic) {
Log.e(TAG, "onServicesDiscovered-->" + "读取外设返回的值的操作失败,无法回调onCharacteristicRead,多半硬件工程师的问题或者没给权限");
}
return;
}
Log.e(TAG, "onServicesDiscovered-->" + "status:" + status + "操作失败");
if (status == 133) {//需要清除Gatt缓存并断开连接和关闭Gatt,然后重新连接
gattError133("onServicesDiscovered");
}
}
//接收到的数据,不一定会回调该方法
@Override
public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
super.onCharacteristicRead(gatt, characteristic, status);
Log.d(TAG, "onCharacteristicRead-->" + characteristic.getValue().toString());
}
//发送数据后的回调,可以在此检测发送的数据包是否有异常
@Override
public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
super.onCharacteristicWrite(gatt, characteristic, status);
if (status == BluetoothGatt.GATT_SUCCESS) {
Log.d(TAG, "onCharacteristicWrite:发送数据成功:" + binaryToHexString(characteristic.getValue()));
} else Log.e(TAG, "onCharacteristicWrite:发送数据失败");
}
//设备的值有变化时会主动返回
@Override
public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
super.onCharacteristicChanged(gatt, characteristic);
Log.d(TAG, "onCharacteristicChanged-->" + characteristic.getUuid());
//过滤,判断是否是目标特征值
if (!mReadCharacteristicUUID.equals(characteristic.getUuid().toString())) return;
if (bleCallback != null)
bleCallback.getDeviceReturnData(characteristic.getValue());
}
@Override
public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
super.onDescriptorRead(gatt, descriptor, status);
Log.d(TAG, "onDescriptorRead-->" + "status:" + status + descriptor.getUuid());
}
//设置Descriptor后回调
@Override
public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
super.onDescriptorWrite(gatt, descriptor, status);
if (status == BluetoothGatt.GATT_SUCCESS) {
Log.d(TAG, "onDescriptorWrite-->" + "描述符写入操作成功,蓝牙连接成功并可以通信成功!!!" + descriptor.getUuid());
if (bleCallback != null)
bleCallback.connectSuccess();
} else {
Log.e(TAG, "onDescriptorWrite-->" + "描述符写入操作失败,蓝牙通信失败...");
}
}
@Override
public void onReliableWriteCompleted(BluetoothGatt gatt, int status) {
super.onReliableWriteCompleted(gatt, status);
Log.d(TAG, "onReliableWriteCompleted");
}
@Override
public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) {
super.onReadRemoteRssi(gatt, rssi, status);
Log.d(TAG, "onReadRemoteRssi");
}
@Override
public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) {
super.onMtuChanged(gatt, mtu, status);
Log.d(TAG, "onMtuChanged");
}
};
//Gatt操作失败status为133时
private void gattError133(String method) {
Log.e(TAG, "BluetoothGattCallback:" + method + "--> 因status=133,所以将关闭Gatt重新连接...");
disConnect(true);//断开连接并关闭Gatt
if (isDirectConnect()) {
Log.d(TAG, "此次为MAC地址直连");
workHandler.sendEmptyMessage(CONNECT_GATT);
} else {
Log.d(TAG, "此次为蓝牙扫描连接");
workHandler.sendEmptyMessage(START_SCAN);
}
}
/**
* 清理本地的BluetoothGatt 的缓存,以保证在蓝牙连接设备的时候,设备的服务、特征是最新的
*/
private boolean refreshDeviceCache(BluetoothGatt gatt) {
Method refreshtMethod = null;
if (null != gatt) {
try {
for (Method methodSub : gatt.getClass().getDeclaredMethods()) {
if ("connect".equalsIgnoreCase(methodSub.getName())) {
Class<?>[] types = methodSub.getParameterTypes();
if (types != null && types.length > 0) {
if ("int".equalsIgnoreCase(types[0].getName())) {
refreshtMethod = methodSub;
}
}
}
}
if (refreshtMethod != null) {
refreshtMethod.invoke(gatt);
}
Log.d(TAG, "refreshDeviceCache-->" + "清理本地的BluetoothGatt 的缓存成功");
return true;
} catch (Exception localException) {
localException.printStackTrace();
}
}
Log.e(TAG, "refreshDeviceCache-->" + "清理本地清理本地的BluetoothGatt缓存失败");
return false;
}
/**
* 获取BluetoothAdapter,使用默认获取方式。无论如何都不会为空
*/
private BluetoothAdapter getBluetoothAdapter() {
return BluetoothAdapter.getDefaultAdapter();//用默认的
}
/**
* 定位权限
*/
@SuppressLint("CheckResult")
private void permissionLocation() {
if (context == null) return;
final RxPermissions rxPermissions = new RxPermissions(context);
rxPermissions.request(Manifest.permission.ACCESS_FINE_LOCATION).subscribe(new Consumer<Boolean>() {
@Override
public void accept(Boolean aBoolean) {
if (aBoolean) {
//申请的定位权限允许
//设置整个连接过程超时时间
workHandler.sendEmptyMessageDelayed(LINK_TIME_OUT, linkTime);
//如果可以Mac直连则不扫描
if (isDirectConnect()) {
Log.d(TAG, "此次为MAC地址直连");
workHandler.sendEmptyMessage(CONNECT_GATT);
} else {
Log.d(TAG, "此次为蓝牙扫描连接");
workHandler.sendEmptyMessage(START_SCAN);
}
} else {
//只要有一个权限被拒绝,就会执行
Log.d(TAG, "未授权定位权限,蓝牙功能不能使用:");
Toast.makeText(context, "未授权定位权限,蓝牙功能不能使用", Toast.LENGTH_LONG).show();
}
}
});
}
/**
* 判断GPS是否开启,GPS或者AGPS开启一个就认为是开启的
*
* @return true 表示开启
*/
private boolean isOPenGps() {
LocationManager locationManager
= (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
// 通过GPS卫星定位,定位级别可以精确到街(通过24颗卫星定位,在室外和空旷的地方定位准确、速度快)
boolean gps = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER);
// 通过WLAN或移动网络(3G/2G)确定的位置(也称作AGPS,辅助GPS定位。主要用于在室内或遮盖物(建筑群或茂密的深林等)密集的地方定位)
boolean network = locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER);
if (gps || network) {
Log.d(TAG, "GPS状态:打开");
return true;
}
Log.e(TAG, "GPS状态:关闭");
return false;
}
/**
* 睡一下:1.停止扫描时需要调用;2.发送特征值给外设时需要有一定的间隔
*/
private void sleep() {
try {
Thread.sleep(100);//延时100ms
} catch (InterruptedException e) {
e.printStackTrace();
Log.i("测试", "延迟异常");
}
}
/**
* @param bytes
* @return 将二进制转换为十六进制字符输出
* new byte[]{0b01111111}-->"7F" ; new byte[]{0x2F}-->"2F"
*/
private static String binaryToHexString(byte[] bytes) {
String result = "";
if (bytes == null) {
return result;
}
String hex = "";
for (int i = 0; i < bytes.length; i++) {
//字节高4位
hex = String.valueOf("0123456789ABCDEF".charAt((bytes[i] & 0xF0) >> 4));
//字节低4位
hex += String.valueOf("0123456789ABCDEF".charAt(bytes[i] & 0x0F));
result += hex + ",";
}
return result;
}
public interface BleCallback {
void connectSuccess();//连接成功
void getDeviceReturnData(byte[] data);
void error(int e);
}
}