Làm cách nào để có được cập nhật vị trí nền mỗi n phút trong ứng dụng iOS của tôi?


206

Tôi đang tìm cách để cập nhật vị trí nền mỗi n phút trong ứng dụng iOS của mình. Tôi đang sử dụng iOS 4.3 và giải pháp sẽ hoạt động cho iPhone không jailbreak.

Tôi đã thử / xem xét các tùy chọn sau:

  • CLLocationManager startUpdatingLocation/startMonitoringSignificantLocationChanges: Điều này hoạt động ở chế độ nền như mong đợi, dựa trên các thuộc tính được định cấu hình, nhưng dường như không thể buộc nó cập nhật vị trí mỗi n phút
  • NSTimer: Không hoạt động khi ứng dụng đang chạy ở nền trước nhưng dường như không được thiết kế cho các tác vụ nền
  • Thông báo cục bộ: Thông báo cục bộ có thể được lên lịch mỗi n phút, nhưng không thể thực thi một số mã để có được vị trí hiện tại (mà không cần người dùng phải khởi chạy ứng dụng qua thông báo). Cách tiếp cận này dường như cũng không phải là một cách tiếp cận rõ ràng vì đây không phải là thông báo nên được sử dụng cho mục đích gì.
  • UIApplication:beginBackgroundTaskWithExpirationHandler: Theo tôi hiểu, điều này nên được sử dụng để hoàn thành một số công việc trong nền (cũng bị giới hạn về thời gian) khi một ứng dụng được chuyển sang nền thay vì thực hiện các quy trình nền "chạy dài".

Làm thế nào tôi có thể thực hiện các cập nhật vị trí nền thường xuyên?




1
Nếu bạn đang cố gắng làm cho nó hoạt động trên iOS 7, bạn có thể thử giải pháp này tại đây: stackoverflow.com/questions/18946881/. Nếu bạn có bất kỳ câu hỏi nào, bạn được chào đón tham gia với chúng tôi để thảo luận tại đây: mobileoop.com/background -location-update-lập trình-cho-ios-7
Ricky

1
Tất cả những phát hiện của bạn là chính xác (bốn điểm đạn). Thông tin có giá trị là sau đó, biết những gì không phù hợp với trường hợp bạn sử dụng? Và đúng vậy, khi ở chế độ TẠM NGỪNG hoặc KHÔNG CHẠY, không có phương pháp tối ưu nào để cập nhật mỗi n phút.
LenArt

Câu trả lời:


113

Tôi đã tìm thấy một giải pháp để thực hiện điều này với sự trợ giúp của Diễn đàn nhà phát triển Apple:

  • Chỉ định location background mode
  • Tạo một NSTimernền trong vớiUIApplication:beginBackgroundTaskWithExpirationHandler:
  • Khi nnhỏ hơn UIApplication:backgroundTimeRemainingnó sẽ làm việc tốt. Khi nlớn hơn , các location managernên được kích hoạt (và vô hiệu hóa) một lần nữa trước khi không có thời gian còn lại để tránh các tác vụ chạy nền bị giết.

Điều này hoạt động vì vị trí là một trong ba loại thực thi nền được phép .

Lưu ý: Tôi đã mất một thời gian bằng cách thử nghiệm điều này trong trình giả lập nơi nó không hoạt động. Tuy nhiên, nó hoạt động tốt trên điện thoại của tôi.


24
Bạn có tình cờ có liên kết đến diễn đàn. Tôi đang tìm cách triển khai cùng loại bản đồ vị trí và không thể làm cho nó hoạt động. Hoặc một số mã mẫu sẽ được đánh giá rất cao.
utahwithak

4
Bạn có thể giải thích lý do tại sao tác vụ nền không bị giết sau 10 phút (thời gian tối đa được phép) nếu bạn chỉ dừng lại và bắt đầu trình quản lý vị trí? nó là một số loại chức năng dự định? Nghe có vẻ giống như một lỗi trong SDK của Apple nếu nó xảy ra như thế. Phiên bản iOS nào bạn đang dùng thử?
saurabh

