การอัปเดตตำแหน่งพื้นหลัง iOS เป็นระยะ


92

ผมเขียนโปรแกรมที่ต้องมีการปรับปรุงสถานที่ตั้งของพื้นหลังที่มีความแม่นยำสูงและความถี่ต่ำ โซลูชันนี้ดูเหมือนจะเป็นงาน NSTimer พื้นหลังที่เริ่มการอัปเดตของผู้จัดการสถานที่ซึ่งจะปิดลงทันที คำถามนี้ถูกถามมาก่อน:

ฉันจะอัปเดตตำแหน่งพื้นหลังทุกๆ n นาทีในแอปพลิเคชัน iOS ของฉันได้อย่างไร

รับตำแหน่งของผู้ใช้ทุก ๆ n นาทีหลังจากแอปไปที่พื้นหลัง

iOS ไม่ใช่ปัญหาตัวจับเวลาการติดตามตำแหน่งพื้นหลังทั่วไป

ตัวจับเวลาพื้นหลังที่ใช้งานได้ยาวนานของ iOS พร้อมโหมดพื้นหลัง "ตำแหน่ง"

บริการพื้นหลังเต็มเวลา iOS ตามการติดตามตำแหน่ง

แต่ฉันยังไม่ได้รับตัวอย่างขั้นต่ำที่ใช้งานได้ หลังจากลองเปลี่ยนทุกคำตอบที่ยอมรับข้างต้นแล้วฉันได้รวบรวมจุดเริ่มต้น เข้าสู่พื้นหลัง:

- (void)applicationDidEnterBackground:(UIApplication *)application
{
    self.bgTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
        NSLog(@"ending background task");
        [[UIApplication sharedApplication] endBackgroundTask:self.bgTask];
        self.bgTask = UIBackgroundTaskInvalid;
    }];


    self.timer = [NSTimer scheduledTimerWithTimeInterval:60
                                                  target:self.locationManager
                                                selector:@selector(startUpdatingLocation)
                                                userInfo:nil
                                                 repeats:YES];
}

และวิธีการมอบหมาย:

- (void)locationManager:(CLLocationManager *)manager 
    didUpdateToLocation:(CLLocation *)newLocation 
           fromLocation:(CLLocation *)oldLocation {

    NSLog(@"%@", newLocation);

    NSLog(@"background time: %f", [UIApplication sharedApplication].backgroundTimeRemaining);
    [self.locationManager stopUpdatingLocation];

}

พฤติกรรมปัจจุบันคือการbackgroundTimeRemainingลดลงจาก 180 วินาทีเป็นศูนย์ (ในขณะที่บันทึกตำแหน่ง) จากนั้นตัวจัดการการหมดอายุจะดำเนินการและไม่มีการสร้างการอัปเดตตำแหน่งเพิ่มเติม ฉันจะแก้ไขโค้ดด้านบนเพื่อรับการอัปเดตตำแหน่งเป็นระยะในพื้นหลังโดยไม่มีกำหนดได้อย่างไร

อัปเดต : ฉันกำหนดเป้าหมายเป็น iOS 7 และดูเหมือนจะมีหลักฐานบางอย่างที่แสดงว่างานเบื้องหลังทำงานแตกต่างกัน:

เริ่มตัวจัดการตำแหน่งใน iOS 7 จากงานพื้นหลัง


คุณไม่ได้ระบุว่าคุณกำหนดเป้าหมายเป็น iOS เวอร์ชันใด iOS 7 (เช่น) มีระบบมัลติทาสก์ที่แตกต่างจากรุ่นก่อน
Jason Whitehorn

@JasonWhitehorn จุดดี. ฉันได้อัปเดตคำถามแล้ว
pcoving

2
@pcoving: โซลูชันของคุณจะใช้งานได้หรือไม่แม้ว่าแอปจะถูกฆ่าหรือยุติก็ตาม
Nirav

คำตอบ:


62

ดูเหมือนว่านั่นstopUpdatingLocationคือสิ่งที่ทำให้เกิดตัวจับเวลาการเฝ้าดูพื้นหลังดังนั้นฉันจึงแทนที่didUpdateLocationด้วย:

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

ซึ่งดูเหมือนจะปิด GPS ได้อย่างมีประสิทธิภาพ ตัวเลือกสำหรับพื้นหลังNSTimerจะกลายเป็น:

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

