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 <CoreLocation/CoreLocation.h>
2.在AppDelegate.m中定义CLLocationManager * locationManager;作为全局变量方便控制
3.在程序启动初期对定位服务进行初始化:

locationManager = [[CLLocationManager alloc] init];
locationManager.delegate = self;//or whatever class you have for managing location</pre>

4.在程序转入后台的时候,启动定位服务
[locationManager startUpdatingLocation];(第一次运行这个方法的时候,如果之前用户没有使用过App,则会弹出是否允许位置服务,关于用户是否允许,后面代码中有判断)
这样在定位服务可用的时候,程序会不断刷新后台时间,实际测试,发现后台180s时间不断被刷新,达到长久后台的目的。

但是这样使用也有一些问题,在部分机器上面,定位服务即使打开也可能不能刷新后台时间,需要完全结束程序再运行。稳定性不知道是因为代码原因还是系统某些机制原因。

下面贴上代码:
注意:代码中包含朋友给的demo中,180s时间后销毁自己再创建自己的后台方法,我自己实现过程中加入了定位服务来确保后台能够一直在线。
源码参考部分来自网上,因为翻了Google,找了很多英文方面的博文,在此感谢原作者分享。

判断用户是否打开了定位服务,是否禁用了该程序的定位权限:

if(![CLLocationManager locationServicesEnabled] || ([CLLocationManager authorizationStatus] == kCLAuthorizationStatusDenied))//判断定位服务是否打开
{
[InterfaceFuncation ShowAlertWithMessage:@"错误" AlertMessage:@"定位服务未打开\n保持在线需要后台定位服务\n请到 设置-隐私 中打开定位服务" ButtonTitle:@"我错了"];
return;
}

AppDelegate.m源码:

@property (assign, nonatomic) UIBackgroundTaskIdentifier bgTask;