5
@all: Có, ứng dụng của chúng tôi có sẵn trong AppStore. Tôi sẽ không đăng tất cả các mã, tất cả các gạch đầu dòng được đề cập là các tính năng được ghi lại rõ ràng. Trong trường hợp bạn đang gặp một vấn đề cụ thể, hãy đăng câu hỏi của riêng bạn để giải thích những gì bạn đã cố gắng và những gì sai.
wjans

3
@ user836026: Vâng, đó là điều tôi muốn nói bằng cách chỉ định chế độ nền. Sau khi dừng cập nhật vị trí, nên khởi động lại trong vòng 10 phút để tránh ứng dụng bị chấm dứt.
wjans

12
Đối với tất cả những ai muốn xem một số mã thực tế phản ánh những gì đang được thảo luận ở đây, hãy kiểm tra stackoverflow.com/questions/10235203/iêu
Lolo

55

Trên iOS 8/9/10 để cập nhật vị trí nền sau mỗi 5 phút, hãy làm như sau:

  1. Chuyển đến Dự án -> Khả năng -> Chế độ nền -> chọn Cập nhật vị trí

  2. Chuyển đến Dự án -> Thông tin -> thêm khóa NSLocationAlwaysUsageDes mô tả với giá trị trống (hoặc tùy chọn bất kỳ văn bản nào)

  3. Để làm cho vị trí hoạt động khi ứng dụng của bạn ở chế độ nền và gửi tọa độ cho dịch vụ web hoặc làm bất cứ điều gì với chúng cứ sau 5 phút thực hiện nó như trong mã bên dưới.

Tôi không sử dụng bất kỳ tác vụ nền hoặc bộ hẹn giờ nào. Tôi đã kiểm tra mã này với thiết bị của mình với iOS 8.1 đang nằm trên bàn trong vài giờ với ứng dụng của tôi chạy ở chế độ nền. Thiết bị đã bị khóa và mã luôn chạy đúng.

@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

3
Điều này có làm việc trong hơn một vài giờ? Có vẻ như ngay cả khi ứng dụng không bị chấm dứt, thiết bị sẽ ngừng cập nhật vị trí sau một giờ hoặc lâu hơn ứng dụng sẽ chạy nền.
Myxtic 3/03/2015

2
Tải xuống nền bị vô hiệu hóa trong dự án của tôi (nó không quan trọng nếu nó tắt hoặc bật). Tuy nhiên, tạm dừngLocationUpdatesAutomatically phải được đặt thành NO cho ví dụ trên để hoạt động đúng. Nó sẽ KHÔNG tự động tiếp tục lại khi bạn bắt đầu di chuyển lại nếu hệ thống bị tạm dừng trước đó. Đó là lý do tại sao trong ví dụ trên tôi đặt thuộc tính này thành NO.
Leszek Szary 3/03/2015

1
Tôi nghĩ bạn hoàn toàn đúng. Tìm thấy một số thông tin về vấn đề ở đây: stackoverflow.com/q/17484352/1048331
Myxtic 3/03/2015

2
@LeszekS bạn cần thêm mã bên dưới để hỗ trợ iOS 9 cho nền - if ([self.locationManager respondsToSelector:@selector(setAllowsBackgroundLocationUpdates:)]) { [self.locationManager setAllowsBackgroundLocationUpdates:YES]; }
Fenil

2
Sự tiện lợi của việc này là gì? Bạn vẫn đang tiêu hao pin bằng cách có độ chính xác cao liên tục. Điều duy nhất bạn không làm là bạn không thực sự sử dụng vị trí đã nhận cho đến khi bạn đạt được một khoảng thời gian 5 phút ...
Honey

34

Tôi đã làm điều này trong một ứng dụng tôi đang phát triển. Bộ hẹn giờ không hoạt động khi ứng dụng ở chế độ nền nhưng ứng dụng liên tục nhận được các cập nhật vị trí. Tôi đã đọc ở đâu đó trong tài liệu (dường như tôi không thể tìm thấy nó bây giờ, tôi sẽ đăng một bản cập nhật khi tôi làm) rằng một phương thức chỉ có thể được gọi trên một vòng lặp hoạt động khi ứng dụng ở chế độ nền. Đại biểu ứng dụng có một vòng lặp hoạt động ngay cả trong bg, do đó bạn không cần phải tạo riêng của mình để thực hiện công việc này. [Tôi không chắc đây có phải là lời giải thích chính xác không nhưng đó là cách tôi hiểu từ những gì tôi đọc]

