IOS 后台运行介绍及解决办法

第一部分

1.先说说iOS 应用程序5个状态:

停止运行-应用程序已经终止,或者还未启动。

不活动-应用程序处于前台但不再接收事件(例如,用户在app处于活动时锁住了设备)。

活动-app处于“使用中”的状态。

后台-app不再屏幕上显示,但它仍然执行代码。

挂起-app仍然驻留内存但不再执行代码。

按下Home键时,app从活动状态转入后台,绝大部分app通常在几秒内就从后台变成了挂起。

在内存吃紧的时候,iphone会首先关闭那些挂起的app。

从 iOS 4 开始,应用就可以在退到后台后,继续运行一小段时间(10 分钟);

2.还可以把自己声明为需要在后台运行,就能不限时地运行了。

不过限制为播放音乐、使用 GPS 、voip、。 值得一提的是,有的应用为了达到后台不限时运行的目的,在后台播放无声的音乐(审核不一定会被发现)。

iOS 5 开始又多了一种类型:下载报刊杂志。

然后 iOS 7 则可以下载各种玩意和定时抓取。

iOS 7 需要注意的区别:iOS 7 以前,应用进入后台继续运行时,如果用户锁屏了,那么 iOS 会等待应用运行完,才进入睡眠状态。而在 iOS 7 上,系统会很快进入睡眠状态,那些后台应用也就暂停了。如果收到事件被唤醒(例如定时事件、推送、位置更新等),后台应用才能继续运行一会。因为处理过程变成了断断续续的,因此下载时也要使用 NSURLSession 来处理(即下文中的 Background Transfer Service)。

3. 在我看来,苹果限制 app在后台运行,是为了更有效的利用硬件使用当前的app,不然,过多的app驻留后台,对手机资源占用是一大问题。

二. ios7以后提供的后台接口模式

1、Background Audio,这是后台的音频,这个很早之前便有,也是iOS设备中用得最多的后台应用,调用这个接口可以实现后台的音乐播放。

2、Location Services,这是后台的定位,系统会拥有统一页面进行管理。

3、VoIP,后台语音服务,类似Skype通话应用需要调用,可进行后台的语音通话。

4、Newsstand,报刊杂志后台自动下载更新,其能够自动实时更新。

5、Background Task  Completion,这个接口早在iOS 4时候便拥有,其可以供任意类型的APP使用,不过在旧系统中,这个接口的后台限制运行时间仅为10分钟,意味着当应用退至后台,其后台运行仅能持续10分钟便会转至休眠状态。iOS 7中对这个接口作出了改变,原来的为连续10分钟,即不论你这10分钟内用户是否关闭屏幕进入休眠状态,应用仍然会在后台等待10分钟完结后推出,而新的改进为假如遇到关闭屏幕休眠的情况,这后台运行的10分钟便会跟随一同休眠,剩余的后台时间将会留待用户再一次唤醒设备才计算。这样后台运行的时间仍然为10分钟,但并不连续,这样做的优点为省电。

如现在有一些词典应用带有后台复制选词功能,实际上其是利用了这个接口,如果用户开启词典后并推出,即使屏幕关闭,但词典仍然在后台运行,电量消耗还是比较大的,在iOS 7上,这个问题可以得到解决。

6、 Remote Notification,这是本次较大的一个改进接口,以往聊天类应用接受推送后点进去需要再收一次信息,这情况在QQ、微信等应用上最为明显。不过拥有了这个接口后,这情况将不复存在,以后推送将能够直接启动后台任务。值得注意的是remote notification支持silent notification(静默推送),这样dropbox这类同步应用可以在后台以最节能的模式实时静默同步了,类似布卡漫画这种也可以推送正在追的漫画的新章节并在后台静默下载,待到下载好再给用户发送一个本地推送,用户点开即看无需再联网。

