[ios] iOS 애플리케이션에서 n 분마다 백그라운드 위치 업데이트를 받으려면 어떻게합니까?

iOS 응용 프로그램에서 n 분마다 백그라운드 위치 업데이트를 얻는 방법을 찾고 있습니다. iOS 4.3을 사용하고 있으며이 솔루션은 손상되지 않은 iPhone에서 작동합니다.

다음 옵션을 시도 / 고려했습니다.

  • CLLocationManager startUpdatingLocation/startMonitoringSignificantLocationChanges: 이것은 구성된 속성에 따라 백그라운드에서 예상대로 작동하지만 n 분마다 위치를 강제로 업데이트 할 수는 없습니다.
  • NSTimer: 앱이 포 그라운드에서 실행될 때 작동하지만 백그라운드 작업을 위해 설계된 것 같지 않습니다.
  • 로컬 알림 : n 분마다 로컬 알림을 예약 할 수 있지만 사용자가 알림을 통해 앱을 시작하지 않고 현재 위치를 얻기 위해 일부 코드를 실행할 수는 없습니다. 이 접근 방식은 알림을 사용해야하는 것이 아니기 때문에 깨끗한 접근 방식이 아닌 것 같습니다.
  • UIApplication:beginBackgroundTaskWithExpirationHandler: 내가 아는 한, 이것은 “장기 실행”백그라운드 프로세스를 구현하는 대신 앱이 백그라운드로 이동 될 때 백그라운드에서 일부 작업 (시간 제한)을 완료하는 데 사용해야합니다.

정기적 인 백그라운드 위치 업데이트를 어떻게 구현할 수 있습니까?



답변

Apple 개발자 포럼의 도움으로 이것을 구현하는 솔루션을 찾았습니다.

  • 지정 location background mode
  • NSTimer와 함께 백그라운드에서 만들기UIApplication:beginBackgroundTaskWithExpirationHandler:
  • n이다 작은 것보다 UIApplication:backgroundTimeRemaining그것을 잘 작동합니다. 때 n이다 는이 location manager다시 전에 살해되는 백그라운드 작업을 피하기 위해 남아있는 시간이 없다 활성화 (장애인)해야합니다.

location은 세 가지 유형의 백그라운드 실행 유형 중 하나이기 때문에 작동합니다 .

참고 : 작동하지 않는 시뮬레이터에서 이것을 테스트하여 시간을 잃었습니다. 그러나 내 전화에서는 제대로 작동합니다.


답변

아이폰 OS 8/9/10 5 분마다 다음을 수행 배경 위치 업데이트로 만들려면 :

  1. 프로젝트-> 기능-> 백그라운드 모드로 이동하여 위치 업데이트를 선택하십시오.

  2. 프로젝트-> 정보-> 빈 값 (또는 선택적으로 모든 텍스트)으로 NSLocationAlwaysUsageDescription 키를 추가하십시오.

  3. 앱이 백그라운드에있을 때 위치가 작동하도록하고 웹 서비스에 좌표를 보내거나 5 분마다 아무 작업을 수행하려면 아래 코드와 같이 구현하십시오.

백그라운드 작업이나 타이머를 사용하고 있지 않습니다. 내 앱을 백그라운드에서 실행하면서 몇 시간 동안 책상에 누워있는 iOS 8.1을 사용하는 기기 에서이 코드를 테스트했습니다. 장치가 잠겨 있고 코드가 항상 제대로 실행되고있었습니다.

@interface LocationManager () <CLLocationManagerDelegate>
@property (strong, nonatomic) CLLocationManager *locationManager;
@property (strong, nonatomic) NSDate *lastTimestamp;

@end

@implementation LocationManager

+ (instancetype)sharedInstance
{
    static id sharedInstance = nil;

    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedInstance = [[self alloc] init];
        LocationManager *instance = sharedInstance;
        instance.locationManager = [CLLocationManager new];
        instance.locationManager.delegate = instance;
        instance.locationManager.desiredAccuracy = kCLLocationAccuracyBest; // you can use kCLLocationAccuracyHundredMeters to get better battery life
        instance.locationManager.pausesLocationUpdatesAutomatically = NO; // this is important
    });

    return sharedInstance;
}

