遇到一個需求,要求監測若干區域,設備進入這些區域則要上傳數據,且可以后台監測,甚至app被殺死也要監測。發現oc的地理圍欄技術完美匹配這個需求,任務做完了,把遇到的坑記錄下來,也許能幫到你呢。
要做這個需求,我們需要把任務分成兩大塊,一塊是支持后台監測且app被殺掉也要持續監測,另一塊是如何進行區域監測。
而區域監測我們有3種方法完成:
1,oc自有的,利用CLLocationManager監測若干CLCircularRegion區域
2,高德地圖舊版地理圍欄,利用AMapLocationManager監測若干AMapLocationCircleRegion區域。其實是對CLLocationManager進行簡單封裝,用法也和CLLocationManager基本一致
3,高德地圖新版地理圍欄,有個專門進行區域監測的管理類AMapGeoFenceManager,該方法對區域監測做了很多優化。
當圍欄創建完畢,且圍欄創建成功時會啟動定位,這部分無需您來設置,SDK內部執行。 定位機制:通過“遠離圍欄時逐漸降低定位頻率”來降低電量消耗,“離近圍欄時逐漸提高定位頻率”來保證有足夠的定位精度從而完成圍欄位置檢測。需要注意,在iOS9及之后版本的系統中,如果您希望程序在后台持續檢測圍欄觸發行為,需要保證manager的allowsBackgroundLocationUpdates為YES,設置為YES的時候必須保證 Background Modes 中的 Location updates 處於選中狀態,否則會拋出異常。
一 如何實現后台定位且被殺掉也能持續定位
1 實現后台定位
1.1 工程配置
- iOS8之前
如果想要定位需要在plist文件中位置keyPrivacy - Location Usage Description
,默認只在前台定位
,如果想開啟后台定位需要在開啟后台模式
Snip20150825_1.png - iOS8
需要在plist文件中配置NSLocationWhenInUseUsageDescription(前台定位)
NSLocationAlwaysUsageDescription(前后台定位) 注:可以兩個都配置上
1.2 用戶權限請求(代碼實現)
利用CLLocationManager的實例去請求權限,如果使用的是高德地圖,就用AMapLocationManager或者AMapGeoFenceManager的實例去請求。需要注意的是,不管使用哪一個類,只要有一個去請求權限就可以了。
//請求前台定位權限 - (void)requestWhenInUseAuthorization __OSX_AVAILABLE_STARTING(__MAC_NA, __IPHONE_8_0); //請求前后台定位權限,即使當前權限為前台定位也會生效 - (void)requestAlwaysAuthorization __OSX_AVAILABLE_STARTING(__MAC_NA, __IPHONE_8_0);
-
注意:如果是前台定位權限,但是開始了后台模式,在后台也是可以定位的,但是屏幕的上邊會有藍條,提示用戶是哪個應用在定位
-
iOS 9
如果想要在后台定位,除了配置NSLocationAlwaysUsageDescription(前后台定位)
外,還需要手動設置allowsBackgroundLocationUpdates = YES
-
指定定位是否會被系統自動暫停屬性也要設置為NO。pausesLocationUpdatesAutomatically = NO;
2 實現app被殺掉也能定位
如果你申請了后台定位權限且用戶同意,那么當你的定位請求被觸發的時候,比如位置移動1000米重新定位,系統會自動喚醒你的app,在application:didFinishLaunchingWithOptions方法中,
UIApplicationLaunchOptionsLocationKey
就是被定位喚醒
在被喚醒后一定要創建你的定位或監測的對象。這樣才能響應到定位監測的回調。在我的例子里,self.regionManager是一個單例,只要app啟動,就會創建並且開始檢測,這一步至關重要,是實現app被殺掉也能定位的最關鍵步驟。
你在被喚醒的這段時間可以做一些簡單操作,可以保存定位信息,上傳監測數據等。
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { [self locationTest:launchOptions]; return YES; } - (void)locationTest:(NSDictionary *)launchOptions { [self.regionManager starMonitorRegion]; if ([launchOptions objectForKey:UIApplicationLaunchOptionsLocationKey]) { [self.regionManager saveMessage:@"被區域監測喚醒"]; } } - (RegionManager *)regionManager { return [RegionManager shareInstance]; }
以上就是如何實現后台定位且被殺掉也能定位。接下來我們討論如何進行區域監測。
二 區域監測,也稱地理圍欄,或者臨近警告
如果希望iOS設備進出某個區域發出通知,那么這種區域監測的功能也被稱為臨近警告。所謂臨近警告的示意圖如圖所示。
臨近警告的示意圖
1 oc自有的地理圍欄實現
利用CoreLocation就可以實現地理圍欄,
1.1 創建CLLocationManager對象,該對象負責獲取定位相關信息,並為該對象設置一些必要的屬性。
-(CLLocationManager *)locationM { if (!_locationM) { _locationM = [[CLLocationManager alloc] init]; _locationM.delegate = self; _locationM.desiredAccuracy = kCLLocationAccuracyBest; _locationM.distanceFilter = 10; // 主動請求定位授權 #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000 [_locationM requestAlwaysAuthorization]; #endif //這是iOS9中針對后台定位推出的新屬性 不設置的話 可是會出現頂部藍條的哦(類似熱點連接) #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 90000 [_locationM setAllowsBackgroundLocationUpdates:YES]; #endif _locationM.pausesLocationUpdatesAutomatically = NO; } return _locationM; }
1.2 為CLLocationManager指定delegate屬性,該屬性值必須是一個實現CLLocationManagerDelegate協議的對象,實現CLLocationManagerDelegate協議的對象.實現CLLocationManagerDelegate協議時可根據需要實現協議中特定的方法.
// 進入指定區域以后將彈出提示框提示用戶 -(void)locationManager:(CLLocationManager *)manager didEnterRegion:(CLRegion *)region { NSString *message; for (StudentInfoModel *student in self.studentArray) { if ([region.identifier isEqualToString:student.qingqingUserId]) { message = [NSString stringWithFormat:@"進入輕輕家教第%d中心區域",[student.qingqingUserId intValue]/1000+1]; } } [[[UIAlertView alloc] initWithTitle:@"區域檢測提示" message:message delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil] show]; } // 離開指定區域以后將彈出提示框提示用戶 -(void)locationManager:(CLLocationManager *)manager didExitRegion:(CLRegion *)region { NSString *message; for (StudentInfoModel *student in self.studentArray) { if ([region.identifier isEqualToString:student.qingqingUserId]) { message = [NSString stringWithFormat:@"離開輕輕家教第%d中心區域",[student.qingqingUserId intValue]/1000+1]; } } [[[UIAlertView alloc] initWithTitle:@"區域檢測提示" message:message delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil] show]; } /** * 監聽區域失敗時調用 * * @param manager 位置管理者 * @param region 區域 * @param error 錯誤 */ -(void)locationManager:(CLLocationManager *)manager monitoringDidFailForRegion:(CLRegion *)region withError:(NSError *)error { NSLog(@"監聽區域失敗"); }
1.3調用CLLocationManager的startMonitoringForRegion:方法進行區域監測.區域監測結束時,可調用stopMonitoringForRegion:方法結束區域監測.可以監測多個區域,數量沒有上線,不過要考慮性能。
- (void)starMonitorRegion { //監聽最近聯系的20個家長 測試數據 for (int i = 0; i < 20 ; i++) { StudentInfoModel *student = [[StudentInfoModel alloc] init]; CLLocationCoordinate2D companyCenter; companyCenter.latitude = 31.200546; companyCenter.longitude = 121.599263 + i*0.005; student.location = companyCenter; student.qingqingUserId = [NSString stringWithFormat:@"%d",i*1000]; [self.studentArray addObject:student]; [self regionObserve:student]; } } - (void)regionObserve:(StudentInfoModel *)student { if([CLLocationManager locationServicesEnabled]) { // 定義一個CLLocationCoordinate2D作為區域的圓 // 使用CLCircularRegion創建一個圓形區域, // 確定區域半徑 CLLocationDistance radius = 200; // 使用前必須判定當前的監聽區域半徑是否大於最大可被監聽的區域半徑 if(radius > self.locationM.maximumRegionMonitoringDistance) { radius = self.locationM.maximumRegionMonitoringDistance; } CLRegion* fkit = [[CLCircularRegion alloc] initWithCenter:student.location radius:radius identifier:student.qingqingUserId]; // 開始監聽fkit區域 [self.locationM startMonitoringForRegion:fkit]; // 請求區域狀態(如果發生了進入或者離開區域的動作也會調用對應的代理方法) [self.locationM requestStateForRegion:fkit]; } else { // 使用警告框提醒用戶 [[[UIAlertView alloc] initWithTitle:@"提醒" message:@"您的設備不支持定位" delegate:self cancelButtonTitle:@"確定" otherButtonTitles: nil] show]; } }
2 高德地圖的舊版地理圍欄
舊版地理圍欄和oc自有的用法基本一致,這里就不累贅。CLLocationManager換成AMapLocationManager,CLCircularRegion換成AMapLocationCircleRegion。文章結尾會有demo下載。
3 高德地圖的新版地理圍欄
新版的高德地圖對地理圍欄進行了優化,把地理圍欄從AMapLocationManager中剝離,有了自己單獨的管理類AMapGeoFenceManager。當圍欄創建完畢,且圍欄創建成功時會啟動定位,這部分無需您來設置,SDK內部執行。 定位機制:通過“遠離圍欄時逐漸降低定位頻率”來降低電量消耗,“離近圍欄時逐漸提高定位頻率”來保證有足夠的定位精度從而完成圍欄位置檢測。需要注意,在iOS9及之后版本的系統中,如果您希望程序在后台持續檢測圍欄觸發行為,需要保證manager的allowsBackgroundLocationUpdates為YES,設置為YES的時候必須保證 Background Modes 中的 Location updates 處於選中狀態,否則會拋出異常。
- AMapGeoFenceManager創建,並設置相關屬性
- (AMapGeoFenceManager *)geoFenceManager { if (!_geoFenceManager) { _geoFenceManager = [[AMapGeoFenceManager alloc] init]; _geoFenceManager.delegate = self; _geoFenceManager.activeAction = AMapGeoFenceActiveActionInside | AMapGeoFenceActiveActionOutside; //設置希望偵測的圍欄觸發行為,默認是偵測用戶進入圍欄的行為,即AMapGeoFenceActiveActionInside,這邊設置為進入,離開觸發回調 _geoFenceManager.allowsBackgroundLocationUpdates = YES; //允許后台定位 _geoFenceManager.pausesLocationUpdatesAutomatically = NO; } return _geoFenceManager; }
- 監聽區域,可以多個
// 清除上一次創建的圍欄 - (void)doClear { [self.geoFenceManager removeAllGeoFenceRegions]; //移除所有已經添加的圍欄,如果有正在請求的圍欄也會丟棄 } - (void)starMonitorRegions{ if([CLLocationManager locationServicesEnabled]) {
[self doClear]; //監聽最近聯系的20個家長 測試數據 for (int i = 0; i < kObserveStudentNum ; i++) { StudentInfoModel *student = [[StudentInfoModel alloc] init]; CLLocationCoordinate2D companyCenter; companyCenter.latitude = 31.200546; companyCenter.longitude = 121.599263 + i*0.005; student.location = companyCenter; student.qingqingUserId = [NSString stringWithFormat:@"%d",i*1000]; [self monitorRegion:student]; } } else { DDLogInfo(@"定位服務.定位不可用"); #if DEBUG [QQThemeAlertView showWithTitle:@"定位不能用了" message:@"設備不支持定位,找開發!" options:@[@"我知道了"] block:nil]; #endif } } - (void)monitorRegion:(StudentInfoModel *)studentInfo { // 確定區域半徑和圓心 CLLocationCoordinate2D regionCenter; regionCenter.latitude = studentInfo.latitude; regionCenter.longitude = studentInfo.longitude; //該區域的唯一標識 NSString *customID = [NSString stringWithFormat:@"%lf+%lf",studentInfo.latitude,studentInfo.longitude]; [self.geoFenceManager addCircleRegionForMonitoringWithCenter:regionCenter radius:kRegionRadius customID:customID]; } -
實現manager的delegate方法
#pragma mark - AMapGeoFenceManagerDelegate //添加地理圍欄完成后的回調,成功與失敗都會調用 - (void)amapGeoFenceManager:(AMapGeoFenceManager *)manager didAddRegionForMonitoringFinished:(NSArray<AMapGeoFenceRegion *> *)regions customID:(NSString *)customID error:(NSError *)error { if (error) { DDLogInfo(@"區域檢測.添加地理圍欄失敗%@",error); } else { AMapGeoFenceRegion *region = [regions firstObject]; DDLogInfo(@"區域檢測.添加地理圍欄成功%@",region.customID); //[self saveMessage:[NSString stringWithFormat:@"區域檢測.添加地理圍欄成功%@",region.customID]]; } } //地理圍欄狀態改變時回調,當圍欄狀態的值發生改變,定位失敗都會調用 - (void)amapGeoFenceManager:(AMapGeoFenceManager *)manager didGeoFencesStatusChangedForRegion:(AMapGeoFenceRegion *)region customID:(NSString *)customID error:(NSError *)error { if (error) { DDLogInfo(@"區域檢測.定位失敗%@",error); }else{ DDLogInfo(@"區域檢測.狀態改變%@",[region description]); [self saveMessage:[NSString stringWithFormat:@"區域檢測.狀態改變%ld",[region fenceStatus]]]; switch (region.fenceStatus) { case AMapGeoFenceRegionStatusInside: { //在區域內 } break; case AMapGeoFenceRegionStatusOutside: { //在區域外 } break; case AMapGeoFenceRegionStatusStayed: { //停留超過十分鍾 } break; default: { //未知 } break; } } }
-
到此,地理圍欄技術講解結束,遇到的坑:
1 新版地理圍欄,高德文檔寫區域監測半徑大於0即可,然而我用模擬器測試,跑gpx文件模擬路線,大於250m才有回調,自己修改模擬器customLocation位置,大於500m才有回調,目前位置還沒有搞明白。有遇到同樣問題歡迎討論。
2 要實現app被殺死持續監測區域,一定要知道當你進入監測區域,系統會喚醒app,在application:didFinishLaunchingWithOptions方法中要有處理定位回調的實例。不然只能實現后台監測。
DEMO下載:
1 利用oc自有CLLocationManager監測區域DEMO:https://github.com/wangdachui/monitorRegion
2 高德地圖的地理圍欄,官方DEMO已經寫得很詳細,注釋也很清除。看了高德的注釋也讓我明白了CLLocationManager監測區域的實現,贊一個。DEMO地址:http://lbs.amap.com/api/ios-location-sdk/download/