为了账号安全,请及时绑定邮箱和手机立即绑定

iOS-实现后台长时间运行

标签:
iOS

前言

一般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 对应的方法中,实现开启和停止后台运行即可!

作者:macro小K
出处:http://www.cnblogs.com/wangkejia/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,请在文章页面明显位置给出原文连接。


点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消