最近在做公司內部的一個項目。主要需求很簡單,就是每隔N分鐘向服務器發送設備的位置,不管此時App是運行在前臺還是后臺。
這里總結一下使用iOS定位服務的一些關鍵點和需要注意的地方。
App 的設置
- 因為App需要在后臺的時候也能不斷地獲取設備的位置。所以要將Capablities里面的BackgroundMode 設置成Enable。并且勾選其中的Location updates選項。
- 在iOS8以后,需要在info.plist里面添加NSLocationAlwaysUsageDescription或者NSLocationWhenInUseUsageDescription,這兩個key都是NSString類型。使用哪個(或者兩者都添加)取決于申請定位的權限,這個下文會提到。這個所謂的描述就是當系統提示用戶App要使用定位的時候,會加在系統提示的后面,如圖。
初始化CLLocationManager
使用iOS定位服務需要引入系統的頭文件并且實現CLLocationManagerDelegate的代理。
#import <CoreLocation/CoreLocation.h>
先來看一下初始化的代碼:
-(void) createLocationManager{
_locationManager = [[CLLocationManager alloc] init];
_locationManager.delegate = self;
if ([_locationManager respondsToSelector:@selector(requestAlwaysAuthorization)]) {
[_locationManager requestAlwaysAuthorization];
}
if ([_locationManager respondsToSelector:@selector(setAllowsBackgroundLocationUpdates:)]) {
[_locationManager setAllowsBackgroundLocationUpdates:YES];
}
_locationManager.pausesLocationUpdatesAutomatically = NO;
}
iOS8以后,系統的定位權限有三種,對應設置里面的總是,永不,和App使用期間。那么根據我們App的需求,我們需要申請“總是”這種權限。相應地,我們要在info.plist里面添加的是NSLocationAlwaysUsageDescription。
if ([_locationManager respondsToSelector:@selector(requestAlwaysAuthorization)]) {
[_locationManager requestAlwaysAuthorization];
}
并且在iOS9之后,如果需要在后臺保持定位,除了上文所說的在App的setting和info文件里面設置以外,還需要加上下面的代碼:
if ([_locationManager respondsToSelector:@selector(setAllowsBackgroundLocationUpdates:)]) {
[_locationManager setAllowsBackgroundLocationUpdates:YES];
}
整個初始化完成以后,調用以下API系統就會開始定位了
[_locationManager startUpdatingLocation];
在代理里面實現位置更新的代碼
正常來說,完成上面的所有設置,就可以使用iOS系統的定位服務了。
系統會每秒都調用
-(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray<CLLocation *> *)locations
這個代理方法,我們所要做的就是在這里處理系統返回回來的位置信息。
CLLocation這個類里面包括的一些常用的位置信息有經度、緯度、海拔、速度、精確度等等,根據項目的需求可以對其進行相應的處理。
到這里,最基礎的部分已經完成。接下來會探討一些別的配置。
pausesLocationUpdatesAutomatically屬性
貼上一段官網對這個屬性的描述:
Allowing the location manager to pause updates can improve battery life on the target device without sacrificing location data. When this property is set to YES, the location manager pauses updates (and powers down the appropriate hardware) at times when the location data is unlikely to change. For example, if the user stops for food while using a navigation app, the location manager might pause updates for a period of time. You can help the determination of when to pause location updates by assigning a value to the activityTypeproperty.
大致的意思就是如果這個屬性設置成YES(默認的也是YES),那么系統會檢測如果設備有一段時間沒有移動,就會自動停掉位置更新服務。這里需要注意的是,一旦定位服務停止了,只有當用戶再次開啟App的時候定位服務才會重新啟動。
這里的一段時間是系統自動判定的,可以通過設置activityTypeproperty這個屬性來決定這個時間的長短。
API的意思是,類似導航類的App,系統檢驗的時間會稍長一點,想運動類的App,就會比導航類的短一點。但是具體時間還是由系統來決定。
DeferredUpdates
默認地,定位服務的代理會每秒鐘都更新一次位置,這樣對電池的消耗量會特別地大。除了設置pausesLocationUpdatesAutomatically這個屬性以外,iOS還提供了DeferredUpdates的機制。
官方API文檔:
- (void)allowDeferredLocationUpdatesUntilTraveled:(CLLocationDistance)distance
timeout:(NSTimeInterval)timeout
distance:
The distance (in meters) from the current location that must be travelled before event delivery resumes. To specify an unlimited distance, pass the CLLocationDistanceMaxconstant.
timeout:
The amount of time (in seconds) from the current time that must pass before event delivery resumes. To specify an unlimited amount of time, pass the CLTimeIntervalMax constant.
就是你可以設置讓系統每隔多遠或者每隔多長時間更新一次位置。注意是“或”的關系,滿足一個就會更新。
使用這個方法有很多要注意的地方:
- desiredAccuracy必須設置成kCLLocationAccuracyBest
- distanceFilter必須設置成kCLErrorDeferredDistanceFiltered
- 必須能夠使用GPS進行定位(而不僅僅是移動數據或者Wi-Fi)
- 非常重要的一點,DeferredUpdates只會出現在設備進入低耗電量的狀態,App運行在前臺或者設備連接在Xcode上正在調試是不會觸發的。(所以不可能在Debug的時候打印Log來檢驗,要調試的話,需要寫一些Log存在本地的數據庫)
官網的Example:
-(void)locationManager:(CLLocationManager *)manager
didUpdateLocations:(NSArray *)locations {
// Add the new locations to the hike
[self.hike addLocations:locations];
// Defer updates until the user hikes a certain distance or a period of time has passed
if (!self.deferringUpdates) {
CLLocationDistance distance = self.hike.goal - self.hike.distance;
NSTimeInterval time = [self.nextUpdate timeIntervalSinceNow];
[self.locationManager allowDeferredLocationUpdatesUntilTraveled:distance timeout:time];
self.deferringUpdates = YES;
} }
-(void)locationManager:(CLLocationManager *)manager
didFinishDeferredUpdatesWithError:(NSError *)error {
// Stop deferring updates
self.deferringUpdates = NO;
// Adjust for the next goal
}
反地理編碼
知道了經緯度,有時候我們需要獲取這個經緯度對應的詳細地址信息,示例如下:
CLGeocoder *revGeo = [[CLGeocoder alloc] init];
[revGeo reverseGeocodeLocation:location
completionHandler:^(NSArray *placemarks, NSError *error) {
if (!error && [placemarks count] > 0)
{
NSDictionary *dict =
[[placemarks objectAtIndex:0] addressDictionary];
NSArray *formattedLines = [dict objectForKey:@"FormattedAddressLines"];
NSString *formattedAddress = formattedLines[0];
NSLog(@"address is %@",formattedAddress);
}else{
NSLog(@"ERROR: %@", error);
}
}];
關于坐標系的問題
最后講一下關于坐標系的問題。
世界通用的坐標系是WGS坐標系,中國國測局的坐標系是GCJ,百度有自己的坐標系。
同樣的經緯度應用在不同的坐標系會有所偏差,在Github上面有一個庫可以實現不同坐標系之間的轉化:
https://github.com/TinyQ/TQLocationConverter
系統返回的自然是根據WGS定位的。如果使用百度SDK獲取的就是Baidu坐標系的。