Trước hết, hãy thêm locationđối tượng cho khóa UIBackgroundModestrong thông tin ứng dụng của bạn. Bây giờ, những gì bạn cần làm là bắt đầu cập nhật vị trí ở bất cứ đâu trong ứng dụng của bạn:

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

Tiếp theo, viết một phương thức để xử lý các cập nhật vị trí, giả sử -(void)didUpdateToLocation:(CLLocation*)locationtrong đại biểu ứng dụng. Sau đó, thực hiện các phương pháp locationManager:didUpdateLocation:fromLocationcủa CLLocationManagerDelegatetrong lớp mà bạn bắt đầu quản lý vị trí (kể từ khi chúng tôi thiết lập các vị trí quản lý đại biểu để 'tự'). Bên trong phương pháp này, bạn cần kiểm tra xem khoảng thời gian mà sau đó bạn phải xử lý các cập nhật vị trí đã trôi qua chưa. Bạn có thể làm điều này bằng cách tiết kiệm thời gian hiện tại mỗi lần. Nếu thời gian đó trôi qua, hãy gọi phương thức UpdateLocation từ đại biểu ứng dụng của bạn:

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];
        }
    }
}

Điều này sẽ gọi phương thức của bạn cứ sau 5 phút ngay cả khi ứng dụng của bạn ở chế độ nền. Imp: Việc triển khai này làm cạn kiệt pin, nếu độ chính xác của dữ liệu vị trí của bạn không quan trọng, bạn nên sử dụng[locationManager startMonitoringSignificantLocationChanges]

Trước khi thêm ứng dụng này vào ứng dụng của bạn, vui lòng đọc Hướng dẫn lập trình nhận thức vị trí


3
Bằng cách đó, các dịch vụ định vị được kích hoạt liên tục (thực sự là hết pin), tôi không muốn điều đó. Tôi muốn kích hoạt dịch vụ định vị mỗi n phút và ngay lập tức vô hiệu hóa nó khi tôi có một bản sửa lỗi tốt (chỉ cần lưu ý rằng tôi đã không giải thích rõ ràng điều đó trong câu hỏi của mình). Tôi có thể đạt được hành vi này trong giải pháp tôi mô tả.
wjans

3
Bạn có thể đặt độ chính xác của trình quản lý vị trí thành 1km - điều này sẽ khiến pin của bạn gần như nguyên vẹn. Sau 5 phút bạn đặt độ chính xác thành 1m. Khi bạn nhận được vị trí thỏa mãn (thông thường sau 5s), chỉ cần đặt độ chính xác trở lại 1km.
knagode

knagode, tôi đã thử giải pháp đề xuất của bạn cho vấn đề hao pin, nhưng ngay cả sau khi tăng độ chính xác sau N phút, phương thức locationManager: didUpdateLocations không được gọi lại. Tôi đã thử startUpdating và stopUpdating, thay vì tăng và giảm độ chính xác, nó được gọi là phương thức đại biểu thành công locationManager: didUpdateLocations, sau N phút, nhưng không hoạt động trong MODE nền tảng ...
Hardik Darji

Đối với các liên kết của tài liệu. Xem ở đây : "Các phương thức của đối tượng ủy nhiệm của bạn được gọi từ luồng mà bạn đã khởi động các dịch vụ định vị tương ứng. Chủ đề đó phải có một vòng lặp hoạt động, giống như một luồng được tìm thấy trong luồng chính của ứng dụng của bạn."
Mật ong

24

Giờ đây, iOS6 là cách tốt nhất để có dịch vụ định vị chạy mãi mãi là ...

- (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]);

}

Chỉ cần thử nó như thế:

Tôi khởi động ứng dụng, đi nền và di chuyển trong xe vài phút. Sau đó tôi về nhà trong 1 giờ và bắt đầu di chuyển lại (không mở lại ứng dụng). Địa điểm bắt đầu lại. Sau đó dừng lại trong hai giờ và bắt đầu lại. Mọi thứ lại ổn thôi ...

KHÔNG QUÊN SỬ DỤNG các dịch vụ định vị mới trong 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
Nếu ứng dụng gặp sự cố hoặc bị tắt, hệ thống sẽ không khởi động lại, phải không?
Ken

