iOS如何保持程序在后台长时间运行
iOS为了让设备尽量省电,减少不必要的开销,保持系统流畅,因而对后台机制采用墓碑式的“假后台”。除了系统官方极少数程序可以真后台,一般开发者开发出来的应用程序后台受到以下限制:
1.用户按Home之后,App转入后台进行运行,此时拥有180s后台时间(iOS7)或者600s(iOS6)运行时间可以处理后台操作
2.当180S或者600S时间过去之后,可以告知系统未完成任务,需要申请继续完成,系统批准申请之后,可以继续运行,但总时间不会超过10分钟。
3.当10分钟时间到之后,无论怎么向系统申请继续后台,系统会强制挂起App,挂起所有后台操作、线程,直到用户再次点击App之后才会继续运行。
当然iOS为了特殊应用也保留了一些可以实现“真后台”的方法,摘取比较常用的:
1.VOIP
2.定位服务
3.后台下载
4.在后台一直播放无声音乐(容易受到电话或者其他程序影响,所以暂未考虑)
5….更多
其中VOIP需要绑定一个Socket链接并申明给系统,系统将会在后台接管这个连接,一旦远端数据过来,你的App将会被唤醒10s(或者更少)的时间来处理数据,超过时间或者处理完毕,程序继续休眠。
后台现在是iOS7引入的新API,网上实现的代码比较少,博主也没有细心去找。
由于博主要做的App需要在后台一直运行,每隔一段时间给服务器主动发送消息来保持帐号登陆状态,因而必须确保App不被系统墓碑限制。
博主最先尝试了很多方法,包括朋友发来的一个Demo,每180s后台时间过期就销毁自己然后再创建一个后台任务,但是实际测试只有10分钟时间。最后因为考虑到VOIP对服务端改动太大,时间又太紧,所以选择了定位服务的方法来保持后台。
要启动定位服务:
1.需要引入头文件:#import
2.在AppDelegate.m中定义CLLocationManager*locationManager;作为全局变量方便控制
3.在程序启动初期对定位服务进行初始化:
locationManager=[[CLLocationManageralloc]init];locationManager.delegate=self; //orwhateverclassyouhaveformanaginglocation
4.在程序转入后台的时候,启动定位服务
[locationManagerstartUpdatingLocation];(第一次运行这个方法的时候,如果之前用户没有使用过App,则会弹出是否允许位置服务,关于用户是否允许,后面代码中有判断)
这样在定位服务可用的时候,程序会不断刷新后台时间,实际测试,发现后台180s时间不断被刷新,达到长久后台的目的。
但是这样使用也有一些问题,在部分机器上面,定位服务即使打开也可能不能刷新后台时间,需要完全结束程序再运行。稳定性不知道是因为代码原因还是系统某些机制原因。
下面贴上代码:
注意:代码中包含朋友给的demo中,180s时间后销毁自己再创建自己的后台方法,我自己实现过程中加入了定位服务来确保后台能够一直在线。
源码参考部分来自网上,因为翻了Google,找了很多英文方面的博文,在此感谢原作者分享。
判断用户是否打开了定位服务,是否禁用了该程序的定位权限:
if(![CLLocationManagerlocationServicesEnabled]||([CLLocationManagerauthorizationStatus]==kCLAuthorizationStatusDenied))//判断定位服务是否打开 { [InterfaceFuncationShowAlertWithMessage:@"错误"AlertMessage:@"定位服务未打开\n保持在线需要后台定位服务\n请到设置-隐私中打开定位服务"ButtonTitle:@"我错了"]; return; }
AppDelegate.m源码:
@property(assign,nonatomic)UIBackgroundTaskIdentifierbgTask; @property(strong,nonatomic)dispatch_block_texpirationHandler; @property(assign,nonatomic)BOOLjobExpired; @property(assign,nonatomic)BOOLbackground; -(BOOL)application:(UIApplication*)applicationdidFinishLaunchingWithOptions:(NSDictionary*)launchOptions { UIApplication*app=[UIApplicationsharedApplication]; __weakNSUAAAIOSAppDelegate*selfRef=self; self.expirationHandler=^{//创建后台自唤醒,当180s时间结束的时候系统会调用这里面的方法 [appendBackgroundTask:selfRef.bgTask]; selfRef.bgTask=UIBackgroundTaskInvalid; selfRef.bgTask=[appbeginBackgroundTaskWithExpirationHandler:selfRef.expirationHandler]; NSLog(@"Expired"); selfRef.jobExpired=YES; while(selfRef.jobExpired) { //spinwhilewewaitforthetasktoactuallyend. NSLog(@"等待180s循环进程的结束"); [NSThreadsleepForTimeInterval:1]; } //Restartthebackgroundtasksowecanrunforever. [selfRefstartBackgroundTask]; }; //Assumethatwe'reinbackgroundatfirstsincewegetnonotificationfromdevicethatwe'reinbackgroundwhen //applaunchesimmediatelyintobackground(i.e.whenpoweringonthedeviceorwhentheappiskilledandrestarted) [selfmonitorBatteryStateInBackground]; locationManager=[[CLLocationManageralloc]init]; locationManager.delegate=self; //[locationManagerstartUpdatingLocation]; returnYES; } -(void)monitorBatteryStateInBackground { self.background=YES; [selfstartBackgroundTask]; } -(void)applicationDidBecomeActive:(UIApplication*)application { //Restartanytasksthatwerepaused(ornotyetstarted)whiletheapplicationwasinactive.Iftheapplicationwaspreviouslyinthebackground,optionallyrefreshtheuserinterface. NSLog(@"Appisactive"); [UIApplicationsharedApplication].applicationIconBadgeNumber=0;//取消应用程序通知脚标 [locationManagerstopUpdatingLocation]; self.background=NO; } -(void)applicationDidEnterBackground:(UIApplication*)application { //Usethismethodtoreleasesharedresources,saveuserdata,invalidatetimers,andstoreenoughapplicationstateinformationtorestoreyourapplicationtoitscurrentstateincaseitisterminatedlater. //Ifyourapplicationsupportsbackgroundexecution,thismethodiscalledinsteadofapplicationWillTerminate:whentheuserquits. //if([selfbgTask]) if(isLogined)//当登陆状态才启动后台操作 { self.bgTask=[[UIApplicationsharedApplication]beginBackgroundTaskWithExpirationHandler:self.expirationHandler]; NSLog(@"Enteredbackground"); [selfmonitorBatteryStateInBackground]; } } -(void)locationManager:(CLLocationManager*)managerdidFailWithError:(NSError*)error//当定位服务不可用出错时,系统会自动调用该函数 { NSLog(@"定位服务出错"); if([errorcode]==kCLErrorDenied)//通过error的code来判断错误类型 { //Accessdeniedbyuser NSLog(@"定位服务未打开"); [InterfaceFuncationShowAlertWithMessage:@"错误"AlertMessage:@"未开启定位服务\n客户端保持后台功能需要调用系统的位置服务\n请到设置中打开位置服务"ButtonTitle:@"好"]; } } -(void)locationManager:(CLLocationManager*)managerdidUpdateLocations:(NSArray*)locations//当用户位置改变时,系统会自动调用,这里必须写一点儿代码,否则后台时间刷新不管用 { NSLog(@"位置改变,必须做点儿事情才能刷新后台时间"); CLLocation*loc=[locationslastObject]; //NSTimeIntervalbackgroundTimeRemaining=[[UIApplicationsharedApplication]backgroundTimeRemaining]; //NSLog(@"BackgroundTimeRemaining=%.02fSeconds",backgroundTimeRemaining); //Lat/Lon floatlatitudeMe=loc.coordinate.latitude; floatlongitudeMe=loc.coordinate.longitude; } -(void)startBackgroundTask { NSLog(@"Restartingtask"); if(isLogined)//当登陆状态才进入后台循环 { //Startthelong-runningtask. NSLog(@"登录状态后台进程开启"); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0),^{ //Whenthejobexpiresitstillkeepsrunningsinceweneverexitedit.Thushavetheexpirationhandler //setaflagthatthejobexpiredandusethattoexitthewhileloopandendthetask. NSIntegercount=0; BOOLNoticeNoBackground=false;//只通知一次标志位 BOOLFlushBackgroundTime=false;//只通知一次标志位 locationManager.distanceFilter=kCLDistanceFilterNone;//任何运动均接受,任何运动将会触发定位更新 locationManager.desiredAccuracy=kCLLocationAccuracyHundredMeters;//定位精度 while(self.background&&!self.jobExpired) { NSLog(@"进入后台进程循环"); [NSThreadsleepForTimeInterval:1]; count++; if(count>60)//每60s进行一次开启定位,刷新后台时间 { count=0; [locationManagerstartUpdatingLocation]; NSLog(@"开始位置服务"); [NSThreadsleepForTimeInterval:1]; [locationManagerstopUpdatingLocation]; NSLog(@"停止位置服务"); FlushBackgroundTime=false; } if(!isLogined)//未登录或者掉线状态下关闭后台 { NSLog(@"保持在线进程失效,退出后台进程"); [InterfaceFuncationShowLocalNotification:@"保持在线失效,登录已被注销,请重新登录"]; [[UIApplicationsharedApplication]endBackgroundTask:self.bgTask]; return;//退出循环 } NSTimeIntervalbackgroundTimeRemaining=[[UIApplicationsharedApplication]backgroundTimeRemaining]; NSLog(@"BackgroundTimeRemaining=%.02fSeconds",backgroundTimeRemaining); if(backgroundTimeRemaining<30&&NoticeNoBackground==false) { [InterfaceFuncationShowLocalNotification:@"向系统申请长时间保持后台失败,请结束客户端重新登录"]; NoticeNoBackground=true; } //测试后台时间刷新 if(backgroundTimeRemaining>200&&FlushBackgroundTime==false) { [[NSNotificationCenterdefaultCenter]postNotificationName:@"MessageUpdate"object:@"刷新后台时间成功\n"]; FlushBackgroundTime=true; //[InterfaceFuncationShowLocalNotification:@"刷新后台时间成功"]; } } self.jobExpired=NO; }); } }
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。