歡迎訪問我的博客 muhlenXi,該文章出自我的博客,歡迎轉(zhuǎn)載,轉(zhuǎn)載請注明來源: http://muhlenxi.com/2017/07/07/About-Ble-Auto-Connecting*
導(dǎo)語:
近期一直在做 BLE 藍牙手環(huán)的一些項目,也積累了一些經(jīng)驗,為了不再被這些問題勞生傷神,還是記錄下來比較靠譜。目前,CoreBluetooth 與有手環(huán)兩種連接方式,一種是連接后就可以直接通信了,另一種則是,連接后需要配對,配對后就可以通信了。本文會針對兩種不同的機制的自動重連方式進行探索和記錄。
到目前為止,感受最深的是,敲代碼跟寫作文有點類似,有思路的時候,行云流水,一氣呵成。沒思路的時候,就跟擠牙膏似得,還問題不斷。為了提高以后行云流水的能力,就把一些比較繞的邏輯記錄下來。
【1】直接連接通信(無需配對的情況)
這種情況,就是每次都得 Scan Peripheral ,找到你想要的 peripheral 后,然后進行 connect 操作,connect 成功后,就可以為所欲為了。
這種情況下,當 peripheral 因為一些原因斷開連接或者用戶主動斷開連接導(dǎo)致連接中斷,CoreBluetooth 會調(diào)用 - (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error 代理方法,你若你想要自動重連的話,需要進行以下操作。
步驟 1
創(chuàng)建一個 斷開原因 標志位,用于標識斷開的原因,用于判斷斷開原因是用戶主動斷開的?還是其他原因斷開的?
比如:
@property (nonatomic,copy) NSString * disConnectedState; //!< 1-其他原因斷開,自動重連 3-手動斷開,不重連
當然,這種情況 enum 是最合理的方式。
步驟 2
創(chuàng)建一個 連接方式 標志位,用于標識連接的方式,用于判斷連接方式是用戶點擊 Peripheral 列表連接的?還是自動重連以前連接過的 Peripheral ?
比如:
@property (nonatomic,copy) NSString * connectedMethod; //!< 1 - 自動重連, 2 - 點擊連接
當然,這種情況 enum 是最合理的方式。
步驟 3
通過 NSUserDefaults 記錄曾經(jīng)連接過的 Peripheral 的名字,前提是這個名字可以唯一標識這個 peripheral。
比如,在連接成功的代理方法中保存 peripheral 的名字。
// 保存外設(shè)名字
[[NSUserDefaults standardUserDefaults] setObject:peripheral.name forKey:@"PeripheralName"];
步驟 4
在 - (void)centralManagerDidUpdateState:(CBCentralManager *)central 代理方法中決定是否要調(diào)用 60秒重連方法自動重連方法,并且在自動重連方法中需要將連接方式標志位設(shè)為自動重連方式。
- (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自動重連方法
// 自動去搜索之前連過的外設(shè)并嘗試一分鐘重連
- (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];
// 取消連接,為了避免死循環(huán),該取消連接方式為主動斷開連接
self.disConnectedState = @"3";
[self.centralManager cancelPeripheralConnection:self.peripheral];
[SVProgressHUD dismiss];
}
});
}
如果是用戶點擊連接的話,需要在 Cell 的 didSelectRowAtIndexPath 代理方法中進行連接并設(shè)置連接方式標志。
//點擊方法
- (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")];
// 設(shè)置連接方式為用戶點擊連接方式
[AppDelegate mainDelegate].connectedMethod = @"2";
// 連接外設(shè)
[[AppDelegate mainDelegate] connectToPeripheralOnOneMinutes];
}
步驟 5
在 - (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary<NSString *, id> *)advertisementData RSSI:(NSNumber *)RSSI 掃描外設(shè)代理方法中區(qū)別對待不同連接方式的Peripheral,如果是自動重連模式下的,發(fā)現(xiàn) Peripheral 后直接連接,如果僅僅是搜索模式下的,僅僅添加到外設(shè)列表數(shù)組就可以了。
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary<NSString *, id> *)advertisementData RSSI:(NSNumber *)RSSI
{
// 自動重連發(fā)現(xiàn)設(shè)備直接連接
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 加入到外設(shè)列表數(shù)組中
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 連接成功代理方法中分別執(zhí)行對應(yīng)操作,如果是自動重連,不需要執(zhí)行相應(yīng)的界面跳轉(zhuǎn)操作,如果是用戶點擊 Peripheral 列表連接成功,則需要進行相應(yīng)界面跳轉(zhuǎn)操作。
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral
{
NSLog(@"--連接成功--%@",peripheral.name);
// 保存外設(shè)名字
[[NSUserDefaults standardUserDefaults] setObject:peripheral.name forKey:@"PeripheralName"];
// 這是重連成功,不進行頁面跳轉(zhuǎn)
if ([self.connectedMethod isEqualToString:@"1"]) {
// 更新外設(shè)狀態(tài)信息
} else if ([self.connectedMethod isEqualToString:@"2"]) {
// 用戶點擊點擊Peripheral 列表連接成功
// 執(zhí)行相應(yīng)界面跳轉(zhuǎn)操作
}
self.peripheral.delegate = self;
[self.peripheral discoverServices:nil];
}
步驟 7
在 - (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error 斷開連接代理方法中執(zhí)行對應(yīng)操作,如果是用戶斷開連接,則不需要自動重連,如果是其他原因斷開連接的,則需要斷開連接。
// 外設(shè)斷開連接
- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error
{
NSLog(@"--斷開連接 -- %@",peripheral.name);
// 更新 外設(shè)狀態(tài)信息
// 用戶主動斷開連接
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];
}
經(jīng)過以上這些步驟,一個自動重連機制就理清了。
【2】連接后需要配對的情況
思路:當你與 Peripheral 配對后,只要你的 Peripheral 在手機的連接范圍內(nèi),系統(tǒng)會去自動連接你的手環(huán),因此,你需要在連接成功后,保存 Peripheral 的UUIDString。如果想要在 APP 啟動后能自動連接之前配對的 Peripheral,你需要執(zhí)行以下操作。
代碼如下:
- (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、獲取之前配對設(shè)備的 UUID String
NSString * uuidStr = [[NSUserDefaults standardUserDefaults] objectForKey:BleBindUUID];
NSLog(@" bind uuidStr == %@",uuidStr);
// 2、如果 UUID String 為 nil,說明之前沒有配對過任何外設(shè),則外設(shè)進行掃描操作,如果不為 nil 且不等于空,則根據(jù) NSUUID 恢復(fù)之前配對的 Peripheral
if (uuidStr != nil && ![uuidStr isEqualToString:@"nil"] && ![uuidStr isEqualToString:@""]) {
// 根據(jù) UUID String 生成 NSUUID 對象
NSUUID * uuid = [[NSUUID alloc] initWithUUIDString:uuidStr];
// 根據(jù) NSUUID 恢復(fù)曾經(jīng)配對過的設(shè)備
NSArray * peripherals = [central retrievePeripheralsWithIdentifiers:@[uuid]];
// 數(shù)組不為空,說明找到之前配對過的設(shè)備
if (peripherals.count > 0) {
// 之前配對的 對象
CBPeripheral *peripheral = [peripherals firstObject];
self.peripheral = peripheral;//**關(guān)鍵**需要轉(zhuǎn)存外設(shè)值,才能發(fā)起連接
self.peripheral.delegate = self;
NSLog(@"self.peripheral == %@",self.peripheral);
// 60秒自動重連方法
[self connectingPeripheralDuringOneMinute];
}
}
else {
// 開始搜索外設(shè)
[central scanForPeripheralsWithServices:nil options:nil];
// 4s 后停止掃描
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(4 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
// 停止搜索外設(shè)
});
}
}
break;
default:
break;
}
}
注意:當手環(huán)與外設(shè)配對連接后,通過 cancelPeripheralConnection 方法是有時是無法斷開與外設(shè)的連接。你需要到系統(tǒng)設(shè)置里去忽略這個外設(shè),方可消除配對狀態(tài)。
結(jié)束語
歡迎在本文下面留言一起交流心得...
如果本文能給你帶來一定的幫助,在自己有能力的情況下,不妨贊助一下,表示對博主辛勤耕作的支持!