iBeacon的使用

weixin_33850890發表於2018-12-24

聽說可以通過iBeacon啟用IOS App,便研究了一下。

一、基本原理

iBeacon一般用於外設的,外設加入了iBeacon後,就會不斷廣播一定範圍的訊號。如果App加入了iBeacon的監聽,那麼如果App進入了外設的廣播範圍,那麼App裡面的iBeacon回撥就會有反應,也是通過這種方法啟用被掛起的App。因為我沒有嵌入了iBeacon的外設,這裡我是通過把mac改造成iBeacon外設,網上有個mac平臺的軟體:
https://github.com/timd/MactsAsBeacon
這軟體介面如下:

9352478-85dae27a1bb7e2c4.png
image.png

然後,看到這個介面你知道App怎麼初始化iBeacon了吧:

NSUUID *uuid = [[NSUUID alloc] initWithUUIDString:IBEACON_UUID];
_beaconRegion = [[CLBeaconRegion alloc] initWithProximityUUID:uuid major:2 minor:1000 identifier:[[NSBundle mainBundle] bundleIdentifier]];

二、IOS端IBeacon的開發

iBeacon的使用是基於藍芽和定位的,所以我這裡使用了許可權:(網上都說是需要基於藍芽的,但我測試過程中,把藍芽關閉了,也是可以的,但我還是加入了藍芽的許可權)

Location Always and When In Use Usage Description
Bluetooth Peripheral Usage Description
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<array>
    <string>bluetooth-peripheral</string>
    <string>location</string>
</array>
</plist>

App的程式碼部分:

@property(nonatomic) CLLocationManager *locationManager;
@property(nonatomic) CLBeaconRegion *beaconRegion;

- (CLLocationManager *)locationManager{
    if(!_locationManager){
        
        _locationManager = [[CLLocationManager alloc] init];
        _locationManager.distanceFilter = 1.0f; //kCLDistanceFilterNone
        _locationManager.delegate = self;
        //控制定位精度,越高耗電量越
//        _locationManager.desiredAccuracy = kCLLocationAccuracyBestForNavigation;
    }
    
    return _locationManager;
}

- (CLBeaconRegion *)beaconRegion{
    if(!_beaconRegion){
        
        NSUUID *uuid = [[NSUUID alloc] initWithUUIDString:IBEACON_UUID];
//        _beaconRegion = [[CLBeaconRegion alloc]initWithProximityUUID:uuid identifier: [[NSBundle mainBundle] bundleIdentifier]];
        _beaconRegion = [[CLBeaconRegion alloc] initWithProximityUUID:uuid major:2 minor:1000 identifier:[[NSBundle mainBundle] bundleIdentifier]];
        _beaconRegion.notifyOnExit = YES;
        _beaconRegion.notifyOnEntry = YES;
        _beaconRegion.notifyEntryStateOnDisplay = YES;
    
    }
    
    return _beaconRegion;
}

//申請定位許可權並且開啟iBeacon監聽
- (void)startLocation{
    
    CLAuthorizationStatus state = [CLLocationManager authorizationStatus];
    if(state == kCLAuthorizationStatusNotDetermined){
        
        if([self.locationManager respondsToSelector:@selector(requestAlwaysAuthorization)]){
            [self.locationManager requestAlwaysAuthorization];
        }
    }
    if(state == kCLAuthorizationStatusDenied){
        NSString *message = @"您的手機目前未開啟定位服務,如欲開啟定位服務,請至設定開啟定位服務功能";
        UIAlertController * ac = [UIAlertController alertControllerWithTitle:@"Tip" message:message preferredStyle:UIAlertControllerStyleAlert];
        UIAlertAction * action1 = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
            ;
        }];
        
        [ac addAction:action1];
        [self presentViewController:ac animated:YES completion:nil];
        return;
    }
    if(state == kCLAuthorizationStatusRestricted){
        NSString *message = @"定位許可權被限制";
        UIAlertController * ac = [UIAlertController alertControllerWithTitle:@"Tip" message:message preferredStyle:UIAlertControllerStyleAlert];
        UIAlertAction * action1 = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
            ;
        }];
        
        [ac addAction:action1];
        [self presentViewController:ac animated:YES completion:nil];
        return;
    }
    
    if( [CLLocationManager isMonitoringAvailableForClass:[self.beaconRegion class]] ) {
        //開始定位
        [self.locationManager startMonitoringForRegion:self.beaconRegion];
    }
    
    if (_beaconRegion && [CLLocationManager isRangingAvailable]) {
        NSLog(@"startRangingBeaconsInRegion");
        //開啟監聽
        [_locationManager startRangingBeaconsInRegion:_beaconRegion];
    }
}