สิ่งที่ฉันทำคือการสลับความแม่นยำเป็นระยะเพื่อให้ได้พิกัดที่มีความแม่นยำสูงทุก ๆ สองสามนาทีและเนื่องจากlocationManagerยังไม่ได้หยุดทำงานจึงbackgroundTimeRemainingอยู่ที่ค่าสูงสุด สิ่งนี้ช่วยลดการใช้แบตเตอรี่จาก ~ 10% ต่อชั่วโมง (โดยมีค่าคงที่kCLLocationAccuracyBestในพื้นหลัง) เป็น ~ 2% ต่อชั่วโมงบนอุปกรณ์ของฉัน


4
คุณช่วยอัปเดตคำถามของคุณด้วยตัวอย่างการทำงานฉบับเต็มได้ไหม สิ่งที่ชอบ: "UPDATE: ฉันคิดออกแล้วนี่คือทางออกของฉัน ... "
smholloway

@Virussmca ฉันเปลี่ยนกลับไปใช้คำตอบก่อนหน้านี้ที่มีประสิทธิภาพมากขึ้นและมีประสิทธิภาพใกล้เคียงกัน การหยุดการอัปเดตตำแหน่งทั้งหมดดูเหมือนจะทำให้เกิดสภาวะการแข่งขัน (หรือสถานการณ์อื่น ๆ ที่ไม่ได้กำหนด)
pcoving

วิธีนี้จะไม่ได้ผลตามที่คุณคาดหวัง: 1) การเพิ่มค่าคุณสมบัติ distanceFilter ไม่ได้ส่งผลให้ประหยัดแบตเตอรี่เนื่องจาก gps ยังคงต้องค้นหาตำแหน่งของคุณ 2) ใน iOS 7 เวลาพื้นหลังไม่ติดกัน 3) คุณอาจคำนึงถึงบริการที่มีนัยสำคัญของ ChangeLocation 4) นอกเหนือจาก soution ของคุณใน iOS 7 คุณไม่สามารถเริ่ม UIBackgroundModes - ตำแหน่งจากพื้นหลัง ...
aMother

2
@aMother 1) ฉันตระหนักดีว่าการเพิ่มตัวกรองระยะทาง (และไม่ประมวลผลการโทรกลับ) ไม่ได้ช่วยประหยัดแบตเตอรี่มากนัก อย่างไรก็ตาม setDesiredAccuracy ทำ! มันตกอยู่กับข้อมูลหอเซลล์ซึ่งโดยทั่วไปแล้วให้ฟรี 2) ไม่ติดกัน? 3) ฉันได้ระบุเป็นตัวหนาว่าฉันต้องการความแม่นยำสูง การเปลี่ยนแปลงตำแหน่งที่สำคัญไม่ได้ให้สิ่งนี้ 4) บริการระบุตำแหน่งไม่ได้ถูกเริ่ม / หยุดจากพื้นหลัง
pcoving

2
ฉันรู้ว่านี่เป็นโพสต์เก่าแล้ว แต่ผู้ใช้แอปพลิเคชันของฉันที่ใช้เคล็ดลับนี้สังเกตเห็นการใช้งานแบตเตอรี่เพิ่มขึ้นอย่างมากนับตั้งแต่อัปเกรดเป็น iOS9 ฉันยังไม่ได้ตรวจสอบ แต่ฉันรู้สึกว่า 'เคล็ดลับ' นี้อาจใช้ไม่ได้อีกต่อไป?
Gary Wright

45

ฉันเขียนแอพโดยใช้บริการตำแหน่งแอพต้องส่งตำแหน่งทุกๆ 10 วินาที และได้ผลดีมาก

เพียงใช้เมธอด " allowDeferredLocationUpdatesUntilTraveled: timeout " ตามเอกสารของ Apple

ขั้นตอนมีดังนี้:

จำเป็น:ลงทะเบียนโหมดพื้นหลังสำหรับการอัปเดตตำแหน่ง

1.สร้าง LocationManger และ startUpdatingLocation ด้วยความแม่นยำและ filteredDistance ตามที่คุณต้องการ:

