美文网首页第三方库收集
后台定时定位-Location实践经验

后台定时定位-Location实践经验

作者: 潇潇潇潇潇潇潇 | 来源:发表于2016-08-18 21:03 被阅读824次

    前言

    最近接到这样一个需求,每隔固定时间采集用户的位置,然后再把这些数据上传到服务器。研究了下ios的定位功能,在后台定时遇到了一些困难。当app进入后台状态,定时器就不再运行,导致无法取到用户的位置。
    在网上查了一些资料,发现有人已经实现了这个功能,它是一个Github上的第三方库,叫Location,不仅能在后台定时采集位置数据,还优化了定位方式,减少耗电。
    接下来我们来看看Location是如何实现的以及它存在的问题。

    代码结构

    Location代码结构

    BackgroundTaskManager

    负责创建、管理后台任务,提供两个方法,开始和结束后台任务。它实现了后台任务的无限期运行。
    @interface BackgroundTaskManager : NSObject

    +(instancetype)sharedBackgroundTaskManager;
    
    -(UIBackgroundTaskIdentifier)beginNewBackgroundTask;
    -(void)endAllBackgroundTasks;
    
    @end
    

    LocationShareModel

    包装了后台任务类,也管理定位定时器。

    @interface LocationShareModel : NSObject
    @property (nonatomic) NSTimer *timer;
    @property (nonatomic) NSTimer * delay10Seconds;
    @property (nonatomic) BackgroundTaskManager * bgTask;
    @property (nonatomic) NSMutableArray *myLocationArray;
    +(id)sharedModel;
    @end
    

    LocationTracker

    负责管理后台定时定位的主类。
    我们先看一下它提供的接口。
    + (CLLocationManager *)sharedLocationManager;

    //开始追踪定位
    - (BOOL)startLocationTracking;
    //停止追踪定位
    - (void)stopLocationTracking;
    //向服务器发送已获取的设备位置数据
    - (void)updateLocationToServer;
    

    LocationTracker类的初始化
    + (CLLocationManager *)sharedLocationManager {
    static CLLocationManager *_locationManager;
    @synchronized(self) {
    if (_locationManager == nil) {
    _locationManager = [[CLLocationManager alloc] init];
    //设备位置精度,这里设置为最高精度
    _locationManager.desiredAccuracy = kCLLocationAccuracyBestForNavigation;
    //是否暂停更新
    _locationManager.pausesLocationUpdatesAutomatically = NO;
    //iOS9的新特性,打开后台位置更新,还要在plist中配置
    if (IOS9ORLATER) {
    _locationManager.allowsBackgroundLocationUpdates = YES;
    }
    }
    }
    return _locationManager;
    }
    当应用进入后台,借助BackgroundTaskManager无限延长后台任务的存活时间,进行后台采集位置信息。
    - (id)init {
    if (self==[super init]) {
    //Get the share model and also initialize myLocationArray
    self.shareModel = [LocationShareModel sharedModel];
    self.shareModel.myLocationArray = [[NSMutableArray alloc]init];

            [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationEnterBackground) name:UIApplicationDidEnterBackgroundNotification object:nil];
        }
        return self;
    }
    
    -(void)applicationEnterBackground{
        CLLocationManager *locationManager = [LocationTracker sharedLocationManager];
        locationManager.delegate = self;
        locationManager.desiredAccuracy = kCLLocationAccuracyBestForNavigation;
        locationManager.distanceFilter = kCLDistanceFilterNone;
    
        if(IOS8ORLATER) {
            [locationManager requestAlwaysAuthorization];
        }
        [locationManager startUpdatingLocation];
    
        //Use the BackgroundTaskManager to manage all the background Task
        self.shareModel.bgTask = [BackgroundTaskManager sharedBackgroundTaskManager];
        [self.shareModel.bgTask beginNewBackgroundTask];
    }
    

    持续获取设备的位置,是个特别耗电的任务。为了减少耗电,定时关闭位置服务。在接收到位置信息10秒后,关闭位置服务,一分钟后再重新打开位置服务,这样就达到减少耗电的目的。我们看代码的实现

    -(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations{
    
        ......
    
        self.shareModel.bgTask = [BackgroundTaskManager sharedBackgroundTaskManager];
        [self.shareModel.bgTask beginNewBackgroundTask];
    
        //Restart the locationMaanger after 1 minute
        self.shareModel.timer = [NSTimer scheduledTimerWithTimeInterval:60 target:self
                                                               selector:@selector(restartLocationUpdates)
                                                               userInfo:nil
                                                                repeats:NO];
    
        //Will only stop the locationManager after 10 seconds, so that we can get some accurate locations
        //The location manager will only operate for 10 seconds to save battery
        if (self.shareModel.delay10Seconds) {
            [self.shareModel.delay10Seconds invalidate];
            self.shareModel.delay10Seconds = nil;
        }
    
        self.shareModel.delay10Seconds = [NSTimer scheduledTimerWithTimeInterval:10 target:self
                                                                        selector:@selector(stopLocationDelayBy10Seconds)
                                                                        userInfo:nil
                                                                         repeats:NO];
    
    }
    

    重启位置服务
    - (void) restartLocationUpdates
    {
    if (self.shareModel.timer) {
    [self.shareModel.timer invalidate];
    self.shareModel.timer = nil;
    }

        CLLocationManager *locationManager = [LocationTracker sharedLocationManager];
        locationManager.delegate = self;
        locationManager.desiredAccuracy = kCLLocationAccuracyBestForNavigation;
        locationManager.distanceFilter = kCLDistanceFilterNone;
    
        if(IOS8ORLATER) {
            [locationManager requestAlwaysAuthorization];
        }
        [locationManager startUpdatingLocation];
    }
    

    按照Location库的设计,在updateLocationToServer方法中,处理向服务器发送信息。
    - (void)updateLocationToServer {

        .........
    
        //TODO: 在这里插入你的代码,处理向服务器发送位置信息
    
    
        ........
    
    }
    

    实践

    这个类库解决了后台定时定位的问题,使用也很简单,而且还处理了定位耗电的问题,使耗电量降低到每小时5%左右。但在实践的过程中发现它存在一些问题。

    1. 发送数据到服务器方法的定时器,最大支持3分钟;
    2. 发送数据到服务器方法的定时器,在运行2小时后就停止了;
    3. 发送数据到服务器方法的定时器,不准确;

    经过一些尝试,废弃了updateLocationToServer方法,将发送数据的代码移到locationManager中,才解决了这几个问题。具体解决方法如下:
    首先为LocationTracker类添加一个私有的属性,记录上次执行发送数据的时间。

    @interface LocationTracker ()
    @property (nonatomic, strong) NSDate *lastSendDate;
    @end
    

    在locationManager回调方法中添加自己的代码。

        .......
    //Select only valid location and also location with good accuracy
            if(newLocation!=nil&&theAccuracy>0
               &&theAccuracy<2000
               &&(!(theLocation.latitude==0.0&&theLocation.longitude==0.0))){
    
                self.myLastLocation = theLocation;
                self.myLastLocationAccuracy= theAccuracy;
    
                NSMutableDictionary * dict = [[NSMutableDictionary alloc]init];
                [dict setObject:[NSNumber numberWithFloat:theLocation.latitude] forKey:@"latitude"];
                [dict setObject:[NSNumber numberWithFloat:theLocation.longitude] forKey:@"longitude"];
                [dict setObject:[NSNumber numberWithFloat:theAccuracy] forKey:@"theAccuracy"];
    
                //Add the vallid location with good accuracy into an array
                //Every 1 minute, I will select the best location based on accuracy and send to server
                [self.shareModel.myLocationArray addObject:dict];
    
                //这里插入代码,处理发送位置数据到服务器
                    if (!self.lastSendDate || [[self.lastSendDate dateByAddingHours:1] compare:[NSDate date]] <= 0) {
                        self.lastSendDate = [NSDate date];
                        }
                }

    相关文章

      网友评论

      • 王德夫:xxxx,写得不错
      • 07212a79db66:博主,这个2小时和定时器不准确问题具体怎么解决的,有Demo没, 按你说的在locationManager里面发送请求貌似也不行啊
      • 来宝:你这个能获取海拔高度不
        潇潇潇潇潇潇潇:@来宝 可以的

      本文标题:后台定时定位-Location实践经验

      本文链接:https://www.haomeiwen.com/subject/xxavsttx.html