問題の声明
アプリケーションでは、アプリケーションがバックグラウンドで実行されているとき(比較的許容できる精度で)、およびアプリケーションがアクティブであるとき(高精度で)にユーザーの位置を追跡する必要があります。
解決策
額の解決策は、 [CLLocationManagerInstance startUpdatingLocation]コールバックからのデータをバックグラウンドおよびアプリケーションがアクティブなときに使用することです。 このソリューションの最初で最も重大な欠点は、消費電力が高いことです(数時間で、iPhoneのバッテリーが完全になくなる可能性があります)。 2番目-アプリケーションが最小化されて「強制終了」された場合、ユーザーの位置の更新を取得できません。
これらの2つの問題を解決し、このソリューションをメインアプリケーションコードに関係なく分離するために、アクティブアプリケーションモードで[CLLocationManagerInstance startUpdatingLocation]を使用し、バックグラウンドで[CLLocationManagerInstance startMonitoringSignificantLocationChanges]を使用するコンポーネントを記述します。 コンポーネントには2つのブロックがあり、アプリケーションの状態に応じて実行されます。
ユーザーの場所
前景
アクティブなアプリケーションの場合、ソリューションは明らかです。CLLocationManagerインスタンスを作成し、デリゲートをインストールして、受信したデータをコールバックで処理する必要があります。 ラッパーオブジェクトを作成します。
#import <CoreLocation/CoreLocation.h> typedef void(^locationHandler)(CLLocation *location); @interface DLLocationTracker : NSObject <CLLocationManagerDelegate> @property (nonatomic, strong) CLLocationManager *locationManager; @property (nonatomic, copy) locationHandler locationUpdatedInForeground; - (void)startUpdatingLocation; - (void)stopUpdatingLocation; @end
locationUpdatedInForegroundブロックは、ユーザーの場所が更新されると実行されます。 オブジェクトがコントローラーで作成された後、 startUpdatingLocationメソッドを呼び出してサービスを開始する必要があります。
背景
前述のように、バックグラウンドで座標の更新を取得する主な方法は2つあります。
- * .plistアプリケーションでUIBackgroundModes = "location"を設定し、[locationManager startUpdatingLocation]を使用します-非常にエネルギーを消費しますが、正確な方法です。
- 重要な場所の変更を使用(> iOS 4.0)-エネルギー効率が高く、携帯電話ネットワークのデータを使用します。 約10〜15分ごとに更新され、エラーは最大500メートルです(実験的に決定されます)。 詳細はこちらをご覧ください 。
2番目のアプローチを使用します。
コンポーネントのヘッダーを更新します。
#import <CoreLocation/CoreLocation.h> typedef void(^locationHandler)(CLLocation *location); @interface DLLocationTracker : NSObject <CLLocationManagerDelegate> @property (nonatomic, strong) CLLocationManager *locationManager; @property (nonatomic, copy) locationHandler locationUpdatedInForeground; @property (nonatomic, copy) locationHandler locationUpdatedInBackground; - (void)startUpdatingLocation; - (void)stopUpdatingLocation; - (void)endBackgroundTask; @end
locationUpdatedInBackgroundブロックは、アプリケーションがバックグラウンドで座標の更新を受け取ると呼び出されます。
endBackgroundTask-バックグラウンドで実行中のタスクを終了できるメソッド(後で確認します)。
また、* .plistアプリケーションでは、必要なバックグラウンドモード= {アプリが位置情報更新のために登録する}アイテムを追加する必要があります 。
重要な場所の変更メカニズムにより、アプリケーションが実行されていなくても場所の更新を受信できます。 これを行うには、標準メソッドappDelegate applicationDidFinishLaunchingWithOptionsをわずかに書き換える必要があります 。
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { if ([launchOptions objectForKey:UIApplicationLaunchOptionsLocationKey]) { self.locationTracker = [[DLLocationTracker alloc] init]; [self.locationTracker setLocationUpdatedInBackground:^(CLLocation *location) { // , local notification UILocalNotification *notification = [[UILocalNotification alloc] init]; notification.fireDate = [NSDate dateWithTimeIntervalSinceNow:15]; notification.alertBody = [NSString stringWithFormat:@"New location: %@", location]; [[UIApplication sharedApplication] scheduleLocalNotification:notification]; }]; [self.locationTracker startUpdatingLocation]; } ..... }
UIApplicationLaunchOptionsLocationKey-受信した位置変更イベントに応答してアプリケーションが起動されたことを示すキー。
コンポーネントの実装
コンポーネントが初期化されると、CLLocationManagerインスタンスが作成され、オブジェクトはそのデリゲートによって設定されます。また、アプリケーションの状態(アクティブ/バックグラウンド)の変更に関する通知に署名します。
- (id)init { if (self = [super init]) { [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationDidBecomeActive) name:UIApplicationDidBecomeActiveNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationDidEnterBackground) name: UIApplicationDidEnterBackgroundNotification object:nil]; self.locationManager = [[CLLocationManager alloc] init]; self.locationManager.delegate = self; } return self; }
次に、startUpdatingLocationを呼び出します。
- (void)startUpdatingLocation { [self stopUpdatingLocation]; [self isInBackground] ? [self.locationManager startMonitoringSignificantLocationChanges] : [self.locationManager startUpdatingLocation]; }
アプリケーションの状態に応じて、目的のサービスがアクティブになります。
すべての楽しみはCLLocationManagerコールバックで発生します。
- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation { // if (oldLocation && ([newLocation.timestamp timeIntervalSinceDate:oldLocation.timestamp] < kMinUpdateTime || [newLocation distanceFromLocation:oldLocation] < kMinUpdateDistance)) { return; } if ([self isInBackground]) { if (self.locationUpdatedInBackground) { bgTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler: ^{ [[UIApplication sharedApplication] endBackgroundTask:bgTask]; }]; self.locationUpdatedInBackground(newLocation); [self endBackgroundTask]; } } else { // - if (self.locationUpdatedInForeground) { self.locationUpdatedInForeground(newLocation); } } }
アプリケーションがバックグラウンドで何かを行えるようにするには、 beginBackgroundTaskWithExpirationHandlerメソッドを呼び出してbgTask識別子を初期化する必要があります(UIBackgroundTaskIdentifier型)。 このメソッドの各呼び出しは、 endBackgroundTask :を呼び出すことによってバランスを取る必要があります。
- (void)endBackgroundTask { if (bgTask != UIBackgroundTaskInvalid) { [[UIApplication sharedApplication] endBackgroundTask:bgTask]; bgTask = UIBackgroundTaskInvalid; } }
重要な点は、locationUpdatedInBackgroundブロックが同期的に実行されることです(アプリケーションがバックグラウンドにある場合は余裕があります)。これにより、ブロック実行中にアプリケーションが最小化/最小化された場合、つまりブロックが10秒以内に実行されない場合、アプリケーションがクラッシュします。
バックグラウンドから非同期的にデータを送信する
非同期サイレント送信の場合、コンポーネントのコードを変更します。
if ([self isInBackground]) { if (self.locationUpdatedInBackground) { bgTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler: ^{ [[UIApplication sharedApplication] endBackgroundTask:bgTask]; }]; self.locationUpdatedInBackground(newLocation); //[self endBackgroundTask]; - }
LocationUpdatedInBackgroundブロック:
__weak DLLocationTracker *lc = self.locationTracker; [self.locationTracker setLocationUpdatedInBackground:^ (CLLocation *location) { //, completion fail [self sendLocationToServer:location completion:^{ [lc endBackGroundTask]; } fail:^(NSError *fail) { [lc endBackGroundTask]; }]; }];
おわりに
同様のエネルギー効率の高い方法が多くのアプリケーションで使用されています。 たとえば、Forsquareのレーダー機能。 テストアプリケーションのコードはgithubで取得できます。