-(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.เพื่อให้แอปทำงานตลอดไปโดยใช้เมธอด "allowDeferredLocationUpdatesUntilTraveled: timeout" ในพื้นหลังคุณต้องรีสตาร์ท updatesLocation ด้วยพารามิเตอร์ใหม่เมื่อแอปย้ายไปที่พื้นหลังดังนี้:

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

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

3.แอปได้รับ updatedLocations ตามปกติด้วย "locationManager: didUpdateLocations:" callback:

-(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.แต่คุณควรจัดการกับข้อมูลในตอนนั้น "locationManager: didFinishDeferredUpdatesWithError:" โทรกลับตามวัตถุประสงค์ของคุณ

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

     _deferringUpdates = NO;

     //do something 
}

5. หมายเหตุ:ฉันคิดว่าเราควรรีเซ็ตพารามิเตอร์ของ LocationManager ทุกครั้งที่แอปสลับไปมาระหว่างโหมดพื้นหลัง / พื้นหลัง


มันใช้งานได้ดีและฉันรู้สึกว่านี่เป็นวิธีที่ถูกต้องตามที่กล่าวไว้ใน WWDC เมื่อปีที่แล้ว !! ขอบคุณสำหรับโพสต์
prasad1250

@ samthui7 ขอบคุณงานนี้ แต่ฉันต้องได้รับตำแหน่งหลังจากการยกเลิกด้วย เป็นไปได้ยังไง. ??
Mohit Tomar

@ samthui7 .. โปรดดูคิวนี้ ... stackoverflow.com/questions/37338466/…
Suraj Sukale

2
@amar: ฉันตรวจสอบ iOS 10 เบต้าแล้วยังใช้งานได้ โปรดทราบว่าเราต้องเพิ่มรหัสบางอย่างก่อนที่ startUpdatingLocation: allowsBackgroundLocationUpdates = YESและหรือrequestAlwaysAuthorization requestWhenInUseAuthorizationเช่น: `CLLocationManager * locationManager = [[จัดสรร CLLocationManager] init]; locationManager.delegate = ตนเอง; locationManager.allowsBackgroundLocationUpdates = ใช่; [locationManager requestAlwaysAuthorization]; [locationManager startUpdatingLocation]; `
samthui7

1
@Nirav ไม่ฉันไม่คิดว่า Apple จะปล่อยให้เราทำอย่างนั้นเท่าที่ฉันรู้
samthui7

38
  1. หากคุณมีUIBackgroundModesใน plist พร้อมlocationคีย์คุณไม่จำเป็นต้องใช้beginBackgroundTaskWithExpirationHandlerวิธีการ ที่ซ้ำซ้อน นอกจากนี้คุณใช้งานไม่ถูกต้อง (ดูที่นี่ ) แต่นั่นเป็นเรื่องที่น่าสงสัยเนื่องจากตั้งค่า plist ของคุณแล้ว

  2. ด้วยUIBackgroundModes locationในแอพพลิเคชั่นแอพจะยังคงทำงานในพื้นหลังไปเรื่อย ๆ ตราบเท่าที่CLLocationMangerยังทำงานอยู่ หากคุณโทรstopUpdatingLocationขณะอยู่เบื้องหลังแอปจะหยุดทำงานและจะไม่เริ่มอีกครั้ง

    บางทีคุณอาจโทรbeginBackgroundTaskWithExpirationHandlerก่อนโทรstopUpdatingLocationและหลังจากโทรแล้วstartUpdatingLocationคุณสามารถโทรendBackgroundTaskไปที่เพื่อให้เป็นพื้นหลังในขณะที่ GPS หยุดทำงาน แต่ฉันไม่เคยลอง - มันเป็นเพียงความคิด

    อีกทางเลือกหนึ่ง (ที่ฉันยังไม่ได้ลอง) คือให้ตัวจัดการสถานที่ทำงานอยู่ในพื้นหลัง แต่เมื่อคุณได้รับตำแหน่งที่ถูกต้องให้เปลี่ยนdesiredAccuracyคุณสมบัติเป็น 1,000 เมตรหรือสูงกว่าเพื่อให้ชิป GPS สามารถปิดได้ (เพื่อประหยัดแบตเตอรี่) จากนั้น 10 นาทีต่อมาเมื่อคุณต้องการการอัปเดตตำแหน่งอื่นให้เปลี่ยนdesiredAccuracyด้านหลังเป็น 100 เมตรเพื่อเปิด GPS จนกว่าคุณจะได้ตำแหน่งที่ถูกต้องแล้วทำซ้ำ

  3. เมื่อคุณโทรหาstartUpdatingLocationผู้จัดการสถานที่คุณต้องให้เวลาเพื่อรับตำแหน่ง stopUpdatingLocationคุณไม่ควรทันทีโทร เราปล่อยให้มันทำงานเป็นเวลาสูงสุด 10 วินาทีหรือจนกว่าเราจะได้ตำแหน่งที่มีความแม่นยำสูงแบบไม่แคช

  4. คุณต้องกรองตำแหน่งที่แคชไว้และตรวจสอบความถูกต้องของตำแหน่งที่คุณได้รับเพื่อให้แน่ใจว่าตรงตามความแม่นยำขั้นต่ำที่คุณต้องการ (ดูที่นี่ ) การอัปเดตครั้งแรกที่คุณได้รับอาจมีอายุ 10 นาทีหรือ 10 วัน ความแม่นยำแรกที่คุณได้รับอาจเป็น 3000m

  5. ลองใช้ API การเปลี่ยนแปลงตำแหน่งที่สำคัญแทน เมื่อคุณได้รับการแจ้งเตือนการเปลี่ยนแปลงที่สำคัญคุณสามารถเริ่มต้นCLLocationManagerสักครู่เพื่อให้ได้ตำแหน่งที่มีความแม่นยำสูง ฉันไม่แน่ใจฉันไม่เคยใช้บริการเปลี่ยนสถานที่สำคัญ


CoreLocationดูเหมือนว่าข้อมูลอ้างอิงของApple จะแนะนำให้ใช้beginBackgroundTaskWithExpirationHandlerเมื่อประมวลผลข้อมูลตำแหน่งในพื้นหลัง: "หากแอป iOS ต้องการเวลาในการประมวลผลข้อมูลตำแหน่งมากขึ้นก็สามารถขอเวลาดำเนินการพื้นหลังเพิ่มเติมได้โดยใช้วิธี startBackgroundTaskWithName: expirationHandler: ของคลาส UIApplication" - developer.apple.com/library/ios/documentation/UserExperience/…
eliocs

4
อย่าลืมเพิ่ม _locationManager.allowsBackgroundLocationUpdates = YES; สำหรับ iOS9 + มิฉะนั้นแอปจะเข้าสู่โหมดสลีปหลังจาก 180 วินาที
kolyancz

@kolyancz ฉันเพิ่งลองใช้บน iOS 10.1.1 สำหรับ iPhone 6+ ใช้เวลาประมาณ 17 นาทีในการเข้าสู่โหมดสลีปและเริ่มlocationManagerDidPauseLocationUpdatesทำงาน ฉันตรวจสอบพฤติกรรมนั้น 10 ครั้ง!
น้ำผึ้ง

ผมไม่ได้รับเหตุผลที่คุณกำลังจะบอกว่ามันเป็นที่ถกเถียงและจากนั้นคุณกำลังจะบอกว่าคุณควรจะห่อภายใน backgroundTask ...
น้ำผึ้ง

10

เมื่อถึงเวลาเริ่มบริการตำแหน่งและหยุดงานพื้นหลังงานพื้นหลังควรหยุดด้วยความล่าช้า (ควรจะเพียงพอ 1 วินาที) มิฉะนั้นบริการตำแหน่งจะไม่เริ่มต้น นอกจากนี้ควรเปิดบริการตำแหน่งไว้สองสามวินาที (เช่น 3 วินาที)

มี cocoapod APScheduledLocationManagerที่อนุญาตให้รับการอัปเดตตำแหน่งพื้นหลังทุกnวินาทีพร้อมความแม่นยำของตำแหน่งที่ต้องการ

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

ที่เก็บยังมีแอปตัวอย่างที่เขียนด้วย Swift 3


1

ลองใช้startMonitoringSignificantLocationChanges:API ดูสิ แน่นอนว่ามันยิงด้วยความถี่ที่น้อยกว่าและความแม่นยำก็ดีพอสมควร นอกจากนี้ยังมีข้อดีมากกว่าการใช้locationManagerAPI อื่น ๆ

มีการพูดคุยเพิ่มเติมเกี่ยวกับ API นี้ในลิงค์นี้แล้ว


1
ฉันได้ลองวิธีนี้แล้ว มันละเมิดข้อกำหนดความถูกต้อง - จากเอกสาร : This interface delivers new events only when it detects changes to the device’s associated cell towers
pcoving

1

คุณต้องเพิ่มโหมดการอัปเดตตำแหน่งในแอปพลิเคชันของคุณโดยเพิ่มคีย์ต่อไปนี้ใน info.plist ของคุณ

<key>UIBackgroundModes</key>
<array>
    <string>location</string>
</array>

didUpdateToLocationจะเรียกวิธีการ (แม้ว่าแอปของคุณจะอยู่ในพื้นหลังก็ตาม) คุณสามารถดำเนินการใด ๆ บนฐานของการเรียกวิธีนี้


โหมดการอัปเดตตำแหน่งอยู่ใน info.plist แล้ว ดังที่ได้กล่าวไปแล้วฉันกำลังได้รับการอัปเดตจนกว่าจะหมดเวลาเบื้องหลัง 180 วินาที
pcoving

0

หลังจาก iOS 8 มีการเปลี่ยนแปลงหลายอย่างใน CoreLocation framework ที่เกี่ยวข้องกับการดึงข้อมูลพื้นหลังและการอัปเดตที่ทำโดย apple

1) Apple แนะนำคำขอ AlwaysAuthorization เพื่อดึงข้อมูลการอัปเดตตำแหน่งในแอปพลิเคชัน