1
Để có mã dễ đọc hơn, bạn có thể thực hiện [vị trí lastObject]; thay vì [object objectAt Index: [đếm vị trí] - 1]
axello

phương pháp của bạn chỉ có tại ios6?
pengwang

Chúng ta có phải sử dụng cái này không? Tôi nghĩ chỉ cần có mã trình quản lý vị trí trong viewDidLoad của một lớp và đã đặt khóa nền trong tệp plist mà ứng dụng đăng ký để cập nhật Vị trí có đủ tốt không? Bạn có thể vui lòng giúp tôi với điều này!
không có

Vâng, nó sẽ hoạt động giống như charm nithinreddy nhưng nó sẽ ngừng hoạt động sau 10 phút kể từ khi iOS giết chết các chuỗi dài sau khoảng thời gian đó. Giải pháp của tôi là hoàn hảo nếu bạn muốn các dịch vụ đó bắt đầu mãi mãi. Tôi đã thực hiện một số thử nghiệm hai ngày trước và nó tiêu hao 6% pin mỗi giờ. Thay vào đó, sử dụng [locationManager startUpdatingLocation] sẽ tiêu tốn 17%
Alejandro Luengo

13

Để người khác gặp ác mộng tìm ra điều này. Tôi có một giải pháp đơn giản.

  1. xem ví dụ này từ raywenderlich.com -> có mã mẫu, điều này hoạt động hoàn hảo, nhưng tiếc là không có bộ đếm thời gian trong vị trí nền. cái này sẽ chạy vô thời hạn
  2. Thêm bộ đếm thời gian bằng cách sử dụng:

    -(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. Chỉ cần đừng quên thêm "Đăng ký ứng dụng để cập nhật vị trí" trong info.plist.


Điều này có được vị trí trong hơn 3 phút?
Ngủ

Phải đặt vị trí trong Khả năng -> Chế độ nền.
Sergio Andreotti

Nó không hoạt động!! ? Tôi đã kiểm tra trên iOS 8 và iOS 10
Kirti

Sửa tôi nếu tôi sai. Dựa trên những gì tôi đọc, bạn chỉ bắt đầu locationManager một lần. Sau đó, tất cả các khoảng là dư thừa. Vì nó đã được bắt đầu
Mật ong

nó đang hoạt động nhưng nó đang dừng lại sau 170 giây. tôi muốn thực hiện nhiệm vụ của mình trong nền không giới hạn thời gian
anshuman burmman

8

Đây là những gì tôi sử dụng:

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)
    }
}

Tôi bắt đầu theo dõi trong AppDelegate như thế:

BackgroundLocationManager.instance.start()

Cảm ơn. Dự án của chúng tôi cần theo dõi vị trí người dùng + gửi các sự kiện PubNub trong nền và giải pháp của bạn đang hoạt động rất tốt.
dùng921509

Xin chào Hmitkov, nơi tôi có thể gọi phương thức sendLocationToServer để gửi vị trí người dùng trên máy chủ
AmanGupta007

@ AmanGupta007 Bạn có thể gọi sendLocationToServer trong func locationManager (người quản lý: didUpdateLocations :). Lưu ý // TODO bình luận trong mã.
hmitkov

@hmitkov Có thể bắt đầu và dừng Dịch vụ vị trí với điều này trong khi ứng dụng ở chế độ nền không? Ví dụ: bắt đầu dịch vụ vị trí từ thông báo đẩy lấy một số lat / long, gửi đến dịch vụ web, sau đó dừng cập nhật vị trí. Làm điều này mỗi khi 'nội dung có sẵn' = 1 được đưa vào phần thân đẩy.
DookieMan

1
Tôi đã thử mã này nhưng có vẻ như nó không hoạt động trên iOS11? (Tôi chưa thử nghiệm nó trên bất kỳ phiên bản nào khác.)
Tom

6

Thật không may, tất cả các giả định của bạn có vẻ đúng và tôi không nghĩ có cách nào để làm điều này. Để tiết kiệm pin, các dịch vụ định vị của iPhone dựa trên chuyển động. Nếu điện thoại nằm ở một vị trí, nó sẽ vô hình đối với các dịch vụ định vị.

