iOS App 后台长定位方案

需求

接到一个新的需求:
收集用户的定位信息,包括前台后台,定位信息不需要持续定位,2-3分钟获取一次即可,需要保存到数据库,每隔30分钟上传到服务器,下次可以通过用户的定位信息绘制地图轨迹图。
那这里就有一个长后台的需求,需要保证程序在后台的也可以定位。

思考点🤔

  1. 长后台,但是我们要避免耗电,而后台持续定位,或者持续voip播放模式后,虽然会长时间保活,但是耗电还是挺高的
  2. 那我们就可以使用UIBackgroundTask,这个可以保证后台会延活3分钟
  3. 定位数据,每次获取到数据数据后就关闭定位,2分钟后再获取定位同时执行新的UIBackgroundTask,同时在合适的时候将旧的task失效掉,保证只有一个最后的后台任务在跑
  4. 定时器问题,我们使用dispatch_source_t timer,不会出现NSTimer的一些常见的问题(runloop,thread,leak)
    1. 必须保证有一个活跃的runloop
    2. NSTimer的创建和撤销必须在同一个线程操作,不能跨线程
    3. 存在内存泄漏的风险(target 对象A(一般可能是当前控制器)中被timer持有,info也被这个timer强引用,如果是设置repeat为YES,那这时候需要手动将timer cancel,不然页面不会被释放掉)

关键代码

长后台

beginBackgroundTaskWithExpirationHandler //开启后台任务
endBackgroundTask //关闭后台任务
这两个需要成对出现
这里的思路是有个后台的任务数组保存开启的后台任务id,和记录一个当前后台任务ID。
定时器每120秒(两分钟)执行一次获取定位信息,并新增新的后台任务

@property (nonatomic,strong) NSMutableArray *bgTaskIdList;///<后台任务数组
@property (assign) UIBackgroundTaskIdentifier  masterTaskId;///<当前后台任务ID
//开启新的后台任务
-(UIBackgroundTaskIdentifier)beginNewBackgroundTask
{
    
    UIApplication *application = [UIApplication sharedApplication];
    __block UIBackgroundTaskIdentifier bgTaskId = UIBackgroundTaskInvalid;
    if([application respondsToSelector:@selector(beginBackgroundTaskWithExpirationHandler:)])
    {
        bgTaskId = [application beginBackgroundTaskWithExpirationHandler:^{
            NSLog(@"bgTask 过期 %lu",(unsigned long)bgTaskId);
            [self.bgTaskIdList removeObject:@(bgTaskId)];//过期任务从后台数组删除
            bgTaskId = UIBackgroundTaskInvalid;
            [application endBackgroundTask:bgTaskId];
        }];
    }
    //如果上次记录的后台任务已经失效了,就记录最新的任务为主任务
    if (_masterTaskId == UIBackgroundTaskInvalid) {
        self.masterTaskId = bgTaskId;
        NSLog(@"开启后台任务 %lu",(unsigned long)bgTaskId);
    }
    else //如果上次开启的后台任务还未结束,就提前关闭了,使用最新的后台任务
    {
        //add this id to our list
        NSLog(@"保持后台任务 %lu", (unsigned long)bgTaskId);
        [self.bgTaskIdList addObject:@(bgTaskId)];
        [self endBackGroundTask:NO];//留下最新创建的后台任务
    }
    
    return bgTaskId;
}
//关闭后台任务
-(void)endBackGroundTask:(BOOL)all
{
    UIApplication *application = [UIApplication sharedApplication];
    //如果为all 清空后台任务数组
    //不为all 留下数组最后一个后台任务,也就是最新开启的任务
    if ([application respondsToSelector:@selector(endBackGroundTask:)]) {
        for (int i = 0; i < (all ? _bgTaskIdList.count :_bgTaskIdList.count -1); i++) {
            UIBackgroundTaskIdentifier bgTaskId = [self.bgTaskIdList[0]integerValue];
            NSLog(@"关闭后台任务 %lu",(unsigned long)bgTaskId);
            [application endBackgroundTask:bgTaskId];
            [self.bgTaskIdList removeObjectAtIndex:0];
        }
    }
    ///如果数组大于0 所有剩下最后一个后台任务正在跑
    if(all){
        [application endBackgroundTask:self.masterTaskId];
        self.masterTaskId = UIBackgroundTaskInvalid;
    }
}

定时器与定位

//开启定位和定时器,外部开启
- (void)initConfigTimerAndLocation{
    self.isConfig = YES;
    [self startLocation];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationEnterBackground) name:UIApplicationDidEnterBackgroundNotification object:nil];
    [self saveLocationTimer];
    [self uploadLocationsToServer];
}

//开启上传定时器
- (void)uploadLocationsToServer{
    //半小时上传 1800s
    [BgDispatchTimer scheduleDispatchTimerWithName:@"UpLoad" timeInterval:1800 queue:nil repeats:YES action:^{
        //从数据库拿出数据
        [[SimpleDB sharedSimpleDB] queryDataInLocationTable:^(NSArray * _Nonnull results) {
            if (results.count > 0) {
                //上传
                [RequestService uploadTrackInfo:results sussess:^(NSString *message) {
                    //上传成功,删除数据库之前所有旧的定位信息
                    [[SimpleDB sharedSimpleDB] deleteAllDataLocationTable];
                } failure:^(NSError *error) {
                }];
            }
        }];
    }];
}
//开启保存定位定时器
- (void)saveLocationTimer{
    //120秒后重启定位 2分钟存一下
    [BgDispatchTimer scheduleDispatchTimerWithName:@"saveLocation" timeInterval:120 queue:nil repeats:YES action:^{
        //开启定位
        [self startLocation];
    }];
}
//监听进入后台方法
- (void)applicationEnterBackground{
    NSLog(@"come in background");
    [self startLocation];
}

//重新开启定位
- (void)startLocation{
    WeakSelf(self);
    //要在主线程,不然定位不回调
    dispatch_async(dispatch_get_main_queue(), ^{
        [self.mapManager getLastLocation:^(CLLocation *location, NSString *address) {
            StrongSelf(self);
            [self saveLocationWithinfo:location addderes:address];
        }];
    });
    
    [self.bgTask beginNewBackgroundTask];
}

Tip

开启定位需要在info.plist添加定位描述代码

//source code 添加
    <key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
    <string>请求获取用户定位权限</string>
    <key>NSLocationWhenInUseUsageDescription</key>
    <string>请求获取用户定位权限</string>

代码地址

Demo已经脱敏,网络请求和数据库信息已经删去
长后台定位Demo

参考文章
iOS 关于后台持续运行
CLLocationManager-blocks
GCD实现多个定时器,完美避过NSTimer的三大缺陷

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,214评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,307评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,543评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,221评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,224评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,007评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,313评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,956评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,441评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,925评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,018评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,685评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,234评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,240评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,464评论 1 261
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,467评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,762评论 2 345

推荐阅读更多精彩内容