- (void)startUpdatingLocation
{
    CLAuthorizationStatus status = [CLLocationManager authorizationStatus];

    if (status == kCLAuthorizationStatusDenied)
    {
        NSLog(@"Location services are disabled in settings.");
    }
    else
    {
        // for iOS 8
        if ([self.locationManager respondsToSelector:@selector(requestAlwaysAuthorization)])
        {
            [self.locationManager requestAlwaysAuthorization];
        }
        // for iOS 9
        if ([self.locationManager respondsToSelector:@selector(setAllowsBackgroundLocationUpdates:)])
        {
            [self.locationManager setAllowsBackgroundLocationUpdates:YES];
        }

        [self.locationManager startUpdatingLocation];
    }
}

- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
{
    CLLocation *mostRecentLocation = locations.lastObject;
    NSLog(@"Current location: %@ %@", @(mostRecentLocation.coordinate.latitude), @(mostRecentLocation.coordinate.longitude));

    NSDate *now = [NSDate date];
    NSTimeInterval interval = self.lastTimestamp ? [now timeIntervalSinceDate:self.lastTimestamp] : 0;

    if (!self.lastTimestamp || interval >= 5 * 60)
    {
        self.lastTimestamp = now;
        NSLog(@"Sending current location to web service.");
    }
}

@end


답변

개발중인 응용 프로그램 에서이 작업을 수행했습니다. 앱이 백그라운드에있을 때 타이머가 작동하지 않지만 앱이 지속적으로 위치 업데이트를 수신하고 있습니다. 응용 프로그램이 백그라운드에있을 때 활성 실행 루프에서만 메소드를 호출 할 수 있다는 문서의 어딘가를 읽었습니다 (지금 찾을 수없는 것 같습니다. 내가 할 때 업데이트를 게시 할 것입니다). 앱 대리자는 bg에서도 활성 실행 루프를 가지고 있으므로이 작업을 수행하기 위해 직접 만들 필요가 없습니다. [올바른 설명인지 확실하지 않지만 내가 읽은 내용을 어떻게 이해했는지]

우선, 앱의 info.plist에 location키 의 객체를 추가하십시오 UIBackgroundModes. 이제 앱에서 위치 업데이트를 시작하면됩니다.

    CLLocationManager locationManager = [[CLLocationManager alloc] init];
    locationManager.delegate = self;//or whatever class you have for managing location
    [locationManager startUpdatingLocation];

다음으로 -(void)didUpdateToLocation:(CLLocation*)location앱 델리게이트에서 위치 업데이트를 처리하는 메소드를 작성하십시오 . 그런 다음 위치 관리자를 ‘self’로 설정 했으므로 위치 관리자를 시작한 클래스에서 메소드 locationManager:didUpdateLocation:fromLocation를 구현하십시오 CLLocationManagerDelegate. 이 방법 내에서 위치 업데이트를 처리해야하는 시간 간격이 경과했는지 확인해야합니다. 매번 현재 시간을 저장하면됩니다. 해당 시간이 경과하면 앱 델리게이트에서 UpdateLocation 메소드를 호출하십시오.

NSDate *newLocationTimestamp = newLocation.timestamp;
NSDate *lastLocationUpdateTiemstamp;

int locationUpdateInterval = 300;//5 mins

NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
if (userDefaults) {

        lastLocationUpdateTiemstamp = [userDefaults objectForKey:kLastLocationUpdateTimestamp];

        if (!([newLocationTimestamp timeIntervalSinceDate:lastLocationUpdateTiemstamp] < locationUpdateInterval)) {
            //NSLog(@"New Location: %@", newLocation);
            [(AppDelegate*)[UIApplication sharedApplication].delegate didUpdateToLocation:newLocation];
            [userDefaults setObject:newLocationTimestamp forKey:kLastLocationUpdateTimestamp];
        }
    }
}

앱이 백그라운드에있는 경우에도 5 분마다 메소드를 호출합니다. Imp : 위치 데이터의 정확도가 중요하지 않은 경우이 구현은 배터리를 소모합니다.[locationManager startMonitoringSignificantLocationChanges]

이것을 앱에 추가하기 전에 위치 인식 프로그래밍 안내서 를 읽으십시오


답변

이제 iOS6가 영원히 실행되는 위치 서비스를 얻는 가장 좋은 방법은 다음과 같습니다.

