欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

iOS-实现后台长时间运行

程序员文章站 2022-05-18 22:29:10
前言 一般APP在按下Home键被挂起后,这时APP的 backgroundTimeRemaining 也就是后台运行时间大约只有3分钟,如果在退出APP后,过十几二十二分钟或者更长时间再回到APP,APP就会回到刚打开时的状态,也就是首页;有的项目在被挂起后需要在后台运行一段时间,使有足够的时间来 ......

前言

一般app在按下home键被挂起后,这时app的 backgroundtimeremaining 也就是后台运行时间大约只有3分钟,如果在退出app后,过十几二十二分钟或者更长时间再回到app,app就会回到刚打开时的状态,也就是首页;有的项目在被挂起后需要在后台运行一段时间,使有足够的时间来完成与服务器对接的操作,或者需要一直运行的需求;如果需要,则在app被挂起后,申请后台,来延长后台运行时间。

app申请后台运行的方式有几种:

播放音乐

定位 

newsstand downloads

fetch

iOS-实现后台长时间运行

这里主要说下后台播放无声音乐(其实是不播放),采用哪种方式,对应勾选上图;我的项目中有音频播放需求,如果没有,那就找一个播放音频的理由,或者用其他方式实现。

实现

这里采用了两个单例:电话监控(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];
}