Các CLLocationManagerchỉ gọi locationManager:didUpdateToLocation:fromLocation:khi điện thoại nhận cập nhật vị trí, trong đó chỉ xảy ra nếu một trong ba dịch vụ định vị (tháp di động, gps, wifi) nhận thức một sự thay đổi.

Một vài điều khác có thể giúp thông báo thêm các giải pháp:

  • Bắt đầu và dừng các dịch vụ khiến didUpdateToLocationphương thức ủy nhiệm được gọi, nhưng newLocationcó thể có dấu thời gian cũ.

  • Giám sát khu vực có thể giúp

  • Khi chạy trong nền, hãy lưu ý rằng có thể khó có được hỗ trợ "Dịch vụ vị trí" đầy đủ được Apple phê duyệt. Từ những gì tôi đã thấy, họ đã thiết kế đặc biệt startMonitoringSignificantLocationChangesnhư một sự thay thế năng lượng thấp cho các ứng dụng cần hỗ trợ vị trí nền và khuyến khích mạnh mẽ các nhà phát triển sử dụng điều này trừ khi ứng dụng thực sự cần nó.

Chúc may mắn!

CẬP NHẬT: Những suy nghĩ này có thể đã lỗi thời. Có vẻ như mọi người đang thành công với câu trả lời @wjans, ở trên.


1
Có những ứng dụng có sẵn trong AppStore (ví dụ như "Địa điểm của tôi") có thể giúp cập nhật vị trí trong nền. Họ không giữ dịch vụ vị trí hoạt động nhưng nó chỉ được kích hoạt trong thời gian ngắn theo khoảng thời gian được xác định. Làm thế nào để họ làm điều này?
wjans

2
Trong tình huống bạn mô tả, ứng dụng rất có thể sử dụng phương pháp startMonitoringSignificantLocationChanges. Tại đây, điện thoại sẽ tạm thời 'thức dậy' khi nhận được cập nhật vị trí, nhưng không có khoảng thời gian nào bạn có thể đặt thành 'ping' dịch vụ này trong nền. Khi điện thoại di chuyển (hoặc chuyển từ Di động sang GPS hoặc Wifi), nó sẽ kích hoạt cập nhật. Bài giảng về iTunes iTunes U về chủ đề này thực sự hữu ích với tôi - hy vọng nó có thể giúp bạn tìm ra cách giải quyết: itunes.apple.com/us/itunes-u/iphone-application-development/
trộm

2
Thx cho con trỏ. Tuy nhiên, tôi vẫn không hiểu ứng dụng đang làm gì. Ngay cả khi điện thoại của tôi ở bàn làm việc, không di chuyển chút nào, tôi có thể thấy dịch vụ định vị được kích hoạt cứ sau 10 phút (chính xác). Nếu tôi hiểu chính xác, startMonitoringSignificantLocationChangessẽ không cung cấp bất kỳ cập nhật nào trong trường hợp đó.
wjans

1
@wjans mức tiêu thụ pin điện thoại của bạn như thế nào, bạn có nhận thấy nó bị cạn kiệt có thể do ứng dụng Myl Focus không?
dùng836026

6

Tôi đã viết một ứng dụng bằng dịch vụ Location, ứng dụng phải gửi vị trí cứ sau 10 giây. Và nó đã làm việc rất tốt.

Chỉ cần sử dụng phương thức " allowDeferredLocationUpdatesUntilTraveled: timeout ", theo tài liệu của Apple.

Những gì tôi đã làm là:

Yêu cầu: Đăng ký chế độ nền để cập nhật Vị trí.

1. Tạo LocationMangerstartUpdatingLocation, với accuracyfilteredDistancenhư bất cứ điều gì bạn muốn:

-(void) initLocationManager    
{
    // Create the manager object
    self.locationManager = [[[CLLocationManager alloc] init] autorelease];
    _locationManager.delegate = self;
    // This is the most important property to set for the manager. It ultimately determines how the manager will
    // attempt to acquire location and thus, the amount of power that will be consumed.
    _locationManager.desiredAccuracy = 45;
    _locationManager.distanceFilter = 100;
    // Once configured, the location manager must be "started".
    [_locationManager startUpdatingLocation];
}