7、Background Transfer Service,后台上传下载。iOS最接近传统多任务的后台接口,可供任意类型的app调用,无时间限制。应用场景包括后台上传和下载数据,这使得游戏后台更新数据包,后台上传视频等等都成为可能,但是正如其名字,它只能用于处理上传下载这种传输类的任务,类似后台剪切板监控这种它就无能为力了。

iOS 7新增的background fetch,这个后台接口在苹果WWDC 2013上有提及,其会根据用户行为自动调整达到效率最优的后台模式,能够处理不是很有时效性的信息获取。例如一些社交、新闻类的应用的后台信息更新,iOS系统便会根据应用启动频率、时间和当前网络和电量的状况来智能分配每个应用的后台获取频率和启动时长。

三 .  当前社交项目,如何使用ios后台

1.当前项目特点:

a. 在保存长连接的情况下,用户一直在线,才能即时接收到消息;

b. 在初始化连接的时候,需要做很多处理,如果经常连接,必然很耗电, 所以尽力在后台的时候,不是时常断开后又连接;

2. 通过以上分析,改选用何种方式来保存app后台运行

voip不行;

静音播放,不清除这种方式,是否可以通过审核;

vpns推送,可取的方式,(具体方法: 用户在登录后,发送一个设备的tokenid; 在发送消息时,平台根据对方是离线还是在线,来判断要不要发推送消息)

3.background fetch在该项目中的应用

由于该app在初始化时,需要耗点时间,最好的方式就是通过  后台获取  来处理该工作,这样能保证用户的流畅体验。

第二部分:保持程序在后台长时间运行

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. 1、需要引入头文件: #import <CoreLocation/CoreLocation.h>
  2. 2、在 AppDelegate.m 中定义 CLLocationManager * locationManager;作为全局变量方便控制
  3. 3、在程序启动初期对定位服务进行初始化:
  4. locationManager = [[CLLocationManager alloc] init];
    locationManager.delegate = self;//or whatever class you have for managing location</pre>
  5. 4、在程序转入后台的时候,启动定位服务
  6. [locationManager startUpdatingLocation]; (第一次运行这个方法的时候,如果之前用户没有使用过App,则会弹出是否允许位置服务,关于用户是否允许,后面代码中有判断)
  7. 这样在定位服务可用的时候,程序会不断刷新后台时间,实际测试,发现后台180s时间不断被刷新,达到长久后台的目的。
  8. 但是这样使用也有一些问题,在部分机器上面,定位服务即使打开也可能不能刷新后台时间,需要完全结束程序再运行。稳定性不知道是因为代码原因还是系统某些机制原因。
  9. 判断用户是否打开了定位服务,是否禁用了该程序的定位权限:
  10. if(![CLLocationManager locationServicesEnabled] || ([CLLocationManager authorizationStatus] == kCLAuthorizationStatusDenied))//判断定位服务是否打开
    {
        [InterfaceFuncation ShowAlertWithMessage:@"错误" AlertMessage:@"定位服务未打开\n保持在线需要后台定位服务\n请到 设置-隐私 中打开定位服务" ButtonTitle:@"我错了"];
        return;
    }
  11. AppDelegate.m
  12. @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;
            });
        }
    }
    //iOS9中还得加入如下设置项:
  13. (CLLocationManager *)sharedLocationManager {
        static CLLocationManager *_locationManager;
        
        @synchronized(self) {
            if (_locationManager == nil) {
                _locationManager = [[CLLocationManager alloc] init];
                
                if ([_locationManager respondsToSelector:@selector(allowsBackgroundLocationUpdates)]) {
                    [_locationManager setAllowsBackgroundLocationUpdates:YES];
                }
                _locationManager.desiredAccuracy = kCLLocationAccuracyBestForNavigation;
                
            }
        }
        return _locationManager;
    }

    在此处感谢简书作者 DevPaoPao 
    还有:最模板   
    我将两篇文章合并希望能给后来者一些方便
    这里附上另外一种解决办法:http://blog.sina.com.cn/s/blog_7b9d64af0101cjci.html


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值