iOS-實現後臺長時間運行

前言

通常APP在按下Home鍵被掛起後,這時APP的 backgroundTimeRemaining 也就是後臺運行時間大約只有3分鐘,若是在退出APP後,過十幾二十二分鐘或者更長時間再回到APP,APP就會回到剛打開時的狀態,也就是首頁;有的項目在被掛起後須要在後臺運行一段時間,使有足夠的時間來完成與服務器對接的操做,或者須要一直運行的需求;若是須要,則在APP被掛起後,申請後臺,來延長後臺運行時間。服務器

APP申請後臺運行的方式有幾種:app

播放音樂async

定位 oop

Newsstand downloads測試

fetchfetch

這裏主要說下後臺播放無聲音樂(實際上是不播放),採用哪一種方式,對應勾選上圖;個人項目中有音頻播放需求,若是沒有,那就找一個播放音頻的理由,或者用其餘方式實現。atom

實現

這裏採用了兩個單例:電話監控(XKTelManager)、後臺運行(XKBGRunManager),電話監控能夠忽略,視狀況而用;採用單例是爲了方便管理;spa

XKTelManager.hcode

#import <Foundation/Foundation.h>

@interface XKTelManager : NSObject
///是否在後臺運行
@property (nonatomic,assign) BOOL inBackgroundRun;
+ (XKTelManager *)sharedManager;
/**
 來電監聽
 */
- (void)startMonitor;
@end

XKTelManager.mblog

#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 對應的方法中,實現開啓和中止後臺運行便可!

相關文章
相關標籤/搜索