前言
一般APP在按下Home键被挂起后,这时APP的 backgroundTimeRemaining 也就是后台运行时间大约只有3分钟,如果在退出APP后,过十几二十二分钟或者更长时间再回到APP,APP就会回到刚打开时的状态,也就是首页;有的项目在被挂起后需要在后台运行一段时间,使有足够的时间来完成与服务器对接的操作,或者需要一直运行的需求;如果需要,则在APP被挂起后,申请后台,来延长后台运行时间。
APP申请后台运行的方式有几种:
播放音乐
定位
Newsstand downloads
fetch 等;

这里主要说下后台播放无声音乐(其实是不播放),采用哪种方式,对应勾选上图;我的项目中有音频播放需求,如果没有,那就找一个播放音频的理由,或者用其他方式实现。
实现
这里采用了两个单例:电话监控(XKTelManager)、后台运行(XKBGRunManager),电话监控可以忽略,视情况而用;采用单例是为了方便管理;
XKTelManager.h
- #import <Foundation/Foundation.h>
-
- @interface XKTelManager : NSObject
- ///是否在后台运行
- @property (nonatomic,assign) BOOL inBackgroundRun;
- + (XKTelManager *)sharedManager;
- /**
- 来电监听
- */
- - (void)startMonitor;
- @end
XKTelManager.m
- #import "XKTelManager.h"
- #import "XKBGRunManager.h"
- #import <CoreTelephony/CTCallCenter.h>
- #import <CoreTelephony/CTCall.h>
-
- static XKTelManager *_sharedManger;
- @interface XKTelManager()
- @property (nonatomic, strong) CTCallCenter *callCenter;
- @end
- @implementation XKTelManager
- + (XKTelManager *)sharedManager{
- static dispatch_once_t onceTelSingle;
- dispatch_once(&onceTelSingle, ^{
- if (!_sharedManger) {
- _sharedManger = [[XKTelManager alloc]init];
- }
- });
- return _sharedManger;
- }
- - (instancetype)init{
- self = [super init];
- if (self) {
- _inBackgroundRun = NO;
- }
- return self;
- }
- #pragma mark -********* 监听电话相关
- - (void)startMonitor {
- __weak typeof(self) weakSelf = self;
- _callCenter = [[CTCallCenter alloc] init];
- _callCenter.callEventHandler = ^(CTCall * call) {
- ///如果已经进入后台了,不做任何操作
- if (weakSelf.inBackgroundRun) {
- return;
- }
- ///APP未进入后台
- if ([call.callState isEqualToString:CTCallStateDisconnected]){
- NSLog(@"电话 --- 断开连接");
- [[XKBGRunManager sharedManager] stopBGRun];
- }
- else if ([call.callState isEqualToString:CTCallStateConnected]){
- NSLog(@"电话 --- 接通");
- }
- else if ([call.callState isEqualToString:CTCallStateIncoming]){
- NSLog(@"电话 --- 待接通");
- [[XKBGRunManager sharedManager] startBGRun];
- }
- else if ([call.callState isEqualToString:CTCallStateDialing]){
- NSLog(@"电话 --- 拨号中");
- [[XKBGRunManager sharedManager] startBGRun];
- }
- else {
- NSLog(@"电话 --- 无操作");
- }
-
- };
- }
- @end
XKBGRunManager.h
- #import <Foundation/Foundation.h>
-
- @interface XKBGRunManager : NSObject
- + (XKBGRunManager *)sharedManager;
- /**
- 开启后台运行
- */
- - (void)startBGRun;
- /**
- 关闭后台运行
- */
- - (void)stopBGRun;
- @end
XKBGRunManager.m
- #import "XKBGRunManager.h"
- ///循环时间
- static NSInteger _circulaDuration = 60;
- static XKBGRunManager *_sharedManger;
- @interface XKBGRunManager()
- @property (nonatomic,assign) UIBackgroundTaskIdentifier task;
- ///后台播放
- @property (nonatomic,strong) AVAudioPlayer *playerBack;
- @property (nonatomic, strong) NSTimer *timerAD;
- ///用来打印测试
- @property (nonatomic, strong) NSTimer *timerLog;
- @property (nonatomic,assign) NSInteger count;
- @end
- @implementation XKBGRunManager{
- CFRunLoopRef _runloopRef;
- dispatch_queue_t _queue;
- }
- + (XKBGRunManager *)sharedManager{
- static dispatch_once_t onceRunSingle;
- dispatch_once(&onceRunSingle, ^{
- if (!_sharedManger) {
- _sharedManger = [[XKBGRunManager alloc]init];
- }
- });
- return _sharedManger;
- }
- /// 重写init方法,初始化音乐文件
- - (instancetype)init {
- if (self = [super init]) {
- [self setupAudioSession];
- _queue = dispatch_queue_create("com.audio.inBackground", NULL);
- //静音文件
- NSString *filePath = [[NSBundle mainBundle] pathForResource:@"****" ofType:@"mp3"];
- NSURL *fileURL = [[NSURL alloc] initFileURLWithPath:filePath];
- self.playerBack = [[AVAudioPlayer alloc] initWithContentsOfURL:fileURL error:nil];
- [self.playerBack prepareToPlay];
- // 0.0~1.0,默认为1.0
- self.playerBack.volume = 0.01;
- // 循环播放
- self.playerBack.numberOfLoops = -1;
- }
- return self;
- }
- - (void)setupAudioSession {
- // 新建AudioSession会话
- AVAudioSession *audioSession = [AVAudioSession sharedInstance];
- // 设置后台播放
- NSError *error = nil;
- [audioSession setCategory:AVAudioSessionCategoryPlayback withOptions:AVAudioSessionCategoryOptionMixWithOthers error:&error];
- if (error) {
- NSLog(@"Error setCategory AVAudioSession: %@", error);
- }
- NSLog(@"%d", audioSession.isOtherAudioPlaying);
- NSError *activeSetError = nil;
- // 启动AudioSession,如果一个前台app正在播放音频则可能会启动失败
- [audioSession setActive:YES error:&activeSetError];
- if (activeSetError) {
- NSLog(@"Error activating AVAudioSession: %@", activeSetError);
- }
- }
- /**
- 启动后台运行
- */
- - (void)startBGRun{
- [self.playerBack play];
- [self applyforBackgroundTask];
- ///确保两个定时器同时进行
- dispatch_async(_queue, ^{
- self.timerLog = [[NSTimer alloc] initWithFireDate:[NSDate date] interval:1 target:self selector:@selector(log) userInfo:nil repeats:YES];
- self.timerAD = [[NSTimer alloc] initWithFireDate:[NSDate date] interval:_circulaDuration target:self selector:@selector(startAudioPlay) userInfo:nil repeats:YES];
- _runloopRef = CFRunLoopGetCurrent();
- [[NSRunLoop currentRunLoop] addTimer:self.timerAD forMode:NSDefaultRunLoopMode];
- [[NSRunLoop currentRunLoop] addTimer:self.timerLog forMode:NSDefaultRunLoopMode];
- CFRunLoopRun();
- });
- }
- /**
- 申请后台
- */
- - (void)applyforBackgroundTask{
- _task =[[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
- dispatch_async(dispatch_get_main_queue(), ^{
- [[UIApplication sharedApplication] endBackgroundTask:_task];
- _task = UIBackgroundTaskInvalid;
- });
- }];
- }
- /**
- 打印
- */
- - (void)log{
- _count = _count + 1;
- NSLog(@"_count = %ld",_count)
- }
- /**
- 检测后台运行时间
- */
- - (void)startAudioPlay{
- _count = 0;
- dispatch_async(dispatch_get_main_queue(), ^{
- if ([[UIApplication sharedApplication] backgroundTimeRemaining] < 61.0) {
- NSLog(@"后台快被杀死了");
- [self.playerBack play];
- [self applyforBackgroundTask];
- }
- else{
- NSLog(@"后台继续活跃呢");
- }///再次执行播放器停止,后台一直不会播放音乐文件
- [self.playerBack stop];
- });
- }
- /**
- 停止后台运行
- */
- - (void)stopBGRun{
- if (self.timerAD) {
- CFRunLoopStop(_runloopRef);
- [self.timerLog invalidate];
- self.timerLog = nil;
- // 关闭定时器即可
- [self.timerAD invalidate];
- self.timerAD = nil;
- [self.playerBack stop];
- }
- if (_task) {
- [[UIApplication sharedApplication] endBackgroundTask:_task];
- _task = UIBackgroundTaskInvalid;
- }
- }
- @end
在 AppDelegate.m 监控程序挂起的方法中:
- /// 后台运行
- - (void)applicationDidEnterBackground:(UIApplication *)application {
- NSLog(@"程序挂起了");
- [XKTelManager sharedManager].inBackgroundRun = YES;
- [[XKBGRunManager sharedManager] startBGRun];
- }
在 AppDelegate.m 监控回到APP的方法中:
- /// 回到APP
- - (void)applicationWillEnterForeground:(UIApplication *)application {
- [XKTelManager sharedManager].inBackgroundRun = NO;
- [[XKBGRunManager sharedManager] stopBGRun];
- }