2. Để giữ cho ứng dụng chạy mãi mãi bằng allowDeferredLocationUpdatesUntilTraveled:timeoutphương thức ở chế độ nền, bạn phải khởi động lại updatingLocationvới tham số mới khi ứng dụng chuyển sang nền, như thế này:

- (void)applicationWillResignActive:(UIApplication *)application {
     _isBackgroundMode = YES;

    [_locationManager stopUpdatingLocation];
    [_locationManager setDesiredAccuracy:kCLLocationAccuracyBest];
    [_locationManager setDistanceFilter:kCLDistanceFilterNone];
    _locationManager.pausesLocationUpdatesAutomatically = NO;
    _locationManager.activityType = CLActivityTypeAutomotiveNavigation;
    [_locationManager startUpdatingLocation];
 }

3. Ứng dụng được cập nhậtLocations như bình thường với locationManager:didUpdateLocations:gọi lại:

-(void) locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
{
//  store data
    CLLocation *newLocation = [locations lastObject];
    self.userLocation = newLocation;

   //tell the centralManager that you want to deferred this updatedLocation
    if (_isBackgroundMode && !_deferringUpdates)
    {
        _deferringUpdates = YES;
        [self.locationManager allowDeferredLocationUpdatesUntilTraveled:CLLocationDistanceMax timeout:10];
    }
}

4. Nhưng bạn nên xử lý dữ liệu sau đó locationManager:didFinishDeferredUpdatesWithError:gọi lại cho mục đích của bạn

- (void) locationManager:(CLLocationManager *)manager didFinishDeferredUpdatesWithError:(NSError *)error {

     _deferringUpdates = NO;

     //do something 
}

5. LƯU Ý: Tôi nghĩ rằng chúng ta nên đặt lại các tham số của LocationManagermỗi lần chuyển đổi ứng dụng giữa chế độ nền / nền tảng.


@ Tất cả giải pháp này hoặc giải pháp nào được giải thích trên wjans tôi nên tiết kiệm pin thiết bị hoặc cả hai sẽ ảnh hưởng như nhau?
Yash

2
Tôi đã thử cả hai giải pháp mà bạn đề cập và thấy rằng giải pháp của @ wjans tiết kiệm pin hơn một chút. Nhưng, sau sự xuất hiện của iOS 8, dường như giải pháp đó không còn hoạt động chính xác nữa. Để biết thêm chi tiết: hầu hết thời gian, ứng dụng không thể tồn tại lâu ở chế độ nền.
samthui7

@ samthui7 Tại sao bạn đặt tạm dừngLocationUpdatesAutomatically = false?
CedricSoubrie

Yêu cầu của ứng dụng của tôi là tiếp tục gửi người dùngLocation sau mỗi 10 giây, đồng thời pausesLocationUpdatesAutomatically = trueyêu cầu người quản lý vị trí tạm dừng cập nhật nếu dường như không có thay đổi vị trí ( tài liệu của Apple ). Dù sao, tôi đã không kiểm tra rõ ràng trường hợp location manager pauses updatesnào: D.
samthui7

@CedricSoubrie: Cài đặt pausesLocationUpdatesAutomatically = truekhiến ứng dụng của tôi ngừng cập nhật vị trí.
samthui7

5
if ([self.locationManager respondsToSelector:@selector(setAllowsBackgroundLocationUpdates:)]) {
    [self.locationManager setAllowsBackgroundLocationUpdates:YES];
}

Điều này là cần thiết để theo dõi vị trí nền kể từ iOS 9.


1
Điều này đã cứu ngày của tôi! sử dụng mục tiêu triển khai iOS8, với thiết bị iOS9
Yaro

4

Tôi đã sử dụng phương pháp của xs2bush để có một khoảng (sử dụng timeIntervalSinceDate) và mở rộng trên đó một chút. Tôi muốn đảm bảo rằng tôi đã nhận được độ chính xác cần thiết mà tôi cần và tôi cũng không hết pin bằng cách giữ cho radio gps nhiều hơn mức cần thiết.

Tôi giữ vị trí chạy liên tục với các cài đặt sau:

locationManager.desiredAccuracy = kCLLocationAccuracyThreeKilometers;
locationManager.distanceFilter = 5;

