iOS后端模式借助位置升级实现
需求:iOS系统下使我们的app在后端下(点击Home键进入后端)仍能继续运行任务.
阅读前提:
- 理解后端任务机制
- 理解获取位置基本原理
GitHub地址(附代码) : iOS后端模式借助位置升级实现
简书地址 : iOS后端模式借助位置升级实现
博客地址 : iOS后端模式借助位置升级实现
掘金地址 : iOS后端模式借助位置升级实现
原理
iOS下默认app中所有线程在进入后端后(点击Home键或者上滑退出)所有线程处于挂起状态,即不支持后端运行程序,当再次点击进入app后,所有线程恢复运行,因而,假如要实现后端模式,即所有线程在进入后端后仍处于活跃状态。
苹果官方提供了打开后端模式开关的操作,但是需要说明使用后端模式的场景,如下项目配置图1中所示,但我们不能平白无故使用例如后端播放音乐,VOIP等等功能(否则会上架被拒),综合考虑,我们可以选用后端实时升级位置以实现支持后端模式,由于位置可以作为app的数据分析统计等等。
流程
- 实现获取地理位置信息(必需给予app始终允许的位置权限)
- 开启后端模式-升级地理位置信息
- 进入后端后轮循开始后端任务(1个后端任务有效时间为3分钟)
项目配置
实现后端模式需要借助例如位置升级,后端播放音乐,VOIP等等功能以实现后端app处于活跃状态
开启后端模式:Project -> Capabilities -> Background Modes -> Location updates
Background_Mode
plist文件中增加位置权限
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key> <string>Give me location authority</string> <key>NSLocationWhenInUseUsageDescription</key> <string>Give me location authority</string>
location_authority
- 导入静态库CoreLocation.framework, 而后在需要的文件中导入头文件#import <CoreLocation/CoreLocation.h>与#import <CoreLocation/CLLocation.h>
- 在启动app时给予始终允许的权限(否则无法支持后端模式)
Note : 由于我们是通过后端任务来实时获取地理位置以实现后端模式,所以假如不给予app始终允许的位置权限则无法实时在后端获取地理位置,也无法实现我们的需求。
实现
1.实现获取地理位置(CoreLocation)
1.1 初始化并配置locationManager对象
遵循CLLocationManagerDelegate协议,按照如下设置完成后就可在回调函数中获取经纬度等位置信息
@property (nonatomic, strong) CLLocationManager *locationManager; _locationManager = [[CLLocationManager alloc] init]; _locationManager.delegate = self; _locationManager.desiredAccuracy = kCLLocationAccuracyBestForNavigation; _locationManager.pausesLocationUpdatesAutomatically = NO; self.locationManager.distanceFilter = kCLDistanceFilterNone; // 不移动也可以后端刷新回调 if([[UIDevice currentDevice].systemVersion floatValue]>= 8.0) { [self.locationManager requestAlwaysAuthorization]; } [self.locationManager startUpdatingLocation];
1.2 回调函数中获取地理位置信息
假如开启后端模式,我们需要使用_isCollectLocation标记当前能否正在定位,假如当前中止定位我们则需要重启定位功能,我们在这里设置120秒后开启一个后端任务并在10秒后中止一个后端任务,由于一个后端任务的有效时间为3分钟,我们就让前一个后端任务执行到2分钟左右时中止掉(以防时间控制本身不准确所以设置2分钟),而后开启一个新的后端任务,不断循环改过程,就可实现后端模式。
-(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray<CLLocation *> *)locations { CLLocation *location = [locations lastObject]; //当前位置信息 self.longitude = location.coordinate.longitude; self.latitude = location.coordinate.latitude; if (self.isSupportBGMode) { //假如正在10秒定时收集的时间,不需要执行延时开启和关闭定位 if (_isCollectLocation) { return; } [self performSelector:@selector(restartLocation) withObject:nil afterDelay:120]; [self performSelector:@selector(stopLocation) withObject:nil afterDelay:10]; _isCollectLocation = YES;//标记正在定位 }}-(void)restartLocation { self.locationManager.delegate = self; self.locationManager.distanceFilter = kCLDistanceFilterNone; // 不移动也可以后端刷新回调 if ([[UIDevice currentDevice].systemVersion floatValue]>= 8.0) { [self.locationManager requestAlwaysAuthorization]; } [self.locationManager startUpdatingLocation]; [self.bgModeManager beginNewBackgroundTask];}-(void)stopLocation { log4cplus_debug("XDXBGModeManager", "%s - Stop Background Mode Location service !",ModuleName); _isCollectLocation = NO; [self.locationManager stopUpdatingLocation];}
1.3 开启与关闭后端模式
中止后端模式即中止位置升级,delegate也失效同时中止当前所有的后端任务,中止监听app进入后端的通知
开启后端模式即重新设置代理商并开始位置升级,同时注册客户进入后端的通知以便在客户进入后端后开启轮循的后端任务
- (void)openBGMode { self.isSupportBGMode = YES; // Note : You need to open background mode in the project setting, Otherwise the app will crash. _locationManager.allowsBackgroundLocationUpdates = YES; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationEnterBackground) name:UIApplicationDidEnterBackgroundNotification object:nil];}- (void)closeBGMode { self.isSupportBGMode = NO; [self.bgModeManager endAllBGTask]; [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidEnterBackgroundNotification object:nil];}-(void)applicationEnterBackground { [self startBGLocationService]; [self.bgModeManager beginNewBackgroundTask];}- (void)startBGLocationService { if ([CLLocationManager locationServicesEnabled] == NO) { log4cplus_error("XDXBGModeManager", "%s - You currently have all location services for this device disabled", ModuleName); }else { CLAuthorizationStatus authorizationStatus = [CLLocationManager authorizationStatus]; if(authorizationStatus == kCLAuthorizationStatusDenied || authorizationStatus == kCLAuthorizationStatusRestricted) { log4cplus_error("XDXBGModeManager", "%s - AuthorizationStatus failed",ModuleName); }else { log4cplus_info("XDXBGModeManager", "%s - Start Background Mode Location service !",ModuleName); self.locationManager.distanceFilter = kCLDistanceFilterNone; if([[UIDevice currentDevice].systemVersion floatValue]>= 8.0) { [self.locationManager requestAlwaysAuthorization]; } [self.locationManager startUpdatingLocation]; } }}
1.4 解决获取地理位置失败的情况
当客户拒绝提供位置权限或者当前网络故障时,应该给予客户提醒
- (void)locationManager: (CLLocationManager *)manager didFailWithError: (NSError *)error { log4cplus_error("XDXBGModeManager", "%s - locationManager error:%s",ModuleName, [NSString stringWithFormat:@"%@",error].UTF8String); self.latitude = 0; self.longitude = 0; switch([error code]) { case kCLErrorNetwork: log4cplus_error("XDXBGModeManager", "%s - %s : Please check the network connection !",ModuleName, __func__); static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Location Warning" message: @"Please check the network connection" preferredStyle:UIAlertControllerStyleAlert]; UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:@"OK" style: UIAlertActionStyleCancel handler:nil]; [alert addAction:cancelAction]; UIViewController *vc = [UIApplication sharedApplication].windows[0].rootViewController; [vc presentViewController:alert animated:YES completion:nil]; }); break; case kCLErrorDenied: { log4cplus_error("XDXBGModeManager", "%s - %s : Please open location authority on the setting if you want to use our service!",ModuleName, __func__); static dispatch_once_t onceToken2; dispatch_once(&onceToken2, ^{ UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Location Warning" message: @"Please allow our app access the location always on the setting->Privacy->Location Services !" preferredStyle:UIAlertControllerStyleAlert]; UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:@"OK" style: UIAlertActionStyleCancel handler:nil]; [alert addAction:cancelAction]; UIViewController *vc = [UIApplication sharedApplication].windows[0].rootViewController; [vc presentViewController:alert animated:YES completion:nil]; }); } break; default: break; }}
2.实现后端任务的轮循
2.1 基本配置
使用bgTaskIdList记录当前所有后端任务的列表,使用masterTaskId记录当前正在执行的后端任务
@property (nonatomic, strong) NSMutableArray *bgTaskIdList;@property (assign) UIBackgroundTaskIdentifier masterTaskId;
2.2 开启后端任务
beginBackgroundTaskWithExpirationHandler
调用此方法可实现开启一个后端任务,我们需要将此后端任务放入我们记录的数组中并将其设置为masterTaskId,当然,假如上次记录的后端记录已经失效,就记录新任务为主任务,假如上次开启的后端任务还没结束,就提前关闭,使用最新的后端任务
-(void)restartLocation { [self beginNewBackgroundTask];}-(UIBackgroundTaskIdentifier)beginNewBackgroundTask { UIApplication *application = [UIApplication sharedApplication]; __block UIBackgroundTaskIdentifier bgTaskId = UIBackgroundTaskInvalid; if([application respondsToSelector:@selector(beginBackgroundTaskWithExpirationHandler:)]) { bgTaskId = [application beginBackgroundTaskWithExpirationHandler:^{ log4cplus_warn("XDXBGModeManager", "%s - bg Task (%lu) expired !",ModuleName,bgTaskId); [self.bgTaskIdList removeObject:@(bgTaskId)];//过期任务从后端数组删除 bgTaskId = UIBackgroundTaskInvalid; [application endBackgroundTask:bgTaskId]; }]; } //假如上次记录的后端任务已经失效了,就记录最新的任务为主任务 if (_masterTaskId == UIBackgroundTaskInvalid) { self.masterTaskId = bgTaskId; log4cplus_warn("XDXBGModeManager", "%s - Start bg task : %lu",ModuleName,(unsigned long)bgTaskId); }else { //假如上次开启的后端任务还未结束,就提前关闭了,使用最新的后端任务 //add this id to our list log4cplus_warn("XDXBGModeManager", "%s - Keep bg task %lu",ModuleName,(unsigned long)bgTaskId); [self.bgTaskIdList addObject:@(bgTaskId)]; [self endInvalidBGTaskWithIsEndAll:NO];//留下最新创立的后端任务 } return bgTaskId;}
2.3 结束后端任务
假如仅仅结束其余后端任务,只保留当前主任务即将数组中除了最后一个元素外都删除,并通过遍历结束所有无效后端任务
- (void)endAllBGTask { [self endInvalidBGTaskWithIsEndAll:YES];}-(void)endInvalidBGTaskWithIsEndAll:(BOOL)isEndAll{ UIApplication *application = [UIApplication sharedApplication]; //假如为all 清空后端任务数组 //不为all 留下数组最后一个后端任务,也就是最新开启的任务 if ([application respondsToSelector:@selector(endBackGroundTask:)]) { for (int i = 0; i < (isEndAll ? _bgTaskIdList.count :_bgTaskIdList.count -1); i++) { UIBackgroundTaskIdentifier bgTaskId = [self.bgTaskIdList[0]integerValue]; log4cplus_debug("XDXBGModeManager", "%s - Close bg task %lu",ModuleName,(unsigned long)bgTaskId); [application endBackgroundTask:bgTaskId]; [self.bgTaskIdList removeObjectAtIndex:0]; } } ///假如数组大于0 所有剩下最后一个后端任务正在跑 if(self.bgTaskIdList.count > 0) { log4cplus_debug("XDXBGModeManager", "%s - The bg task is running %lu!",ModuleName,(long)[_bgTaskIdList[0]integerValue]); } if(isEndAll) { [application endBackgroundTask:self.masterTaskId]; self.masterTaskId = UIBackgroundTaskInvalid; }else { log4cplus_debug("XDXBGModeManager", "%s - Kept master background task id : %lu",ModuleName,(unsigned long)self.masterTaskId); }}
注意
后端模式在特定情况下会app被系统自动杀死
例如:息屏后放置较长时间或者app占用大量CPU资源
结果
运行Demo程序,打开后端开关,进入后端观察控制台仍有打印,证实后端模式启用成功
1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长!
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系管理员处理!
6. 本站资源售价只是摆设,本站源码仅提供给会员学习使用!
7. 如遇到加密压缩包,请使用360解压,如遇到无法解压的请联系管理员
开心源码网 » iOS后端模式借助位置升级实现