zoukankan      html  css  js  c++  java
  • 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 对应的方法中,实现开启和停止后台运行即可!

  • 相关阅读:
    VMware虚拟机下实现NAT方式上网的小方法
    文件附件上传
    [转] 深入了解 HTML 5
    用JS获取图片尺寸
    Stream与byte的转换
    递归算法
    搞懂java中的synchronized关键字
    初学UML之用例图
    浅析tomcat nio 配置
    面向对象:单一任务原则(SRP)
  • 原文地址:https://www.cnblogs.com/wangkejia/p/9773285.html
Copyright © 2011-2022 走看看