Đây là một sự tiêu hao tương đối thấp trên pin. Khi tôi đã sẵn sàng để đọc vị trí định kỳ tiếp theo của mình, trước tiên tôi sẽ kiểm tra xem vị trí đó có nằm trong độ chính xác mong muốn của tôi không, nếu có, tôi sẽ sử dụng vị trí đó. Nếu không, thì tôi tăng độ chính xác với điều này:

locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters;
locationManager.distanceFilter = 0;

lấy vị trí của tôi và sau đó khi tôi có vị trí, tôi quay lại độ chính xác một lần nữa để giảm thiểu hao pin. Tôi đã viết một mẫu làm việc đầy đủ về điều này và tôi cũng đã viết nguồn cho mã phía máy chủ để thu thập dữ liệu vị trí, lưu trữ vào cơ sở dữ liệu và cho phép người dùng xem dữ liệu gps trong thời gian thực hoặc truy xuất và xem các tuyến được lưu trữ trước đó. Tôi có khách hàng cho iOS, android, windows phone và java me. Tất cả các khách hàng được viết nguyên bản và tất cả đều hoạt động đúng trong nền. Dự án được MIT cấp phép.

Dự án iOS được nhắm mục tiêu cho iOS 6 bằng cách sử dụng SDK cơ bản của iOS 7. Bạn có thể lấy mã ở đây .

Vui lòng gửi một vấn đề trên github nếu bạn thấy bất kỳ vấn đề nào với nó. Cảm ơn.


Tôi đã thử giải pháp của bạn, nhưng không hoạt động ... khi ứng dụng chạy nền, ngay cả sau khi tăng độ chính xác, ứng dụng không được gọi là phương thức didUpdateToLocation trong trường hợp của tôi
Hardik Darji

@HardikDarji câu hỏi quan trọng. Bạn đang di chuyển à Nếu không, cập nhật vị trí có thể dừng lại. Hãy thử mang điện thoại ra ngoài đi dạo hoặc lái xe và xem điều đó có khắc phục được không.
nickfox

Cảm ơn cho phản ứng nhanh chóng của bạn. Nhưng tôi muốn cập nhật vị trí trong mỗi 2 phút, mà không cần quan tâm điện thoại của tôi có di chuyển hay không. Trong kịch bản này, phương thức didUpdateToLocation không được gọi. Tôi đang tìm kiếm ở đây: Làm thế nào để tôi cập nhật vị trí mỗi n phút !!!
Hardik Darji

hãy thử đặt timeIntervalInSeconds thành 120 và bỏ dòng này: locationManager.pausesLocationUpdatesAutomatically = NO;
nickfox

1
giải pháp bạn đã đăng trong nhận xét trên có làm giảm tuổi thọ pin hay bạn vẫn đang thực hiện một số tối ưu hóa cho việc đó?
samfr

2

Có vẻ như stopUpdatingLocation là thứ kích hoạt bộ đếm thời gian theo dõi nền, vì vậy tôi đã thay thế nó trong didUpdateLocation bằng:

     [self.locationManager setDesiredAccuracy:kCLLocationAccuracyThreeKilometers];
     [self.locationManager setDistanceFilter:99999];

có vẻ như tắt nguồn GPS một cách hiệu quả. Bộ chọn cho NSTimer nền sau đó trở thành:

- (void) changeAccuracy {
[self.locationManager setDesiredAccuracy:kCLLocationAccuracyBest];
[self.locationManager setDistanceFilter:kCLDistanceFilterNone];
}

Tất cả những gì tôi đang làm là định kỳ thay đổi độ chính xác để có tọa độ chính xác cao cứ sau vài phút và vì trình quản lý địa điểm chưa bị dừng, backgroundTimeRemained giữ nguyên giá trị tối đa. Điều này giúp giảm mức tiêu thụ pin từ ~ 10% mỗi giờ (với kCLLocationAccuracyBest không đổi trong nền) xuống ~ 2% mỗi giờ trên thiết bị của tôi


2

Có một cocoapod APSchediatedLocationManager cho phép nhận cập nhật vị trí nền mỗi n giây với độ chính xác vị trí mong muốn.

let manager = APScheduledLocationManager(delegate: self)
manager.startUpdatingLocation(interval: 170, acceptableLocationAccuracy: 100)

