iBeacon是什么?
??? 蘋果官方對(duì)iBeacon的描述:iBeacon是iOS 7推出的一項(xiàng)技術(shù),可為APP提供新的位置感知功能。利用藍(lán)牙低功耗(BLE)骆莹,具有iBeacon技術(shù)的設(shè)備可用于在對(duì)象周圍建立區(qū)域。這允許iOS設(shè)備確定它何時(shí)進(jìn)入或離開(kāi)該區(qū)域担猛,以及估計(jì)與信標(biāo)的接近度幕垦。在使用iBeacon技術(shù)時(shí)丢氢,需要考慮硬件和軟件組件,本文檔將介紹這兩者先改,以及建議的用途和最佳實(shí)踐疚察,以幫助確保高效部署,從而實(shí)現(xiàn)出色的用戶體驗(yàn)仇奶。
采用iBeacon技術(shù)的設(shè)備
???采用iBeacon技術(shù)的設(shè)備可以使用紐扣電池供電一個(gè)月或更長(zhǎng)時(shí)間貌嫡,或者使用更大的電池一次運(yùn)行數(shù)月,或者可以在外部長(zhǎng)時(shí)間供電该溯。iOS設(shè)備也可以配置為生成iBeacon廣告岛抄,但此功能的范圍有限。iBeacon廣告通過(guò)藍(lán)牙低功耗提供以下信息:
???UUID狈茉,主要和次要值提供iBeacon的標(biāo)識(shí)信息夫椭。該信息本質(zhì)上是分層的,主要和次要字段允許細(xì)分由UUID建立的身份氯庆〔淝铮可以使用OS X中的uuidgen命令行實(shí)用程序生成UUID,也可以使用NSUUID Foundation類以編程方式生成UUID堤撵。
iBeacon軟件 - 核心位置API
???在iOS 7之前感凤,核心位置使用由地理位置(緯度和經(jīng)度)和半徑定義的區(qū)域,稱為“地理圍欄”粒督。通過(guò)定義具有標(biāo)識(shí)符的區(qū)域,iBeacon實(shí)現(xiàn)了新的靈活性禽翼。這允許將信標(biāo)附加到不綁定到單個(gè)位置的對(duì)象上屠橄。例如,信標(biāo)設(shè)備可用于設(shè)置諸如食物卡車或游輪之類的可移動(dòng)物體周圍的區(qū)域闰挡。此外锐墙,多個(gè)設(shè)備可以使用相同的標(biāo)識(shí)符。這將使零售連鎖店能夠在其所有位置使用信標(biāo)长酗,并允許iOS設(shè)備知道何時(shí)進(jìn)入其中任何一個(gè)溪北。隱私和位置由于iBeacon是Core Location的一部分,因此需要相同的用戶授權(quán)才能使用夺脾。當(dāng)APP嘗試使用iBeacon API時(shí)之拨,用戶將看到相同的位置授權(quán)警報(bào):
???在CoreLocation中使用信標(biāo)區(qū)域API的APP將顯示在“隱私”>“位置服務(wù)”下的“設(shè)置”APP中,用戶可以隨時(shí)允許或拒絕APP訪問(wèn)iBeacon功能咧叭。此外蚀乔,任何與iBeacon關(guān)聯(lián)的藍(lán)牙數(shù)據(jù)包都將從CoreBluetooth API中排除。
???與地理圍欄區(qū)域監(jiān)控一樣菲茬,當(dāng)處于活動(dòng)狀態(tài)時(shí)吉挣,狀態(tài)欄將顯示空心箭頭派撕。使用測(cè)距時(shí),狀態(tài)欄將顯示實(shí)心位置箭頭睬魂。
iBeacon的準(zhǔn)確性
???當(dāng)iOS設(shè)備檢測(cè)到信標(biāo)的信號(hào)時(shí)终吼,它使用信號(hào)的強(qiáng)度(RSSI或接收信號(hào)強(qiáng)度指示)來(lái)確定信標(biāo)的接近度以及其估計(jì)接近度的準(zhǔn)確性。信號(hào)越強(qiáng)氯哮,iOS對(duì)信標(biāo)的接近程度就越高际跪。信號(hào)越弱,iOS對(duì)信標(biāo)的接近程度越低蛙粘。
???當(dāng)iOS設(shè)備可以清晰地接收GPS信號(hào)時(shí)垫卤,例如當(dāng)設(shè)備處于戶外開(kāi)放狀態(tài)且軌道GPS衛(wèi)星的視線暢通時(shí),你的位置就可以更準(zhǔn)確地確定出牧。
區(qū)域監(jiān)測(cè)
???與現(xiàn)有的地理圍欄區(qū)域監(jiān)視類似穴肘,APP可以在設(shè)備進(jìn)入或離開(kāi)由信標(biāo)定義的區(qū)域時(shí)請(qǐng)求通知。當(dāng)APP使該請(qǐng)求開(kāi)始監(jiān)視信標(biāo)區(qū)域時(shí)舔痕,它必須指定iBeacon廣播包的UUID评抚。雖然APP僅限于20個(gè)受監(jiān)控區(qū)域,但通過(guò)在多個(gè)位置使用單個(gè)UUID伯复,設(shè)備可以輕松地同時(shí)監(jiān)控多個(gè)物理位置慨代。
???除了UUID之外,APP還可以選擇提供主要和次要字段啸如,以進(jìn)一步指定要監(jiān)視的信標(biāo)區(qū)域侍匙。假設(shè)我們指定一塊區(qū)域,如果APP僅為信標(biāo)區(qū)域指定UUID叮雳,則當(dāng)進(jìn)入或離開(kāi)該區(qū)域時(shí)將會(huì)通知它想暗。由于主要字段用于確定特定區(qū)域,如果只是在進(jìn)入該區(qū)域時(shí)得到通知帘不,APP可以使用UUID +主要值來(lái)配置信標(biāo)區(qū)域说莫。如果只想在進(jìn)入該區(qū)域的指定位置時(shí)收到通知,APP可以使用顆粒度更細(xì)的值來(lái)配置信標(biāo)寞焙,即:UUID + major + minor的組合储狭。
應(yīng)用實(shí)例
1.BLE設(shè)備數(shù)據(jù)同步
??? BLE設(shè)備與APP配對(duì)成功之后,為該設(shè)備注冊(cè)IBeacon信標(biāo)捣郊。此時(shí)設(shè)備通過(guò)廣播包把數(shù)據(jù)點(diǎn)傳給APP辽狈,APP再開(kāi)啟HTTP請(qǐng)求上傳數(shù)據(jù)點(diǎn)到云端。當(dāng)iPhone與BLE設(shè)備斷連的時(shí)候模她,數(shù)據(jù)同步中斷稻艰。當(dāng)iPhone再次進(jìn)入設(shè)備藍(lán)牙有效距離內(nèi),設(shè)備發(fā)送IBeacon廣播包侈净,此廣播包與傳輸數(shù)據(jù)點(diǎn)的廣播包不同尊勿,它的作用是用來(lái)喚醒APP僧凤,它必須帶有特定標(biāo)識(shí)的UUID,當(dāng)iPhone監(jiān)聽(tīng)到IBeacon廣播包中的UUID元扔,與注冊(cè)信標(biāo)的UUID相同時(shí)躯保,發(fā)起設(shè)備掃描并恢復(fù)連接狀態(tài),同步數(shù)據(jù)澎语。
??? IBeacon喚醒設(shè)備的能力非常強(qiáng)大途事,鎖屏、后臺(tái)掛起擅羞、kill都能喚醒APP尸变。
代碼示例:
// 初始化 Location
- (CLLocationManager *)locationManager {
if (!_locationManager) {
_locationManager = [[CLLocationManager alloc] init];
_locationManager.delegate = self;
_locationManager.desiredAccuracy = kCLLocationAccuracyBest;
_locationManager.distanceFilter = kCLDistanceFilterNone;
// _locationManager.allowsBackgroundLocationUpdates = YES;
_locationManager.pausesLocationUpdatesAutomatically = NO;
}
return _locationManager;
}
// 注冊(cè)IBeacon監(jiān)聽(tīng)
- (void)startIbeaconMonitorWithUUID:(NSString *)uuid devId:(NSString *)devId {
if (![PHBleUnlockUtil isValidUUID:uuid]) {
return;
}
if (![self checkIBeacon:devId]) {
return;
}
NSString *ibeaconIdentifer = [PHBleUnlockUtil ibeaconRegionIdentifierWithDevId:devId];
CLBeaconRegion *ibeacon = [[CLBeaconRegion alloc] initWithProximityUUID:[[NSUUID alloc] initWithUUIDString:[PHBleUnlockUtil generatedStandardUUIDWithDeviceUUID:uuid]] identifier:ibeaconIdentifer];
ibeacon.notifyOnEntry = YES;
ibeacon.notifyOnExit = YES;
ibeacon.notifyEntryStateOnDisplay = YES;
// 系統(tǒng)框架會(huì)根據(jù)identifier和Region類型自動(dòng)替換,不用手動(dòng)先stop stopMonitoringForRegion停止IBeacon監(jiān)聽(tīng)。這是異步完成的减俏,可能不會(huì)立即反映在MonitoredRegions中
[self.locationManager stopMonitoringForRegion:ibeacon];
[self.locationManager startMonitoringForRegion:ibeacon];
}
// 移除單個(gè)IBeacon監(jiān)聽(tīng)
- (void)stopIbeaconMonitorWithDevId:(NSString *)devId {
NSSet *monitoredRegions = [self.locationManager monitoredRegions];
for (CLRegion *rg in monitoredRegions) {
if ([rg isMemberOfClass:[CLBeaconRegion class]] && [rg.identifier containsString:devId]) {
[self.locationManager stopMonitoringForRegion:rg];
break;
}
}
}
// check 當(dāng)前設(shè)備是否已注冊(cè)過(guò)IBeacon
- (BOOL)checkIBeacon:(NSString *)devId {
NSSet *regions = [self.locationManager monitoredRegions];
// 系統(tǒng)允許IBeacon注冊(cè)上線為20個(gè)召烂,達(dá)到20個(gè)批量移除,再添加
if (regions.count == kBleIBeaconMonitorMaxCount) {
[self removeIbeaconAllMonitors];
return [self checkIBeacon:devId];
} else {
for (CLRegion *rg in regions) {
if ([rg isMemberOfClass:[CLBeaconRegion class]]) {
NSString *identifier = rg.identifier;
// 該設(shè)備已經(jīng)注冊(cè)過(guò)IBeacon娃承,不再注冊(cè)
if ([identifier containsString:L(devId)]) {
return NO;
}
}
}
return YES;
}
}
// 移除所有IBeacon監(jiān)聽(tīng)
- (void)removeIbeaconAllMonitors {
NSSet *regions = [self.locationManager monitoredRegions];
for (CLRegion *rg in regions) {
if ([rg isMemberOfClass:[CLBeaconRegion class]]) {
[self.locationManager stopMonitoringForRegion:rg];
}
}
}
// 發(fā)起設(shè)備連接
- (void)repeatBleStartListening:(NSString *)devId {
BOOL connectedCompleted = [self bleConnectedCompletedStatusWithDevId:devId];
if (!connectedCompleted) {
NSLog(@"---??ibeacon----50秒后藍(lán)牙重連");
// 設(shè)備重連
[self removeBleConnectedCompletedStatusWithDevId:devId];
}
}
// 設(shè)置當(dāng)前設(shè)備的狀態(tài)為連接中
- (void)setupBleConnectedCompletedWithDevId:(NSString *)devId {
[self.bleConnectedCompletedList setObject:@(YES) forKey:devId];
}
// 獲取當(dāng)前設(shè)備的連接狀態(tài)
- (BOOL)bleConnectedCompletedStatusWithDevId:(NSString *)devId {
return [[self.bleConnectedCompletedList valueForKey:devId] boolValue];
}
// 移除當(dāng)前設(shè)備的連接狀態(tài)
- (void)removeBleConnectedCompletedStatusWithDevId:(NSString *)devId {
return [self.bleConnectedCompletedList removeObjectForKey:devId];
}
// 重置設(shè)備連接狀態(tài)
- (void)resetStatesWithIdentifier:(NSString *)identifier {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self.handleBeaconList setValue:@(NO) forKey:identifier];
self.needRequestStateForRegion = YES;
});
}
#pragma mark - backgroup task
- (void)beginBackgroundUpdateTask {
weakify(self);
self.backgroundTaskId = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
strongify(self);
[self endBackgroundUpdateTask];
}];
}
- (void)endBackgroundUpdateTask {
[[UIApplication sharedApplication] endBackgroundTask:self.backgroundTaskId];
self.backgroundTaskId = UIBackgroundTaskInvalid;
}
#pragma mark - CLLocationManagerDelegate
// notifyOnEntry:缺省為NO奏夫。設(shè)置為YES時(shí),當(dāng)用戶進(jìn)入一個(gè)iBeaconRegion內(nèi)历筝,無(wú)論此應(yīng)用處于什么狀態(tài)酗昼,調(diào)用此委托方法
- (void)locationManager:(CLLocationManager *)manager didEnterRegion:(CLRegion *)region {
if ([region isMemberOfClass:[CLBeaconRegion class]]) {
NSLog(@"---??ibeacon----進(jìn)入IBeacon區(qū)域-----%@", region.identifier);
}
}
// notifyOnExit:缺省為NO。設(shè)置為YES時(shí)梳猪,當(dāng)用戶進(jìn)入一個(gè)iBeaconRegion內(nèi)麻削,無(wú)論此應(yīng)用處于什么狀態(tài),調(diào)用此委托方法
- (void)locationManager:(CLLocationManager *)manager didExitRegion:(CLRegion *)region {
if ([region isMemberOfClass:[CLBeaconRegion class]]) {
NSLog(@"---??ibeacon----離開(kāi)IBeacon區(qū)域-----%@", region.identifier);
}
}
- (void)locationManager:(CLLocationManager *)manager didStartMonitoringForRegion:(CLRegion *)region {
if ([region isMemberOfClass:[CLBeaconRegion class]]) {
NSLog(@"---??ibeacon----發(fā)起監(jiān)聽(tīng)成功-----%@", region.identifier);
}
}
- (void)locationManager:(CLLocationManager *)manager monitoringDidFailForRegion:(nullable CLRegion *)region withError:(NSError *)error {
if ([region isMemberOfClass:[CLBeaconRegion class]]) {
NSLog(@"---??ibeacon----監(jiān)聽(tīng)失敗-----%@", region.identifier);
}
}
// notifyEntryStateOnDisplay:缺省為NO春弥。設(shè)置為YES時(shí)碟婆,當(dāng)屏幕點(diǎn)亮且在一個(gè)iBeaconRegion內(nèi),無(wú)論此應(yīng)用處于什么狀態(tài)惕稻,調(diào)用此委托方法
- (void)locationManager:(CLLocationManager *)manager didDetermineState:(CLRegionState)state forRegion:(CLRegion *)region {
if (![region isMemberOfClass:[CLBeaconRegion class]]) {
return;
}
NSLog(@"---??ibeacon----觸發(fā)-----%ld--%@", (long)state, region.identifier);
NSString *identifier = region.identifier;
NSArray<NSString *> *identifierInfos = [region.identifier componentsSeparatedByString:@"_"];
if (identifierInfos.count != kBleUnlockReginIdentifierElementCount) {
return;
}
switch (state) {
case CLRegionStateInside: {
BOOL isHandling = [[self.handleBeaconList objectForKey:identifier] boolValue];
if (isHandling) {
return;
}
[self.handleBeaconList setValue:@(YES) forKey:identifier];
// NSString *pid = [identifierInfos objectAtIndex:0];
NSString *devId = [identifierInfos objectAtIndex:1];
// NSString *uid = [identifierInfos objectAtIndex:3];
NSLog(@"will find device %@", devId);
NSTimeInterval limitSeconds = [NSDate dateWithTimeIntervalSinceNow:5].timeIntervalSince1970;
do {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:2]];
} while (![SmartDevice deviceWithDeviceId:devId] && ([NSDate date].timeIntervalSince1970 < limitSeconds));
self.deviceModel = [SmartDevice deviceWithDeviceId:devId].deviceModel;
if (!self.deviceModel) {
NSLog(@"not find device %@", devId);
[self resetStatesWithIdentifier:identifier];
return;
}
NSLog(@"did find device %@", self.deviceModel.devId);
// devId一致、設(shè)備不在線才可以發(fā)起連接藍(lán)牙
if ([self.deviceModel.devId isEqualToString:devId] && !self.deviceModel.isOnline) {
// 查找到設(shè)備才開(kāi)啟藍(lán)牙
[self addBleConnectedStateObserver];
[self bleStartListening];
// 50秒后如果藍(lán)牙沒(méi)連上重連一次
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(50 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self repeatBleStartListening:devId];
});
}
[self resetStatesWithIdentifier:identifier];
}
case CLRegionStateOutside:
case CLRegionStateUnknown:
if (_needRequestStateForRegion) {
if (_requestStateForRegionCount < MAX_RETRY_COUNT) {
_requestStateForRegionCount++;
[self.locationManager requestStateForRegion:region];
} else {
_needRequestStateForRegion = NO;
_requestStateForRegionCount = 0;
}
}
break;
default:
break;
}
}
- (void)locationManager:(CLLocationManager *)manager didRangeBeacons:(NSArray<CLBeacon *> *)beacons inRegion:(CLBeaconRegion *)region {
}
2.解鎖
家里面的門鎖蝙叛,汽車的門鎖俺祠,如果硬件的模組支持IBeacon,iPhone進(jìn)入門鎖/汽車藍(lán)牙的范圍借帘,門鎖發(fā)送IBeacon廣播包蜘渣,喚醒APP并發(fā)起重連,重連成功之后APP下發(fā)指令來(lái)解鎖肺然,解鎖成功之后需要馬上移除IBeacon監(jiān)聽(tīng)蔫缸,防止重復(fù)開(kāi)鎖。
踩坑點(diǎn)
???IBeacon是Core Location框架提供的一個(gè)能力际起,那就必須開(kāi)啟位置權(quán)限拾碌,并且位置權(quán)限為始終允許吐葱,精確位置也要打開(kāi)。否則APP在后臺(tái)校翔、kill進(jìn)程的時(shí)候弟跑,是沒(méi)法通過(guò)IBeacon喚醒APP的。除了定位權(quán)限防症,后臺(tái)藍(lán)牙通訊的權(quán)限也必須開(kāi)啟孟辑,如果你的業(yè)務(wù)場(chǎng)景是:手機(jī)作為藍(lán)牙中心 server,主動(dòng)發(fā)現(xiàn)藍(lán)牙設(shè)備蔫敲,建立連接饲嗽,后臺(tái)不斷刷新和保持藍(lán)牙通信會(huì)話,那么需要開(kāi)啟Uses Bluetooth LE accessories奈嘿。如果你的業(yè)務(wù)場(chǎng)景是:把手機(jī)模擬成藍(lán)牙設(shè)備貌虾,被迫連接,那么需要開(kāi)啟Acts as a Bluetooth LE accessory指么。