@property (strong, nonatomic) dispatch_block_t expirationHandler;
@property (assign, nonatomic) BOOL jobExpired;
@property (assign, nonatomic) BOOL background;
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{ UIApplication* app = [UIApplication sharedApplication]; __weak NSUAAAIOSAppDelegate* selfRef = self; self.expirationHandler = ^{ //创建后台自唤醒,当180s时间结束的时候系统会调用这里面的方法
[app endBackgroundTask:selfRef.bgTask];
selfRef.bgTask = UIBackgroundTaskInvalid;
selfRef.bgTask = [app beginBackgroundTaskWithExpirationHandler:selfRef.expirationHandler];
NSLog(@"Expired");
selfRef.jobExpired = YES;
while(selfRef.jobExpired)
{
// spin while we wait for the task to actually end.
NSLog(@"等待180s循环进程的结束");
[NSThread sleepForTimeInterval:1];
}
// Restart the background task so we can run forever.
[selfRef startBackgroundTask];
}; // Assume that we're in background at first since we get no notification from device that we're in background when
// app launches immediately into background (i.e. when powering on the device or when the app is killed and restarted)
[self monitorBatteryStateInBackground];
locationManager = [[CLLocationManager alloc] init];
locationManager.delegate = self;
//[locationManager startUpdatingLocation];
return YES;
} - (void)monitorBatteryStateInBackground
{
self.background = YES;
[self startBackgroundTask];
} - (void)applicationDidBecomeActive:(UIApplication *)application
{
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
NSLog(@"App is active");
[UIApplication sharedApplication].applicationIconBadgeNumber=0;//取消应用程序通知脚标
[locationManager stopUpdatingLocation];
self.background = NO;
} - (void)applicationDidEnterBackground:(UIApplication *)application
{
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
//if([self bgTask])
if(isLogined)//当登陆状态才启动后台操作
{
self.bgTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:self.expirationHandler];
NSLog(@"Entered background");
[self monitorBatteryStateInBackground];
}
} - (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error//当定位服务不可用出错时,系统会自动调用该函数
{
NSLog(@"定位服务出错");
if([error code]==kCLErrorDenied)//通过error的code来判断错误类型
{
//Access denied by user
NSLog(@"定位服务未打开");
[InterfaceFuncation ShowAlertWithMessage:@"错误" AlertMessage:@"未开启定位服务\n客户端保持后台功能需要调用系统的位置服务\n请到设置中打开位置服务" ButtonTitle:@"好"];
}
} - (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations//当用户位置改变时,系统会自动调用,这里必须写一点儿代码,否则后台时间刷新不管用
{
NSLog(@"位置改变,必须做点儿事情才能刷新后台时间");
CLLocation *loc = [locations lastObject];
//NSTimeInterval backgroundTimeRemaining = [[UIApplication sharedApplication] backgroundTimeRemaining];
//NSLog(@"Background Time Remaining = %.02f Seconds",backgroundTimeRemaining);
// Lat/Lon
float latitudeMe = loc.coordinate.latitude;
float longitudeMe = loc.coordinate.longitude;
} - (void)startBackgroundTask
{
NSLog(@"Restarting task");
if(isLogined)//当登陆状态才进入后台循环
{
// Start the long-running task.
NSLog(@"登录状态后台进程开启");
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// When the job expires it still keeps running since we never exited it. Thus have the expiration handler
// set a flag that the job expired and use that to exit the while loop and end the task.
NSInteger count=0;
BOOL NoticeNoBackground=false;//只通知一次标志位
BOOL FlushBackgroundTime=false;//只通知一次标志位
locationManager.distanceFilter = kCLDistanceFilterNone;//任何运动均接受,任何运动将会触发定位更新
locationManager.desiredAccuracy = kCLLocationAccuracyHundredMeters;//定位精度
while(self.background && !self.jobExpired)
{
NSLog(@"进入后台进程循环");
[NSThread sleepForTimeInterval:1];
count++;
if(count>60)//每60s进行一次开启定位,刷新后台时间
{
count=0;
[locationManager startUpdatingLocation];
NSLog(@"开始位置服务");
[NSThread sleepForTimeInterval:1];
[locationManager stopUpdatingLocation];
NSLog(@"停止位置服务");
FlushBackgroundTime=false;
}
if(!isLogined)//未登录或者掉线状态下关闭后台
{
NSLog(@"保持在线进程失效,退出后台进程");
[InterfaceFuncation ShowLocalNotification:@"保持在线失效,登录已被注销,请重新登录"];
[[UIApplication sharedApplication] endBackgroundTask:self.bgTask];
return;//退出循环
}
NSTimeInterval backgroundTimeRemaining = [[UIApplication sharedApplication] backgroundTimeRemaining];
NSLog(@"Background Time Remaining = %.02f Seconds",backgroundTimeRemaining);
if(backgroundTimeRemaining<30&&NoticeNoBackground==false)
{
[InterfaceFuncation ShowLocalNotification:@"向系统申请长时间保持后台失败,请结束客户端重新登录"];
NoticeNoBackground=true;
}
//测试后台时间刷新
if(backgroundTimeRemaining>200&&FlushBackgroundTime==false)
{
[[NSNotificationCenter defaultCenter] postNotificationName:@"MessageUpdate" object:@"刷新后台时间成功\n"];
FlushBackgroundTime=true;
//[InterfaceFuncation ShowLocalNotification:@"刷新后台时间成功"];
}
}
self.jobExpired = NO;
});
}
}