- (void)applicationWillResignActive:(UIApplication *)application
{
/*
 Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
 Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
 */

NSLog(@"to background");

app.isInBackground = TRUE;

UIApplication *app = [UIApplication sharedApplication];

// Request permission to run in the background. Provide an
// expiration handler in case the task runs long.
NSAssert(bgTask == UIBackgroundTaskInvalid, nil);

bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
    // Synchronize the cleanup call on the main thread in case
    // the task actually finishes at around the same time.
    dispatch_async(dispatch_get_main_queue(), ^{

        if (bgTask != UIBackgroundTaskInvalid)
        {
            [app endBackgroundTask:bgTask];
            bgTask = UIBackgroundTaskInvalid;
        }
    });
}];

// Start the long-running task and return immediately.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

    // Do the work associated with the task.

    locationManager.distanceFilter = 100;
    locationManager.desiredAccuracy = kCLLocationAccuracyHundredMeters;
    [locationManager startMonitoringSignificantLocationChanges];
    [locationManager startUpdatingLocation];

    NSLog(@"App staus: applicationDidEnterBackground");
    // Synchronize the cleanup call on the main thread in case
    // the expiration handler is fired at the same time.
    dispatch_async(dispatch_get_main_queue(), ^{
        if (bgTask != UIBackgroundTaskInvalid)
        {
            [app endBackgroundTask:bgTask];
            bgTask = UIBackgroundTaskInvalid;
        }
    });
});

NSLog(@"backgroundTimeRemaining: %.0f", [[UIApplication sharedApplication] backgroundTimeRemaining]);

}

다음과 같이 테스트했습니다.

앱을 시작하고 백그라운드로 가서 몇 분 정도 차 안에서 움직입니다. 그런 다음 1 시간 동안 집에 가서 다시 앱을 열지 않고 다시 움직이기 시작합니다. 위치가 다시 시작되었습니다. 그런 다음 2 시간 동안 멈추었다가 다시 시작했습니다. 다 괜찮아 …

iOS6의 새로운 위치 서비스 사용을 잊지 마십시오

- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
{
    CLLocation *loc = [locations lastObject];

    // Lat/Lon
    float latitudeMe = loc.coordinate.latitude;
    float longitudeMe = loc.coordinate.longitude;
}


답변

악몽을 겪고있는 다른 사람에게 이것을 알아보십시오. 간단한 해결책이 있습니다.

  1. raywenderlich.com 에서이 예제를 보십시오 -> 샘플 코드가 있습니다.이 코드는 완벽하게 작동하지만 불행히도 백그라운드 위치에는 타이머가 없습니다. 이것은 무한정 실행됩니다.
  2. 다음을 사용하여 타이머를 추가하십시오.

    -(void)applicationDidEnterBackground {
    [self.locationManager stopUpdatingLocation];
    
    UIApplication*    app = [UIApplication sharedApplication];
    
    bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
        [app endBackgroundTask:bgTask];
        bgTask = UIBackgroundTaskInvalid;
    }];
    
     self.timer = [NSTimer scheduledTimerWithTimeInterval:intervalBackgroundUpdate
                                                  target:self.locationManager
                                                selector:@selector(startUpdatingLocation)
                                                userInfo:nil
                                                 repeats:YES];
    
    }
  3. info.plist에 “위치 업데이트를위한 앱 레지스터”를 추가하는 것을 잊지 마십시오.


답변

내가 사용하는 것은 다음과 같습니다.

import Foundation
import CoreLocation
import UIKit

class BackgroundLocationManager :NSObject, CLLocationManagerDelegate {

    static let instance = BackgroundLocationManager()
    static let BACKGROUND_TIMER = 150.0 // restart location manager every 150 seconds
    static let UPDATE_SERVER_INTERVAL = 60 * 60 // 1 hour - once every 1 hour send location to server

    let locationManager = CLLocationManager()
    var timer:NSTimer?
    var currentBgTaskId : UIBackgroundTaskIdentifier?
    var lastLocationDate : NSDate = NSDate()

    private override init(){
        super.init()
        locationManager.delegate = self
        locationManager.desiredAccuracy = kCLLocationAccuracyKilometer
        locationManager.activityType = .Other;
        locationManager.distanceFilter = kCLDistanceFilterNone;
        if #available(iOS 9, *){
            locationManager.allowsBackgroundLocationUpdates = true
        }

        NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(self.applicationEnterBackground), name: UIApplicationDidEnterBackgroundNotification, object: nil)
    }

    func applicationEnterBackground(){
        FileLogger.log("applicationEnterBackground")
        start()
    }

    func start(){
        if(CLLocationManager.authorizationStatus() == CLAuthorizationStatus.AuthorizedAlways){
            if #available(iOS 9, *){
                locationManager.requestLocation()
            } else {
                locationManager.startUpdatingLocation()
            }
        } else {
                locationManager.requestAlwaysAuthorization()
        }
    }
    func restart (){
        timer?.invalidate()
        timer = nil
        start()
    }

    func locationManager(manager: CLLocationManager, didChangeAuthorizationStatus status: CLAuthorizationStatus) {
        switch status {
        case CLAuthorizationStatus.Restricted:
            //log("Restricted Access to location")
        case CLAuthorizationStatus.Denied:
            //log("User denied access to location")
        case CLAuthorizationStatus.NotDetermined:
            //log("Status not determined")
        default:
            //log("startUpdatintLocation")
            if #available(iOS 9, *){
                locationManager.requestLocation()
            } else {
                locationManager.startUpdatingLocation()
            }
        }
    }
    func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {

        if(timer==nil){
            // The locations array is sorted in chronologically ascending order, so the
            // last element is the most recent
            guard let location = locations.last else {return}

            beginNewBackgroundTask()
            locationManager.stopUpdatingLocation()
            let now = NSDate()
            if(isItTime(now)){
                //TODO: Every n minutes do whatever you want with the new location. Like for example sendLocationToServer(location, now:now)
            }
        }
    }

    func locationManager(manager: CLLocationManager, didFailWithError error: NSError) {
        CrashReporter.recordError(error)

        beginNewBackgroundTask()
        locationManager.stopUpdatingLocation()
    }

    func isItTime(now:NSDate) -> Bool {
        let timePast = now.timeIntervalSinceDate(lastLocationDate)
        let intervalExceeded = Int(timePast) > BackgroundLocationManager.UPDATE_SERVER_INTERVAL
        return intervalExceeded;
    }

    func sendLocationToServer(location:CLLocation, now:NSDate){
        //TODO
    }

    func beginNewBackgroundTask(){
        var previousTaskId = currentBgTaskId;
        currentBgTaskId = UIApplication.sharedApplication().beginBackgroundTaskWithExpirationHandler({
            FileLogger.log("task expired: ")
        })
        if let taskId = previousTaskId{
            UIApplication.sharedApplication().endBackgroundTask(taskId)
            previousTaskId = UIBackgroundTaskInvalid
        }

        timer = NSTimer.scheduledTimerWithTimeInterval(BackgroundLocationManager.BACKGROUND_TIMER, target: self, selector: #selector(self.restart),userInfo: nil, repeats: false)
    }
}

AppDelegate에서 다음과 같이 추적을 시작합니다.

BackgroundLocationManager.instance.start()


답변

불행히도, 당신의 모든 가정은 정확 해 보입니다. 나는 이것을 할 방법이 없다고 생각합니다. 배터리 수명을 절약하기 위해 iPhone의 위치 서비스는 움직임을 기반으로합니다. 휴대 전화가 한 곳에 있으면 위치 서비스가 보이지 않습니다.

CLLocationManager단지 호출 locationManager:didUpdateToLocation:fromLocation:전화가 세 개의 위치 서비스 중 하나 (기지국, GPS, 와이파이)의 변화를 감지하는 경우에만 발생 위치 업데이트를받을 때.

추가 솔루션을 알리는 데 도움이되는 몇 가지 다른 사항 :

  • 서비스를 시작 및 중지하면 didUpdateToLocation대리자 메서드가 호출되지만 newLocation이전 타임 스탬프가있을 수 있습니다.

  • 지역 모니터링이 도움이 될 수 있습니다

  • 백그라운드에서 실행하는 경우 Apple에서 승인 한 “전체”LocationServices 지원을 받기가 어려울 수 있습니다. 필자가 본 바에 startMonitoringSignificantLocationChanges따르면 백그라운드 위치 지원이 필요한 앱을위한 저전력 대안으로 특별히 설계되었으며 앱에서 절대적으로 필요하지 않는 한 개발자가이 기능을 사용하도록 권장합니다.

행운을 빕니다!

업데이트 :이 생각은 지금 구식 일 수 있습니다. 사람들이 위의 @ wjans 답변으로 성공한 것처럼 보입니다.