iBeacon的各種回撥:

// delegate
- (void)locationManager:(CLLocationManager *)manager didStartMonitoringForRegion:(CLRegion *)region{
    _errorLab.text = @"didStartMonitoringForRegion";
    WLlog(@"didStartMonitoringForRegion");
}
// 裝置進入該區域時的回撥
- (void)locationManager:(CLLocationManager *)manager didEnterRegion:(CLRegion *)region{
    //App在後臺才會執行
    self.view.backgroundColor = [UIColor redColor];
    _errorLab.text = @"didEnterRegion";
    WLlog(@"didEnterRegion");
}
// 裝置退出該區域時的回撥
- (void)locationManager:(CLLocationManager *)manager didExitRegion:(CLRegion *)region{
    //App在後臺才會執行
    self.view.backgroundColor = [UIColor blackColor];
    _errorLab.text = @"didExitRegion";
    WLlog(@"didExitRegion");
}
// 有錯誤產生時的回撥
- (void)locationManager:(CLLocationManager *)manager monitoringDidFailForRegion:(nullable CLRegion *)region withError:(NSError *)error{
    _errorLab.text = @"monitoringDidFailForRegion";
}

- (void)locationManager:(CLLocationManager *)manager
        didRangeBeacons:(NSArray<CLBeacon *> *)beacons inRegion:(CLBeaconRegion *)region{
    NSString *proximity = @"unKnow";
    CLBeacon *beacon = [beacons firstObject];
    switch (beacon.proximity) {
        case CLProximityImmediate:
        {
//            NSLog(@"very close");
            proximity = @"very close";
        }
            break;
        case CLProximityNear:
        {
//            NSLog(@"near");
            proximity = @"near";
        }
            break;
        case CLProximityFar:
        {
//            NSLog(@"far");
            proximity = @"far";
        }
            break;
            
        default:
        {
            NSLog(@"unKnow");
        }
            break;
    }
    proximity = [NSString stringWithFormat:@"%@\n%f meter\nrssi:%zi",proximity,beacon.accuracy,beacon.rssi];
    
    _textLab.text = proximity;
    
//    NSLog(@">>>>%f meter  rssi:%zi",beacon.accuracy,beacon.rssi);
}

- (void)locationManager:(CLLocationManager *)manager
      didDetermineState:(CLRegionState)state forRegion:(CLRegion *)region API_AVAILABLE(ios(7.0), macos(10.10)) API_UNAVAILABLE(watchos, tvos){

    //第一次執行的時候,會走這裡,之後App在前臺的情況下,這裡就算狀態傳送改變了,也沒有執行。App在後臺才會執行
    NSString *msg = @"didDetermineState";
    
    if(state == CLRegionStateInside){
        msg = @"Inside";
        [[LocalNotifyManager sharedInstanced] pushLocalNotification:@"Tip" alertBody:msg flag:@"test" infoDic:nil];
        [self checkAppLiveTime];
    }
    if(state == CLRegionStateOutside){
        msg = @"Outside";
        [[LocalNotifyManager sharedInstanced] pushLocalNotification:@"Tip" alertBody:msg flag:@"test" infoDic:nil];
        [self checkAppLiveTime];
    }
    
    _errorLab.text = msg;
    WLlog(msg);
}

很多人說didEnterRegion和didExitRegion都沒有執行,我測試過了,只有App進入到後臺時候,這兩個方法才有機會執行。

三、App後臺被啟用的時間

我使用了通知來測試後臺的啟用。App進入到後臺了,通知被啟用了,說明App確實被啟用了。
我接著測試了App被啟用的時間:

- (void)checkAppLiveTime{
    //經過測試,每次App可有10秒左右的啟用時間,加上starBGTask後,可以存活幾分鐘
//    [self starBGTask];
    if(_liveTimer){
        [_liveTimer invalidate];
        _liveTimer = nil;
    }
    
    _liveTimer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(timeAction) userInfo:nil repeats:YES];
}

通過定時器,每秒寫入內容到檔案中。測試的結論是,啟用時間大概10秒左右。當然可以通過設定後臺任務讓時間延長一點,程式碼如下:

- (void)starBGTask {
    if (_bgTask == UIBackgroundTaskInvalid) {
        UIApplication *app = [UIApplication sharedApplication];
        __block UIBackgroundTaskIdentifier bgTask;
        bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
            NSLog(@"EndBackgroundTask");
            bgTask = UIBackgroundTaskInvalid;
        }];
        _bgTask = bgTask;
    }
}