欢迎访问我的博客 muhlenXi,该文章出自我的博客,欢迎转载,转载请注明来源: http://muhlenxi.com/2017/07/07/About-Ble-Auto-Connecting*
导语:
近期一直在做 BLE 蓝牙手环的一些项目,也积累了一些经验,为了不再被这些问题劳生伤神,还是记录下来比较靠谱。目前,CoreBluetooth 与有手环两种连接方式,一种是连接后就可以直接通信了,另一种则是,连接后需要配对,配对后就可以通信了。本文会针对两种不同的机制的自动重连方式进行探索和记录。
到目前为止,感受最深的是,敲代码跟写作文有点类似,有思路的时候,行云流水,一气呵成。没思路的时候,就跟挤牙膏似得,还问题不断。为了提高以后行云流水的能力,就把一些比较绕的逻辑记录下来。
【1】直接连接通信(无需配对的情况)
这种情况,就是每次都得 Scan Peripheral ,找到你想要的 peripheral 后,然后进行 connect 操作,connect 成功后,就可以为所欲为了。
这种情况下,当 peripheral 因为一些原因断开连接或者用户主动断开连接导致连接中断,CoreBluetooth 会调用 - (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error
代理方法,你若你想要自动重连的话,需要进行以下操作。
步骤 1
创建一个 断开原因
标志位,用于标识断开的原因,用于判断断开原因是用户主动断开的?还是其他原因断开的?
比如:
@property (nonatomic,copy) NSString * disConnectedState; //!< 1-其他原因断开,自动重连 3-手动断开,不重连
当然,这种情况 enum 是最合理的方式。
步骤 2
创建一个 连接方式
标志位,用于标识连接的方式,用于判断连接方式是用户点击 Peripheral 列表连接的?还是自动重连以前连接过的 Peripheral ?
比如:
@property (nonatomic,copy) NSString * connectedMethod; //!< 1 - 自动重连, 2 - 点击连接
当然,这种情况 enum 是最合理的方式。
步骤 3
通过 NSUserDefaults
记录曾经连接过的 Peripheral 的名字,前提是这个名字可以唯一标识这个 peripheral。
比如,在连接成功的代理方法中保存 peripheral 的名字。
// 保存外设名字
[[NSUserDefaults standardUserDefaults] setObject:peripheral.name forKey:@"PeripheralName"];
步骤 4
在 - (void)centralManagerDidUpdateState:(CBCentralManager *)central
代理方法中决定是否要调用 60秒重连方法自动重连方法
,并且在自动重连方法中需要将连接方式标志位设为自动重连方式。
- (void)centralManagerDidUpdateState:(CBCentralManager *)central
{
switch (central.state) {
case CBCentralManagerStateUnknown:
NSLog(@">>> CBCentralManagerStateUnknown");
break;
case CBCentralManagerStateResetting:
NSLog(@">>> CBCentralManagerStateResetting");
break;
case CBCentralManagerStateUnsupported:
NSLog(@">>> CBCentralManagerStateUnsupported");
break;
case CBCentralManagerStateUnauthorized:
NSLog(@">>> CBCentralManagerStateUnauthorized");
break;
case CBCentralManagerStatePoweredOff:
NSLog(@">>> CBCentralManagerStatePoweredOff");
break;
case CBCentralManagerStatePoweredOn:
{
NSLog(@">>> CBCentralManagerStatePoweredOn");
// 其他原因断开 并且 peripheral 的名字不为 nil
NSString * existName = [[NSUserDefaults standardUserDefaults] objectForKey:@"PeripheralName"];
if ([self.disConnectedState isEqualToString:@"1"] && existName != nil) {
[self autoScanAndConnectedToExistPeripheralOnOneMinutes];
}
}
break;
default:
break;
}
}
以下为60S自动重连方法
// 自动去搜索之前连过的外设并尝试一分钟重连
- (void) autoScanAndConnectedToExistPeripheralOnOneMinutes
{
// 如果之前连接的 peripheral 为 nil,则需要先搜索到这个 peripheral 后再连接,如果不为 nil ,则直接连接处理。
if (self.peripheral == nil) {
[self.centralManager scanForPeripheralsWithServices:nil options:nil];
} else {
[self.centralManager connectPeripheral:self.peripheral options:nil];
}
// 正在连接的加载动画
[self.centralManager scanForPeripheralsWithServices:nil options:nil];
[SVProgressHUD setDefaultMaskType:SVProgressHUDMaskTypeBlack];
[SVProgressHUD setDefaultStyle:SVProgressHUDStyleCustom];
[SVProgressHUD setBackgroundColor:[UIColor blackColor]];
[SVProgressHUD setForegroundColor: [UIColor whiteColor]];
[SVProgressHUD showWithStatus:NSLocalizedString(@"In the connection...", @"连接中")];
// 连接方式为自动重连
self.connectedMethod = @"1";
// 60秒后超时处理
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(60 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
// 如果 60 S后还未找到连接过的 peripheral 则取消搜索和连接。
if (self.peripheral == nil ) {
// 停止搜索
[self.centralManager stopScan];
// 取消连接,为了避免死循环,该取消连接方式为主动断开连接
self.disConnectedState = @"3";
[self.centralManager cancelPeripheralConnection:self.peripheral];
[SVProgressHUD dismiss];
}
});
}
如果是用户点击连接的话,需要在 Cell 的 didSelectRowAtIndexPath 代理方法中进行连接并设置连接方式标志。
//点击方法
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
// 取消连接
[[AppDelegate mainDelegate] cancelScanPeripherals];
// 保存用户指定的 Peripheral
CBPeripheral * per = [AppDelegate mainDelegate].bleArray[indexPath.row];
[AppDelegate mainDelegate].peripheral = per;
[SVProgressHUD showWithStatus:NSLocalizedString(@"Linking Bluetooth devices...", @"name")];
// 设置连接方式为用户点击连接方式
[AppDelegate mainDelegate].connectedMethod = @"2";
// 连接外设
[[AppDelegate mainDelegate] connectToPeripheralOnOneMinutes];
}
步骤 5
在 - (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary<NSString *, id> *)advertisementData RSSI:(NSNumber *)RSSI
扫描外设代理方法中区别对待不同连接方式的Peripheral,如果是自动重连模式下的,发现 Peripheral 后直接连接,如果仅仅是搜索模式下的,仅仅添加到外设列表数组就可以了。
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary<NSString *, id> *)advertisementData RSSI:(NSNumber *)RSSI
{
// 自动重连发现设备直接连接
if ([self.connectedMethod isEqualToString:@"1"]) {
NSString * name = [[NSUserDefaults standardUserDefaults] objectForKey:@"PeripheralName"];
if ([peripheral.name isEqualToString:name]) {
NSLog(@"自动重连搜索到了 %@ ,正在重连。。。",name);
self.peripheral = peripheral;
[central connectPeripheral:peripheral options:nil];
}
}
// 不是自动重连,则将满足条件的 peripheral 加入到外设列表数组中
if ([peripheral.name hasPrefix:@"SMART_"]) {
NSLog(@"DiscoverPeripheral--%@",peripheral.name);
if (![self.bleArray containsObject:peripheral] ) {
[self.bleArray addObject:peripheral];
[[NSNotificationCenter defaultCenter] postNotificationName:@"BleArrayChanged" object:nil];
}
}
}
步骤 6
在 - (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral
连接成功代理方法中分别执行对应操作,如果是自动重连,不需要执行相应的界面跳转操作,如果是用户点击 Peripheral 列表连接成功,则需要进行相应界面跳转操作。
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral
{
NSLog(@"--连接成功--%@",peripheral.name);
// 保存外设名字
[[NSUserDefaults standardUserDefaults] setObject:peripheral.name forKey:@"PeripheralName"];
// 这是重连成功,不进行页面跳转
if ([self.connectedMethod isEqualToString:@"1"]) {
// 更新外设状态信息
} else if ([self.connectedMethod isEqualToString:@"2"]) {
// 用户点击点击Peripheral 列表连接成功
// 执行相应界面跳转操作
}
self.peripheral.delegate = self;
[self.peripheral discoverServices:nil];
}
步骤 7
在 - (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error
断开连接代理方法中执行对应操作,如果是用户断开连接,则不需要自动重连,如果是其他原因断开连接的,则需要断开连接。
// 外设断开连接
- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error
{
NSLog(@"--断开连接 -- %@",peripheral.name);
// 更新 外设状态信息
// 用户主动断开连接
if ([self.disConnectedState isEqualToString:@"3"]) {
// do nothing
return;
}
[self connectToPeripheralOnOneMinutes];
}
步骤 8
在用户主动断开连接的方法中,保存断开方式标志,并断开连接。
//断开连接
- (void)StopConnectedButtonDidClicked:(id) sender
{
// 手动断开方式
[AppDelegate mainDelegate].disConnectedState = @"3";
[[AppDelegate mainDelegate].centralManager cancelPeripheralConnection:[AppDelegate mainDelegate].peripheral];
}
经过以上这些步骤,一个自动重连机制就理清了。
【2】连接后需要配对的情况
思路:当你与 Peripheral 配对后,只要你的 Peripheral 在手机的连接范围内,系统会去自动连接你的手环,因此,你需要在连接成功后,保存 Peripheral 的UUIDString。如果想要在 APP 启动后能自动连接之前配对的 Peripheral,你需要执行以下操作。
代码如下:
- (void)centralManagerDidUpdateState:(CBCentralManager *)central
{
switch (central.state) {
case CBCentralManagerStateUnknown:
NSLog(@">>> CBCentralManagerStateUnknown");
break;
case CBCentralManagerStateResetting:
NSLog(@">>> CBCentralManagerStateResetting");
break;
case CBCentralManagerStateUnsupported:
NSLog(@">>> CBCentralManagerStateUnsupported");
break;
case CBCentralManagerStateUnauthorized:
NSLog(@">>> CBCentralManagerStateUnauthorized");
break;
case CBCentralManagerStatePoweredOff:
NSLog(@">>> CBCentralManagerStatePoweredOff");
[[NSNotificationCenter defaultCenter] postNotificationName:DisconnectPeripheral object:nil];
break;
case CBCentralManagerStatePoweredOn:
{
NSLog(@">>> CBCentralManagerStatePoweredOn");
// 1、获取之前配对设备的 UUID String
NSString * uuidStr = [[NSUserDefaults standardUserDefaults] objectForKey:BleBindUUID];
NSLog(@" bind uuidStr == %@",uuidStr);
// 2、如果 UUID String 为 nil,说明之前没有配对过任何外设,则外设进行扫描操作,如果不为 nil 且不等于空,则根据 NSUUID 恢复之前配对的 Peripheral
if (uuidStr != nil && ![uuidStr isEqualToString:@"nil"] && ![uuidStr isEqualToString:@""]) {
// 根据 UUID String 生成 NSUUID 对象
NSUUID * uuid = [[NSUUID alloc] initWithUUIDString:uuidStr];
// 根据 NSUUID 恢复曾经配对过的设备
NSArray * peripherals = [central retrievePeripheralsWithIdentifiers:@[uuid]];
// 数组不为空,说明找到之前配对过的设备
if (peripherals.count > 0) {
// 之前配对的 对象
CBPeripheral *peripheral = [peripherals firstObject];
self.peripheral = peripheral;//**关键**需要转存外设值,才能发起连接
self.peripheral.delegate = self;
NSLog(@"self.peripheral == %@",self.peripheral);
// 60秒自动重连方法
[self connectingPeripheralDuringOneMinute];
}
}
else {
// 开始搜索外设
[central scanForPeripheralsWithServices:nil options:nil];
// 4s 后停止扫描
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(4 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
// 停止搜索外设
});
}
}
break;
default:
break;
}
}
注意:当手环与外设配对连接后,通过 cancelPeripheralConnection
方法是有时是无法断开与外设的连接。你需要到系统设置里去忽略这个外设,方可消除配对状态。
结束语
欢迎在本文下面留言一起交流心得...
如果本文能给你带来一定的帮助,在自己有能力的情况下,不妨赞助一下,表示对博主辛勤耕作的支持!