关于蓝牙的各种概念,不太熟悉的,可以看我的51CTO的博客https://blog.51cto.com/4259297/1959736,上面的文章纯粹是做笔记用,有点杂乱,这里作一下系统的梳理。
网上找的一些比较好的博客:
https://www.jianshu.com/p/f20327b40268
目录
客户端实现
客户端蓝牙要实现的功能就是扫描外围设备蓝牙,连接后实现通讯。
第1步:蓝牙初始化环境判断
1.判断android系统是否支持蓝牙
我这里说的是android系统,包括了手机,手机一般都有蓝牙模块。但是有些搭载android系统的硬件设备,不需要使用蓝牙,为了节约成功,砍掉了蓝牙模块。
BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
if (mBluetoothAdapter == null) {
// Device does not support Bluetooth
responseFailure(pluginCallback,ErrorPool.BluetoothError.CODE_10009);
return;
}
mBluetoothAdapter null就表示系统不支持蓝牙。
2.打开系统的蓝牙开关
判断蓝牙开关是否打开
mBluetoothAdapter.isEnabled()
开启蓝牙,注意加上权限:
mBluetoothAdapter.enable()
```xml
<uses-permission android:name="android.permission.BLUETOOTH"></uses-permission>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"></uses-permission>
<pre><code class="line-numbers">关闭蓝牙
```java
mBluetoothAdapter.disable()
监听蓝牙是否打开,通过监听BluetoothAdapter.ACTION_STATE_CHANGED
这个广播。
需要说明的是BluetoothAdapter.STATE_ON和BluetoothAdapter.STATE_OFF
这两个广播有时候不会回调(9.0 vivo iqoo手机),所以需要监听另外的两个状态会比较准确。
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
int btState = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1);
Logger.d(TAG, "[XXunBtReceiver] >> action: " + action);
if (BluetoothAdapter.ACTION_STATE_CHANGED.equals(action)) {
switch (btState) {
case BluetoothAdapter.STATE_TURNING_ON:
Logger.d(TAG, "[XXunBtReceiver] >> bt state turning on. ");
if(onBlueToothCallBack != null){
onBlueToothCallBack.onBlueToothOpen();
}
break;
case BluetoothAdapter.STATE_ON:
Logger.d(TAG, "[XXunBtReceiver] >> bt state is on.");
break;
case BluetoothAdapter.STATE_TURNING_OFF:
Logger.d(TAG, "[XXunBtReceiver] >> bt state is turning off. ");
if(onBlueToothCallBack != null){
onBlueToothCallBack.onBlueToothClosed();
}
break;
case BluetoothAdapter.STATE_OFF: //有的时候不会回调
Logger.d(TAG, "[XXunBtReceiver] >> bt state is off. ");
break;
}
}
}
第2步:扫描连接蓝牙
1.扫描外围蓝牙设备
1)定位权限申请
注意,BLE(蓝牙4.0)扫描对于6.0以上手机先要动态申请Manifest.permission.ACCESS_FINE_LOCATION这个权限(对于9.0手机,即使是动态权限也要在XML里声明,否则申请的时候不会有提示的UI。)
2)打开定位开关
判断GPS是否打开的方法
public static boolean isOPen(Context context) {
int locationMode = 0;
String locationProviders;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
try {
locationMode = Settings.Secure.getInt(context.getContentResolver(), Settings.Secure.LOCATION_MODE);
} catch (Settings.SettingNotFoundException e) {
e.printStackTrace();
return false;
}
return locationMode != Settings.Secure.LOCATION_MODE_OFF;
} else {
locationProviders = Settings.Secure.getString(context.getContentResolver(), Settings.Secure.LOCATION_PROVIDERS_ALLOWED);
return !TextUtils.isEmpty(locationProviders);
}
}
如果GPS开关没有打开,引导用户打开定位开关,注意有的手机即使用户打开了GPS开关,再返回到界面onActivityResult方法的resultCode并不是Activity.RESULT_OK,所以需要在onActivityResult方法里再一次调用判断GPS是否打开的方法。
new AlertDialog.Builder(webViewFragment.getActivity())
.setTitle("提示")
.setMessage("当前手机扫描蓝牙需要打开定位功能")
.setNegativeButton("取消",
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
Logger.e(TAG, "用户拒绝了打开位置权限");
responseFailure(pluginCallback, ErrorPool.BluetoothError.CODE_10001);
}
})
.setPositiveButton("前往设置",
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
webViewFragment.addResultListsners(new ActivityResultListener() {
@Override
public boolean onSdkActivityResult(int requestCode, int resultCode, Intent intent) {
// if (resultCode == Activity.RESULT_OK && requestCode == REQUEST_CODE_OPEN_GPS) {
// startDiscovery(webViewFragment, paramsBean, pluginCallback, mBluetoothAdapter, leScanCallback);
// return false;
// }
//上面的判断方法有BUG,在YuLong手机上,即使用户打开了定位服务,resultCode返回的并不是 Activity.RESULT_OK
//那就再判断一次呐,机智如我!
if(requestCode == REQUEST_CODE_OPEN_GPS){
boolean gpsIsOpen = GpsUtil.isOPen(WebApplication.getInstance().getApplication());
if(gpsIsOpen){
startDiscovery(webViewFragment, paramsBean, pluginCallback, mBluetoothAdapter, leScanCallback);
return false;
}
Logger.e(TAG, "用户拒绝了打开位置权限");
responseFailure(pluginCallback, ErrorPool.BluetoothError.CODE_10001);
}
return false;
}
});
Intent intent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS);
webViewFragment.startActivityForResult(intent, REQUEST_CODE_OPEN_GPS);
}
})
.setCancelable(false)
.show();
3)开始扫描蓝牙
开始扫描
调用mBluetoothAdapter.startLeScan()方法,点进方法的注释,官方说明了这个方法是专门用来扫描BLE设备的方法。
注意mBluetoothAdapter.startDiscovery()方法是用来扫描经典蓝牙设备的,不是用来专门扫BLE的。
停止扫描
注意停止扫描方法传入的callback必须和开始扫描创建的callback是同一个对象,而且,停止扫描方法调用之后,callback不会立马回调,所以如果在回调里有精微的处理要注意这个地方。
mBluetoothAdapter.stopLeScan(leScanCallback);
扫描回调
leScanCallback = new BluetoothAdapter.LeScanCallback() {
@Override
public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) {
}
};
onLeScan方法回调的三个参数
device:扫描到的设备
rssi:蓝牙信号强度
scanRecord:扫描到的设备广播的数据(广播数据解析,请参照:https://blog.csdn.net/zzx2436125/article/details/79698017
,注意博客里没有类,可以去源码里找然后复制一份就行)
注意callback,同一个设备,如果rssi数值有变化,也会回调,需要根据需求进行过滤。
扫描到指定设备之后,注意一定要停止扫描!
2.连接蓝牙
调用上面的callback返回的device的connectGatt方法即可连接蓝牙,蓝牙的核心就是这个方法的回调,下面我截取我的主要代码如下(直接粘贴肯定有问题):
BluetoothGattCallback bluetoothGattCallback = new BluetoothGattCallback() {
public void onConnectionStateChange(BluetoothGatt gatt, int status,
int newState) {
if (status != BluetoothGatt.GATT_SUCCESS) {
Logger.e(TAG, "系统异常回调onConnectionStateChange");
return;
}
if (newState == BluetoothGatt.STATE_CONNECTED) {
if (BleDeviceContext.this.isDeviceConnected) {
Logger.e(TAG, "蓝牙已经处于连接状态,忽略系统的重复回调。");
return;
}
Logger.e(TAG, "蓝牙连接成功");
//请求发现服务,否则onServicesDiscovered不会回调。
gatt.discoverServices();
} else if (newState == BluetoothGatt.STATE_DISCONNECTED) {
if (BleDeviceContext.this.isDeviceConnected == false) {
Logger.e(TAG, "蓝牙已经处于断开连接状态(有可能是通过wx.closeConnection方法来断开的),忽略系统的重复回调。");
return;
}
Logger.e(TAG, "蓝牙真正地断开连接");
BleDeviceContext.this.isDeviceConnected = false;
BleDeviceContext.this.curConnectedGatt = null;
} else if (newState == BluetoothGatt.STATE_CONNECTING) {
Logger.e(TAG, "onConnectionStateChange: 正在连接中...");
}
}
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
if (status != BluetoothGatt.GATT_SUCCESS) {
Logger.e(TAG, "系统异常回调onServicesDiscovered");
return;
}
Logger.e(TAG, "onServicesDiscovered: ");
BleDeviceContext.this.bluetoothGattServiceList.addAll(gatt.getServices());
}
public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic
characteristic,
int status) {
if (status != BluetoothGatt.GATT_SUCCESS) {
Logger.e(TAG, "系统异常回调onCharacteristicRead");
return;
}
Logger.e(TAG, "onCharacteristicRead: ");
byte[] value = characteristic.getValue();
}
public void onCharacteristicWrite(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic, int status) {
if (status != BluetoothGatt.GATT_SUCCESS) {
Logger.e(TAG, "系统异常回调onCharacteristicWrite");
if (onCharacteristicWriteResultListener != null) {
onCharacteristicWriteResultListener.onWriteFailure();
}
return;
}
Logger.e(TAG, "onCharacteristicWrite: ");
if (onCharacteristicWriteResultListener != null) {
onCharacteristicWriteResultListener.onWriteSuccess();
}
}
public void onCharacteristicChanged(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic) {
Logger.e(TAG, "onCharacteristicChanged: ");
byte[] value = characteristic.getValue();
if (value != null) {
Logger.e(TAG, "onCharacteristicChanged result: " + TransferUtil.bytes2HexString(value) + "\n" + "byte[] = " + value);
}
}
};
关于上面的回调,我特地在此强调一下它的坑:
上面的回调方法需要过滤status != BluetoothGatt.GATT_SUCCESS的异常回调,也要过滤成功回调的重复回调过滤(通过一些boolean值控制,一定要准确。)。
连接成功之后,记得保存一下BluetoothGatt这个对象,后面会用到这个对象根据UUID获取服务或者与设备断开连接。
第3步:BLE核心操作
也就是对服务、特征值(相当于服务端暴露给客户端的接口)的各种操作,实现与服务端设备的通讯。关于这些概念不太清楚的,可以参考我文章开头的博客链接和https://blog.csdn.net/MakeWorks/article/details/69487344,设备、服务、特征(值、描述符)是依次包涵的关系。
1.发现服务
在上面的代码中,蓝牙连接成功之后的回调里调用
gatt.discoverServices();
会触发BluetoothGattCallback的onServicesDiscovered方法被回调,在这里把服务(BluetoothGattService)列表缓存起来。注意这个过程需要一定的时间。
2.遍历服务特征值
遍历服务是获取特征值,特征值有一些属性:可读、可写、可notify、可indicate。
1)获取服务
可以从前面缓存的服务列表里获取,也可用连接成功返回的BluetoothGatt对象通过UUID来获取服务。
2)遍历服务的特征值
List<BluetoothGattCharacteristic> characteristics = supportedGattService.getCharacteristics();
3)判断特征值的属性
/**
* 判断特征值是否可通知 - notify的方式
* @param bluetoothGattCharacteristic
* @return
*/
public static boolean isNotifable(BluetoothGattCharacteristic bluetoothGattCharacteristic) {
int charaProp = bluetoothGattCharacteristic.getProperties();
if ((charaProp & BluetoothGattCharacteristic.PROPERTY_NOTIFY) > 0
/* || (charaProp & BluetoothGattCharacteristic.PROPERTY_INDICATE) > 0*/) {
return true;
}
return false;
}
/**
* 判断特征值是否可indicate
* @param bluetoothGattCharacteristic
* @return
*/
public static boolean isIndicatable(BluetoothGattCharacteristic bluetoothGattCharacteristic) {
int charaProp = bluetoothGattCharacteristic.getProperties();
if (/*(charaProp & BluetoothGattCharacteristic.PROPERTY_NOTIFY) > 0
|| */(charaProp & BluetoothGattCharacteristic.PROPERTY_INDICATE) > 0) {
return true;
}
return false;
}
public static boolean isReadable(BluetoothGattCharacteristic bluetoothGattCharacteristic) {
int charaProp = bluetoothGattCharacteristic.getProperties();
// 可读
if ((charaProp & BluetoothGattCharacteristic.PROPERTY_READ) > 0) {
return true;
}
return false;
}
public static boolean isWritable(BluetoothGattCharacteristic bluetoothGattCharacteristic) {
int charaProp = bluetoothGattCharacteristic.getProperties();
if ((charaProp & BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE) > 0
|| (charaProp & BluetoothGattCharacteristic.PROPERTY_WRITE) > 0) {
return true;
}
return false;
}
3.特征值操作
假如,取到连接成功保存的BluetoothGatt对象gatt,要操作的特征值BluetoothGattCharacteristic对象为characteristicObj.
特别注意:对于同一个连接,关于特征值操作的API 一定要等这个API的回调成功之后方可继续操作,绝对不能同时操作某个特征值,这个是应用层调用特别要注意的问题。
1)读取特征值
先利用前面给的方法判断特征值是否可读,只有可读才能读取特征值。
boolean readOk = gatt.readCharacteristic(characteristicObj) //传入到读取的特征值BluetoothGattCharacteristic
注意读取的结果在readOk为true的基础上,是在连接的回调BluetoothGattCallback的onCharacteristicRead()方法异步返回的。
public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic
characteristic,
int status) {
if (status != BluetoothGatt.GATT_SUCCESS) {
Logger.e(TAG, "系统异常回调onCharacteristicRead");
return;
}
Logger.e(TAG, "onCharacteristicRead: ");
byte[] value = characteristic.getValue();
}
2)写特征值
写特征值其实就是往服务端发送数据
先利用前面给的方法判断特征值是否可写,只有可写才能给特征值写值。
characteristicObj.setValue(byte[]) //将要写的数据赋值给特征值对象
boolean writeOk = gatt.writeCharacteristic(characteristicObj) //写特征值对象
特别注意:由于低层蓝牙协议栈的限定,一次性写入的字节数不可超过20字节,否则会写不成功。
注意写入的结果是在writeOk 为true的基础,在连接的回调BluetoothGattCallback的onCharacteristicWrite()方法异步返回的。
public void onCharacteristicWrite(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic, int status) {
if (status != BluetoothGatt.GATT_SUCCESS) {
Logger.e(TAG, "系统异常回调onCharacteristicWrite");
if (onCharacteristicWriteResultListener != null) {
onCharacteristicWriteResultListener.onWriteFailure();
}
return;
}
Logger.e(TAG, "onCharacteristicWrite: ");
if (onCharacteristicWriteResultListener != null) {
onCharacteristicWriteResultListener.onWriteSuccess();
}
}
3)打开或者关闭notify/indicate开关
这个操作就是控制可notify或者indicate的特征值是否可以接收服务端的数据,打开之后BluetoothGattCallback的onCharacteristicChanged方法才能回调,也就是接收服务端写入的数据的地方。
同理,得判断一下特征值的属性是notify还是indicate。
下面这个UUID是下面开启、关闭notify/indicate需要用到的描述符的UUID,是固定不变的。
private static final String UUID_CLIENT_CHARACTERISTIC_CONFIG_DESCRIPTOR = "00002902-0000-1000-8000-00805f9b34fb";
- indicate的处理:
/**
* 开关indicate
* @param gatt
* @param characteristic
* @param enable
* @return
*/
public static boolean setCharacteristicIndicate(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic,
boolean enable){
Logger.e(TAG, "setCharacteristicIndicate: " + characteristic.getUuid().toString() + ",enable = " + enable);
//第1步:改变特征值本身
boolean success = gatt.setCharacteristicNotification(characteristic, enable);
if(success == false) {
Logger.e(TAG, "setCharacteristicNotification failure");
return false;
}
//第2步:改变特征值描述符
BluetoothGattDescriptor descriptor = characteristic.getDescriptor(formUUID(UUID_CLIENT_CHARACTERISTIC_CONFIG_DESCRIPTOR));
if (descriptor != null) {
if ((characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_NOTIFY) != 0) {
descriptor.setValue(enable ? BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE :
BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE);
} else if ((characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_INDICATE) != 0) {
descriptor.setValue(enable ? BluetoothGattDescriptor.ENABLE_INDICATION_VALUE :
BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE);
}
return gatt.writeDescriptor(descriptor);
}
return false;
}
- notify的处理:
/**
* 开关notify
* @param gatt
* @param characteristic
* @param enable
* @return
*/
public static boolean setCharacteristicNotification(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic,
boolean enable){
Logger.e(TAG, "setCharacteristicNotification: " + characteristic.getUuid().toString() + ",enable = " + enable);
//第1步:改变特征值本身
boolean success = gatt.setCharacteristicNotification(characteristic, enable);
if(success == false) {
Logger.e(TAG, "setCharacteristicNotification failure");
return false;
}
//第2步:改变特征值描述符
BluetoothGattDescriptor descriptor = characteristic.getDescriptor(formUUID(UUID_CLIENT_CHARACTERISTIC_CONFIG_DESCRIPTOR));
if (descriptor != null) {
descriptor.setValue(enable ? BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE :
BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE);
return gatt.writeDescriptor(descriptor);
}
return false;
}
4)监听特征值变化
监听特征值变化,也就是监听服务端发来的数据。
注意这个是在上面notify、indicate打开的情况下才会回调
public void onCharacteristicChanged(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic) {
Logger.e(TAG, "onCharacteristicChanged: ");
byte[] value = characteristic.getValue();
if (value != null) {
Logger.e(TAG, "onCharacteristicChanged result: " + TransferUtil.bytes2HexString(value) + "\n" + "byte[] = " + value);
}
}
第4步:多个外围设备的封装
综上所述,设备的区分是在连接处开始的,所以将连接、断开连接及其Callback单独封装到一个类里,Callback返回的重要参数也缓存在这个类里。然后创建一个Manager类来管理维护封装的类。
下面是我的代码,没法直接粘贴使用,仅供参考:
设备封装类
/**
* 一台设备对应的上下文
* 包括创建连接时所有关联的变量
*
* 注意:
* 1)对于连接传给我的超时,我会做扫描和连接两个超时处理。
* 扫描中如果超时,我会停止扫描,假如回调仍然找到了设备,超时了话,我会不进行后面的连接等操作。
*/
public class BleDeviceContext {
private static final String TAG = "BleDeviceContext";
private WebViewFragment webViewFragment;
private Plugin.PluginCallback connectionImplPluginCallback;
private BLEConnectionImpl bleConnectionImpl;
private BluetoothGatt curConnectedGatt;
private boolean isDeviceConnected = false; /**当前是否有设备在连接着
特别要注意,部分android手机蓝牙断开回调会有很大延迟。//TODO 这个是在真正断开的回调里赋值的吗?
*/
private CZWrapperBluetoothDevice czWrapperBluetoothDevice; //当前连接的设备信息存储
private boolean isOnServicesDiscoveredCalledBack = false; //连接设备时,防止onServicesDiscovered方法重复回调。
private CZBluetoothGattServiceList bluetoothGattServiceList; //记录连接的设备发现的服务
private BLEConnectionParamsBean bleConnectionParamsBean;
private OnCharacteristicWriteResultListener onCharacteristicWriteResultListener;
private final TimeCounter timeCounter;
private boolean isTimeOut = false; //判断是否是超时,如果超时,即使蓝牙连接上了也会自动断开。
public BleDeviceContext() {
this.bluetoothGattServiceList = new CZBluetoothGattServiceList();
timeCounter = new TimeCounter(); //扫描、连接超时判断。
isDeviceConnected = false;
}
BluetoothGattCallback bluetoothGattCallback = new BluetoothGattCallback() {
public void onConnectionStateChange(BluetoothGatt gatt, int status,
int newState) {
if (status != BluetoothGatt.GATT_SUCCESS) {
Logger.e(TAG, "系统异常回调onConnectionStateChange");
// bleConnectionImpl.responseFailure(connectionImplPluginCallback,ErrorPool.BluetoothError.CODE_10008);
return;
}
BleDeviceContext.this.curConnectedGatt = gatt; //记录起来,用于断开连接。
//关闭连接超时计时器
timeCounter.stop();
if (newState == BluetoothGatt.STATE_CONNECTED) {
if (BleDeviceContext.this.isDeviceConnected) {
Logger.e(TAG, "蓝牙已经处于连接状态,忽略系统的重复回调。");
return;
}
if(isTimeOut) {
Logger.e(TAG, "连接超时,即使已经连接了,我也不处理,你连接晚了。");
force_disconnect();
return;
}
Logger.e(TAG, "蓝牙连接成功");
WxBluetoothGlobalPropertyHolder.alreadyConnectedOnceDevices.add(czWrapperBluetoothDevice);
BleDeviceContext.this.isDeviceConnected = true;
bleConnectionImpl.responseSuccess(connectionImplPluginCallback);
BleJsCallbackHelper.getInstance().callbackConnectionStateChange(webViewFragment, czWrapperBluetoothDevice, true);
//请求发现服务,否则onServicesDiscovered不会回调。
gatt.discoverServices();
} else if (newState == BluetoothGatt.STATE_DISCONNECTED) {
if (BleDeviceContext.this.isDeviceConnected == false) {
Logger.e(TAG, "蓝牙已经处于断开连接状态(有可能是通过wx.closeConnection方法来断开的),忽略系统的重复回调。");
return;
}
Logger.e(TAG, "蓝牙真正地断开连接");
bleConnectionImpl.responseSuccess(connectionImplPluginCallback); //对于closeBleConnect是成功回调
BleDeviceContext.this.isDeviceConnected = false;
BleDeviceContext.this.curConnectedGatt = null;
BleJsCallbackHelper.getInstance().callbackConnectionStateChange(webViewFragment, czWrapperBluetoothDevice, false);
} else if (newState == BluetoothGatt.STATE_CONNECTING) {
Logger.e(TAG, "onConnectionStateChange: 正在连接中...");
}
}
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
if (status != BluetoothGatt.GATT_SUCCESS) {
Logger.e(TAG, "系统异常回调onServicesDiscovered");
return;
}
Logger.e(TAG, "onServicesDiscovered: ");
BleDeviceContext.this.bluetoothGattServiceList.addAll(gatt.getServices());
}
public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic
characteristic,
int status) {
if (status != BluetoothGatt.GATT_SUCCESS) {
Logger.e(TAG, "系统异常回调onCharacteristicRead");
return;
}
Logger.e(TAG, "onCharacteristicRead: ");
byte[] value = characteristic.getValue();
BleJsCallbackHelper.getInstance().callbackBleCharacteristicValue(webViewFragment,
czWrapperBluetoothDevice.getRealBluetoothDevice().getAddress(),
characteristic.getService().getUuid().toString(),
characteristic.getUuid().toString(),
TransferUtil.bytes2HexString(value));
if (value != null) {
Logger.e(TAG, "onCharacteristicRead result: " + TransferUtil.bytes2HexString(value) + "\n" + "byte[] = " + value);
}
}
public void onCharacteristicWrite(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic, int status) {
if (status != BluetoothGatt.GATT_SUCCESS) {
Logger.e(TAG, "系统异常回调onCharacteristicWrite");
if (onCharacteristicWriteResultListener != null) {
onCharacteristicWriteResultListener.onWriteFailure();
}
return;
}
Logger.e(TAG, "onCharacteristicWrite: ");
if (onCharacteristicWriteResultListener != null) {
onCharacteristicWriteResultListener.onWriteSuccess();
}
}
public void onCharacteristicChanged(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic) {
Logger.e(TAG, "onCharacteristicChanged: ");
byte[] value = characteristic.getValue();
BleJsCallbackHelper.getInstance().callbackBleCharacteristicValue(webViewFragment,
czWrapperBluetoothDevice.getRealBluetoothDevice().getAddress(),
characteristic.getService().getUuid().toString(),
characteristic.getUuid().toString(),
TransferUtil.bytes2HexString(value));
if (value != null) {
Logger.e(TAG, "onCharacteristicChanged result: " + TransferUtil.bytes2HexString(value) + "\n" + "byte[] = " + value);
}
}
};
public void connect(final BluetoothAdapter mBluetoothAdapter, WebViewFragment webViewFragment, final Plugin.PluginCallback connectionImplPluginCallback,
final BLEConnectionImpl bleConnectionImpl, final BLEConnectionParamsBean bleConnectionParamsBean) {
if (isDeviceConnected) {
bleConnectionImpl.responseFailure(connectionImplPluginCallback, ErrorPool.BluetoothError.createOtherTypeError("device already connected"));
return;
}
this.webViewFragment = webViewFragment;
this.connectionImplPluginCallback = connectionImplPluginCallback;
this.bleConnectionImpl = bleConnectionImpl;
this.bleConnectionParamsBean = bleConnectionParamsBean;
this.isOnServicesDiscoveredCalledBack = false;
BleDeviceContext.this.bluetoothGattServiceList.clear();
final boolean[] isScanStopped = {false}; /**有时候,即使mBluetoothAdapter.stopLeScan()已经调用过了,Callback还是会有回调,需要过滤这种错误的回调。*/
CZWrapperBluetoothDevice connectedDevice = WxBluetoothGlobalPropertyHolder.getInstance().findConnectedDevice(bleConnectionParamsBean.getDeviceId());
if(connectedDevice != null){
//已经连接过一次,直接连接。
Logger.e(TAG, "已经连接过一次,直接连接 " + connectedDevice.toString() );
BleDeviceContext.this.czWrapperBluetoothDevice = connectedDevice;
connectedDevice.getRealBluetoothDevice().connectGatt(WebApplication.getInstance().getApplication(), false, bluetoothGattCallback);
}else{
//没有连接,扫描后再连接。
final BluetoothAdapter.LeScanCallback leScanCallback = new BluetoothAdapter.LeScanCallback() {
@Override
public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) {
CZWrapperBluetoothDevice czWrapperBluetoothDevice = new CZWrapperBluetoothDevice(device, rssi,scanRecord);
if(bleConnectionParamsBean.getDeviceId().equals(czWrapperBluetoothDevice.getRealBluetoothDevice().getAddress())){
if(isScanStopped[0]){
Logger.e(TAG, "扫描已经停止了,但是系统还是在回调,拒绝这种回调,否则会导致连接不成功。");
return;
}
//关闭扫描超时计时器
timeCounter.stop();
if(isTimeOut) {
Logger.e(TAG, "扫描已经超时了,即使设备找到了,我也不做处理。");
return;
}
Logger.e(TAG, "扫描,找到指定的连接设备" + WxBluetoothDeviceInfo.parse(czWrapperBluetoothDevice));
Logger.e(TAG,"停止扫描,一定要记着,否则连接可能会有问题。");
mBluetoothAdapter.stopLeScan(this);
isScanStopped[0] = true;
Logger.e(TAG,"扫描后,开始连接设备");
BleDeviceContext.this.czWrapperBluetoothDevice = czWrapperBluetoothDevice;
device.connectGatt(WebApplication.getInstance().getApplication(), false, bluetoothGattCallback);
isTimeOut = false;
//开启连接超时计时器
if(bleConnectionParamsBean.getTimeout() > 0) {
timeCounter.setDelayTime(bleConnectionParamsBean.getTimeout());
timeCounter.setTimeCallBack(new TimeCounter.TimeCallBack() {
@Override
public void onTime(int passedCount, long delayMilliSeconds) {
Logger.e(TAG, "连接超时了");
timeCounter.stop();
isTimeOut = true;
force_disconnect();
bleConnectionImpl.responseFailure(connectionImplPluginCallback, ErrorPool.BluetoothError.CODE_10012);
}
});
timeCounter.startCount();
}
}
}
};
mBluetoothAdapter.startLeScan(leScanCallback);
isScanStopped[0] = false;
/**TODO bleConnectionParamsBean里会有一个超时时间,不清楚到底是扫描超时还是连接超时。
* 目前处理办法是:扫描时用这个超时,连接也用这个超时。
* */
isTimeOut = false;
if(bleConnectionParamsBean.getTimeout() > 0){
timeCounter.setDelayTime(bleConnectionParamsBean.getTimeout());
timeCounter.setTimeCallBack(new TimeCounter.TimeCallBack() {
@Override
public void onTime(int passedCount, long delayMilliSeconds) {
Logger.e(TAG, "扫描超时了");
isTimeOut = true;
timeCounter.stop();
bleConnectionImpl.responseFailure(connectionImplPluginCallback,ErrorPool.BluetoothError.CODE_10012);
mBluetoothAdapter.stopLeScan(leScanCallback);
}
});
timeCounter.startCount();
}
}
}
public void disconnect(){
Logger.e(TAG, "disconnect: " );
//清除之前的连接,防止用户连续调用了create2次,导致连接没有释放。
if(this.curConnectedGatt != null && isDeviceConnected){
this.curConnectedGatt.disconnect();
this.curConnectedGatt = null;
Logger.e(TAG, "disconnect:1111 " );
}else{
Logger.e(TAG, "disconnect:2222 " );
bleConnectionImpl.responseFailure(connectionImplPluginCallback,ErrorPool.BluetoothError.createOtherTypeError("device already disconnected"));
}
}
/**
* 用于超时关闭,不给JS回调。
*/
private void force_disconnect(){
Logger.e(TAG, "disconnect: " );
//清除之前的连接,防止用户连续调用了create2次,导致连接没有释放。
if(this.curConnectedGatt != null && isDeviceConnected){
this.curConnectedGatt.disconnect();
this.curConnectedGatt = null;
Logger.e(TAG, "disconnect:1111 " );
}else{
Logger.e(TAG, "disconnect:3333 " );
}
}
/**
* 是否有服务
* @return
*/
public boolean isThereAnySerivce(){
if(bluetoothGattServiceList == null || bluetoothGattServiceList.size() == 0) return false;
return true;
}
/**
* 查找指定的服务
* @param serviceId
* @return
*/
public BluetoothGattService findService(String serviceId){
if(bluetoothGattServiceList == null || bluetoothGattServiceList.size() == 0) return null;
for (BluetoothGattService item : bluetoothGattServiceList){
if(serviceId.equals(item.getUuid().toString())){
return item;
}
}
return null;
}
//写特征值监听
public void registOnCharacteristicWriteResultListener(OnCharacteristicWriteResultListener onCharacteristicWriteResultListener){
this.onCharacteristicWriteResultListener = onCharacteristicWriteResultListener;
}
public interface OnCharacteristicWriteResultListener{
void onWriteSuccess();
void onWriteFailure();
}
//java bean
public BluetoothGatt getCurConnectedGatt() {
return curConnectedGatt;
}
public void setCurConnectedGatt(BluetoothGatt curConnectedGatt) {
this.curConnectedGatt = curConnectedGatt;
}
public boolean isDeviceConnected() {
return isDeviceConnected;
}
public void setDeviceConnected(boolean deviceConnected) {
isDeviceConnected = deviceConnected;
}
public CZWrapperBluetoothDevice getCzWrapperBluetoothDevice() {
return czWrapperBluetoothDevice;
}
public void setCzWrapperBluetoothDevice(CZWrapperBluetoothDevice czWrapperBluetoothDevice) {
this.czWrapperBluetoothDevice = czWrapperBluetoothDevice;
}
public List<BluetoothGattService> getBluetoothGattServiceList() {
return bluetoothGattServiceList;
}
}
设备封装类的管理类
/**
* 管理设备上下文
*/
public class BleDeviceContextManager {
private static final String TAG = "BleDeviceContextManager";
private static final BleDeviceContextManager ourInstance = new BleDeviceContextManager();
private Map<String,BleDeviceContext> bleDeviceContextMap;
public static BleDeviceContextManager getInstance() {
return ourInstance;
}
private BleDeviceContextManager() {
bleDeviceContextMap =new HashMap<>();
}
public void release(){
//断开连接,释放资源。
List<BleDeviceContext> connectedBleDeviceContextList = findConnectedBleDeviceContextList();
for (BleDeviceContext item : connectedBleDeviceContextList){
item.disconnect();
}
this.bleDeviceContextMap.clear();
}
/**
* 根据mac地址获取设备上下文
* @param deviceId
* @return
*/
public BleDeviceContext findBleDeviceContext(String deviceId){
Logger.e(TAG, "findBleDeviceContext: " + deviceId );
return bleDeviceContextMap.get(deviceId);
}
/**
* 获取已经连接的设备
* @return
*/
public List<BleDeviceContext> findConnectedBleDeviceContextList(){
List<BleDeviceContext> result = new ArrayList<>();
for (Iterator<Map.Entry<String, BleDeviceContext>> it = bleDeviceContextMap.entrySet().iterator(); it.hasNext();){
Map.Entry<String, BleDeviceContext> item = it.next();
if(item.getValue().isDeviceConnected()){
result.add(item.getValue());
}
}
return result;
}
/**
* 根据mac地址创建设备上下文
* @param deviceId
* @return
*/
public BleDeviceContext createBleDeviceContext(String deviceId){
BleDeviceContext bleDeviceContext = findBleDeviceContext(deviceId);
if(bleDeviceContext != null){
Logger.e(TAG, "deviceId = " + deviceId + ",已经创建上下文对象,请勿重复创建。" );
return null;
}
BleDeviceContext result = new BleDeviceContext();
bleDeviceContextMap.put(deviceId,result);
return result;
}
/**
* 根据mac地址移除设备上下文
* @param deviceId
* @return
*/
public void removeBleDeviceContext(String deviceId){
BleDeviceContext bleDeviceContext = findBleDeviceContext(deviceId);
if(bleDeviceContext == null){
Logger.e(TAG, "deviceId = " + deviceId + ",上下文对象不存在,就别移除了。" );
return ;
}
bleDeviceContextMap.remove(deviceId);
}
}
服务端的实现,会在后续博客中持续更新,敬请关注。
如有疑惑或者好的建议,或者想纠正作者的博文,请联系如下
公众号:微信公众号搜索“修符道人”或者扫描下方二维码
微信号:XinYi1349308479
QQ邮箱:1349308479@qq.com
0 条评论