2) Apple แนะนำ backgroundLocationupdates สำหรับดึงตำแหน่งจากพื้นหลังใน iOS

สำหรับการดึงตำแหน่งในพื้นหลังคุณต้องเปิดใช้งานการอัปเดตตำแหน่งในความสามารถของ Xcode

//
//  ViewController.swift
//  DemoStackLocation
//
//  Created by iOS Test User on 02/01/18.
//  Copyright © 2018 iOS Test User. All rights reserved.
//

import UIKit
import CoreLocation

class ViewController: UIViewController {

    var locationManager: CLLocationManager = CLLocationManager()
    override func viewDidLoad() {
        super.viewDidLoad()
        startLocationManager()
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    internal func startLocationManager(){
        locationManager.requestAlwaysAuthorization()
        locationManager.desiredAccuracy = kCLLocationAccuracyBest
        locationManager.delegate = self
        locationManager.allowsBackgroundLocationUpdates = true
        locationManager.startUpdatingLocation()
    }

}

extension ViewController : CLLocationManagerDelegate {

    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]){
        let location = locations[0] as CLLocation
        print(location.coordinate.latitude) // prints user's latitude
        print(location.coordinate.longitude) //will print user's longitude
        print(location.speed) //will print user's speed
    }

}

0

ในการใช้บริการตำแหน่งมาตรฐานในขณะที่แอปพลิเคชันทำงานอยู่เบื้องหลังคุณต้องเปิดโหมดพื้นหลังก่อนในแท็บความสามารถของการตั้งค่าเป้าหมายแล้วเลือกการอัปเดตตำแหน่ง