Kho lưu trữ cũng chứa một ứng dụng ví dụ được viết bằng Swift 3.


Bạn có bất cứ điều gì như thế này trong mục tiêu c?
Moxarth

1

Trong iOS 9 và watchOS 2.0, có một phương thức mới trên CLLocationManager cho phép bạn yêu cầu vị trí hiện tại: CLLocationManager: requestLocation (). Điều này hoàn thành ngay lập tức và sau đó trả lại vị trí cho đại biểu CLLocationManager.

Bạn có thể sử dụng NSTimer để yêu cầu vị trí mỗi phút với phương thức này ngay bây giờ và không phải làm việc với các phương thức startUpdatingLocation và stopUpdatingLocation.

Tuy nhiên, nếu bạn muốn chụp các vị trí dựa trên thay đổi X mét từ vị trí cuối cùng, chỉ cần đặt thuộc tính distanceFilter của CLLocationManger và gọi X gọi startUpdatingLocation ().


-1

Kèm theo là một giải pháp Swift dựa trên:

Xác định App registers for location updatestrong thông tin.plist

Giữ vị trí Trình quản lý chạy mọi lúc

Chuyển đổi kCLLocationAccuracygiữa BestForNavigation(trong 5 giây để lấy vị trí) và ThreeKilometerstrong phần còn lại của thời gian chờ để tránh hao pin

Ví dụ này cập nhật vị trí cứ sau 1 phút ở Tiền cảnh và cứ sau 15 phút trong Nền.

Ví dụ này hoạt động tốt với Xcode 6 Beta 6, chạy trên thiết bị iOS 7.

Trong Đại biểu ứng dụng (mapView là Tùy chọn trỏ đến Bộ điều khiển mapView)

func applicationDidBecomeActive(application: UIApplication!) {
    if appLaunched! == false { // Reference to mapView used to limit one location update per timer cycle
        appLaunched = true
        var appDelegate = UIApplication.sharedApplication().delegate as AppDelegate
        var window = appDelegate.window
        var tabBar = window?.rootViewController as UITabBarController
        var navCon = tabBar.viewControllers[0] as UINavigationController
        mapView = navCon.topViewController as? MapViewController
    }
    self.startInitialPeriodWithTimeInterval(60.0)
}

func applicationDidEnterBackground(application: UIApplication!) {
    self.startInitialPeriodWithTimeInterval(15 * 60.0)
}

func startInitialPeriodWithTimeInterval(timeInterval: NSTimeInterval) {
    timer?.invalidate() // reset timer
    locationManager?.desiredAccuracy = kCLLocationAccuracyBestForNavigation
    timer = NSTimer.scheduledTimerWithTimeInterval(5.0, target: self, selector: Selector("getFirstLocationUpdate:"), userInfo: timeInterval, repeats: false)
}

func getFirstLocationUpdate(sender: NSTimer) {
    let timeInterval = sender.userInfo as Double
    timer?.invalidate()
    mapView?.canReportLocation = true
    timer = NSTimer.scheduledTimerWithTimeInterval(timeInterval, target: self, selector: Selector("waitForTimer:"), userInfo: timeInterval, repeats: true)
}

func waitForTimer(sender: NSTimer) {
    let time = sender.userInfo as Double
    locationManager?.desiredAccuracy = kCLLocationAccuracyBestForNavigation
    finalTimer = NSTimer.scheduledTimerWithTimeInterval(5.0, target: self, selector: Selector("getLocationUpdate"), userInfo: nil, repeats: false)
}

func getLocationUpdate() {
    finalTimer?.invalidate()
    mapView?.canReportLocation = true
}

Trong mapView (locationManager trỏ đến đối tượng trong AppDelegate)

override func viewDidLoad() {
    super.viewDidLoad()
    var appDelegate = UIApplication.sharedApplication().delegate! as AppDelegate
    locationManager = appDelegate.locationManager!
    locationManager.delegate = self
    canReportLocation = true
}

  func locationManager(manager: CLLocationManager!, didUpdateLocations locations: [AnyObject]!) {
        if canReportLocation! {
            canReportLocation = false
            locationManager.desiredAccuracy = kCLLocationAccuracyThreeKilometers
        } else {
            //println("Ignore location update")
        }
    }
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.