iOS开发:保持程序在后台长时间运行的更多相关文章

  1. iOS开发:后台运行以及保持程序在后台长时间运行

    第一部分 1.先说说iOS 应用程序5个状态: 停止运行-应用程序已经终止,或者还未启动. 不活动-应用程序处于前台但不再接收事件(例如,用户在app处于活动时锁住了设备). 活动-app处于“使用中 ...

  2. 保持程序在后台长时间运行-b

    iOS为了让设备尽量省电,减少不必要的开销,保持系统流畅,因而对后台机制采用墓碑式的“假后台”.除了系统官方极少数程序可以真后台,一般开发者开发出来的应用程序后台受到以下限制:1.用户按Home之后, ...

  3. iOS-实现后台长时间运行

    前言 一般APP在按下Home键被挂起后,这时APP的 backgroundTimeRemaining 也就是后台运行时间大约只有3分钟,如果在退出APP后,过十几二十二分钟或者更长时间再回到APP, ...

  4. iOS开发人员程序许可协议

    请细致阅读以下的许可协议条款和条件之前下载或使用苹果软件.   这些条款和条件构成你和苹果之间的法律协议.   iOS开发人员程序许可协议   目的 你想使用苹果软件(例如以下定义)来开发一个或多个应 ...

  5. Xamarin iOS开发中的编辑、连接、运行

    Xamarin iOS开发中的编辑.连接.运行 创建好工程后,就可以单击Xamarin Studio上方的运行按钮,如图1.37所示,对HelloWorld项目进行编辑.连接以及运行了.运行效果如图1 ...

  6. iOS开发-捕获程序崩溃日志

    iOS开发中遇到程序崩溃是很正常的事情,如何在程序崩溃时捕获到异常信息并通知开发者,是大多数软件都选择的方法.下面就介绍如何在iOS中实现: 1. 在程序启动时加上一个异常捕获监听,用来处理程序崩溃时 ...

  7. iOS开发 - App程序启动原理

    Info.plist和pch文件的作用 建立一个project后,会在Supporting files目录下看到一个"project名-Info.plist"的文件,该文件对pro ...

  8. iOS开发--应用程序上线

    iOS应用上线 http://www.jianshu.com/p/ffddc5e5f0b9 iOS真机测试 http://www.jianshu.com/p/986e02d38f1b iOS应用程序打 ...

  9. IOS开发之程序执行状态更改

    1 前言 上节我们介绍了程序执行的状态,从例子中我们可以发现处理这些状态更改的时候有明确的策略可以遵循,这次我们就来介绍一下. 2 详述 2.1 活动->不活动 使用applicationWil ...

随机推荐

  1. examine self thrice a day2017

    1.6.2017 葰γí千萬丆γáò絠ィ壬菏鰯嚸,銣惈絠,倁噵ㄖㄅ者β淂簳掉.千澫丕楆被莂亽抓ィ主鰯點,以佌襲撃.那個記駐,吢軟劊嗐死尓垍己ㄖㄅ.絠仒槇可笑,鯟覀者βる誐手裏魢泾吺有談半リ的籌碼,還茬 ...

  2. angular遇到问题

    一.一个js中只有一个angunlar.module,但可以有多个controller,从而控制多个不同的作用域,每个作用域都有独立的$scope.不同作用域之间又有$rootScope这个桥梁 二. ...

  3. 引用类型(object、array)

    1.Object类型 1)创建方法: //使用new加object构造函数 var person = new Object(); person.name = "aaa"; pers ...

  4. 从下往上看--新皮层资料的读后感 第三部分 70年前的逆向推演- 从NN到ANN

    第三部分 NN-ANN 70年前的逆向推演 从这部分开始,调整一下视角主要学习神经网络算法,将其与生物神经网络进行横向的比较,以窥探一二. 现在基于NN的AI应用几乎是满地都是,效果也不错,这种貌似神 ...

  5. java.lang.NumberFormatException: For input string: "1608020001 " 错误

    错误: java.lang.NumberFormatException: For input string: "1608020001 "    at java.lang.Numbe ...

  6. Error 403--Forbidden

    转自他人:From RFC 2068 Hypertext Transfer Protocol -- HTTP/1.1:10.4.4 403 ForbiddenThe server understood ...

  7. Y Combinator

    常见的例子 阶乘函数: fact = (a) -> if a > 0 then a * fact(a - 1) else 1 问题的提出 如上,在fact函数中调用了fact本身,无法使用 ...

  8. 更新Debian软件源

    更新Debian软件源 sudo cp /etc/apt/sources.list /etc/apt/sources.list_bak #备份一下软件源 sudo vi /etc/apt/source ...

  9. JS学习笔记01

    文章转载pigpigpig4587 的 1.Javascript是区分大小写的语言.也就是说.关键字.变量,函数和所有的标识符都必须采取一致的大小写形式.因为html不严格区分大小写,所以在html中 ...

  10. Apache、NGINX支持中文URL

    Apache(32位):安装环境:CentOS 5.6 + Apache 2.2.15安装结果:安装后支持“中文图片.文件名”链接直接打开以下为安装过程:1.下载安装包 wget ftp://ftp. ...