หรือเพิ่มลงในInfo.plistโดยตรง

<key>NSLocationAlwaysUsageDescription</key>
 <string>I want to get your location Information in background</string>
<key>UIBackgroundModes</key>
 <array><string>location</string> </array>

จากนั้นคุณต้องตั้งค่า CLLocationManager

วัตถุประสงค์ค

//The Location Manager must have a strong reference to it.
_locationManager = [[CLLocationManager alloc] init];
_locationManager.delegate = self;
//Request Always authorization (iOS8+)
if ([_locationManager respondsToSelector:@selector(requestAlwaysAuthorization)]) { [_locationManager requestAlwaysAuthorization];
}
//Allow location updates in the background (iOS9+)
if ([_locationManager respondsToSelector:@selector(allowsBackgroundLocationUpdates)]) { _locationManager.allowsBackgroundLocationUpdates = YES;
}
[_locationManager startUpdatingLocation];

รวดเร็ว

self.locationManager.delegate = self
if #available (iOS 8.0,*) {
    self.locationManager.requestAlwaysAuthorization()
}
if #available (iOS 9.0,*) {
    self.locationManager.allowsBackgroundLocationUpdates = true
}
self.locationManager.startUpdatingLocation()

อ้างอิง: https://medium.com/@javedmultani16/location-service-in-the-background-ios-942c89cd99ba


-2
locationManager.allowsBackgroundLocationUpdates = true

4
คุณสามารถแก้ไขคำตอบของคุณและเพิ่มคำอธิบายว่าทำไม / วิธีนี้แก้ปัญหาได้หรือไม่
barbsan
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.