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;
});
}
}
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。