经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 移动开发 » iOS » 查看文章
iOS-实现后台长时间运行
来源:cnblogs  作者:macro小K  时间:2018/10/12 9:36:49  对本文有异议

前言

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

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

播放音乐

定位 

Newsstand downloads

fetch

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

实现

这里采用了两个单例:电话监控(XKTelManager)、后台运行(XKBGRunManager),电话监控可以忽略,视情况而用;采用单例是为了方便管理;

XKTelManager.h

  1. #import <Foundation/Foundation.h>
  2.  
  3. @interface XKTelManager : NSObject
  4. ///是否在后台运行
  5. @property (nonatomic,assign) BOOL inBackgroundRun;
  6. + (XKTelManager *)sharedManager;
  7. /**
  8. 来电监听
  9. */
  10. - (void)startMonitor;
  11. @end

XKTelManager.m

  1. #import "XKTelManager.h"
  2. #import "XKBGRunManager.h"
  3. #import <CoreTelephony/CTCallCenter.h>
  4. #import <CoreTelephony/CTCall.h>
  5.  
  6. static XKTelManager *_sharedManger;
  7. @interface XKTelManager()
  8. @property (nonatomic, strong) CTCallCenter *callCenter;
  9. @end
  10. @implementation XKTelManager
  11. + (XKTelManager *)sharedManager{
  12. static dispatch_once_t onceTelSingle;
  13. dispatch_once(&onceTelSingle, ^{
  14. if (!_sharedManger) {
  15. _sharedManger = [[XKTelManager alloc]init];
  16. }
  17. });
  18. return _sharedManger;
  19. }
  20. - (instancetype)init{
  21. self = [super init];
  22. if (self) {
  23. _inBackgroundRun = NO;
  24. }
  25. return self;
  26. }
  27. #pragma mark -********* 监听电话相关
  28. - (void)startMonitor {
  29. __weak typeof(self) weakSelf = self;
  30. _callCenter = [[CTCallCenter alloc] init];
  31. _callCenter.callEventHandler = ^(CTCall * call) {
  32. ///如果已经进入后台了,不做任何操作
  33. if (weakSelf.inBackgroundRun) {
  34. return;
  35. }
  36. ///APP未进入后台
  37. if ([call.callState isEqualToString:CTCallStateDisconnected]){
  38. NSLog(@"电话 --- 断开连接");
  39. [[XKBGRunManager sharedManager] stopBGRun];
  40. }
  41. else if ([call.callState isEqualToString:CTCallStateConnected]){
  42. NSLog(@"电话 --- 接通");
  43. }
  44. else if ([call.callState isEqualToString:CTCallStateIncoming]){
  45. NSLog(@"电话 --- 待接通");
  46. [[XKBGRunManager sharedManager] startBGRun];
  47. }
  48. else if ([call.callState isEqualToString:CTCallStateDialing]){
  49. NSLog(@"电话 --- 拨号中");
  50. [[XKBGRunManager sharedManager] startBGRun];
  51. }
  52. else {
  53. NSLog(@"电话 --- 无操作");
  54. }
  55. };
  56. }
  57. @end

XKBGRunManager.h

  1. #import <Foundation/Foundation.h>
  2.  
  3. @interface XKBGRunManager : NSObject
  4. + (XKBGRunManager *)sharedManager;
  5. /**
  6. 开启后台运行
  7. */
  8. - (void)startBGRun;
  9. /**
  10. 关闭后台运行
  11. */
  12. - (void)stopBGRun;
  13. @end

XKBGRunManager.m

  1. #import "XKBGRunManager.h"
  2. ///循环时间
  3. static NSInteger _circulaDuration = 60;
  4. static XKBGRunManager *_sharedManger;
  5. @interface XKBGRunManager()
  6. @property (nonatomic,assign) UIBackgroundTaskIdentifier task;
  7. ///后台播放
  8. @property (nonatomic,strong) AVAudioPlayer *playerBack;
  9. @property (nonatomic, strong) NSTimer *timerAD;
  10. ///用来打印测试
  11. @property (nonatomic, strong) NSTimer *timerLog;
  12. @property (nonatomic,assign) NSInteger count;
  13. @end
  14. @implementation XKBGRunManager{
  15. CFRunLoopRef _runloopRef;
  16. dispatch_queue_t _queue;
  17. }
  18. + (XKBGRunManager *)sharedManager{
  19. static dispatch_once_t onceRunSingle;
  20. dispatch_once(&onceRunSingle, ^{
  21. if (!_sharedManger) {
  22. _sharedManger = [[XKBGRunManager alloc]init];
  23. }
  24. });
  25. return _sharedManger;
  26. }
  27. /// 重写init方法,初始化音乐文件
  28. - (instancetype)init {
  29. if (self = [super init]) {
  30. [self setupAudioSession];
  31. _queue = dispatch_queue_create("com.audio.inBackground", NULL);
  32. //静音文件
  33. NSString *filePath = [[NSBundle mainBundle] pathForResource:@"****" ofType:@"mp3"];
  34. NSURL *fileURL = [[NSURL alloc] initFileURLWithPath:filePath];
  35. self.playerBack = [[AVAudioPlayer alloc] initWithContentsOfURL:fileURL error:nil];
  36. [self.playerBack prepareToPlay];
  37. // 0.0~1.0,默认为1.0
  38. self.playerBack.volume = 0.01;
  39. // 循环播放
  40. self.playerBack.numberOfLoops = -1;
  41. }
  42. return self;
  43. }
  44. - (void)setupAudioSession {
  45. // 新建AudioSession会话
  46. AVAudioSession *audioSession = [AVAudioSession sharedInstance];
  47. // 设置后台播放
  48. NSError *error = nil;
  49. [audioSession setCategory:AVAudioSessionCategoryPlayback withOptions:AVAudioSessionCategoryOptionMixWithOthers error:&error];
  50. if (error) {
  51. NSLog(@"Error setCategory AVAudioSession: %@", error);
  52. }
  53. NSLog(@"%d", audioSession.isOtherAudioPlaying);
  54. NSError *activeSetError = nil;
  55. // 启动AudioSession,如果一个前台app正在播放音频则可能会启动失败
  56. [audioSession setActive:YES error:&activeSetError];
  57. if (activeSetError) {
  58. NSLog(@"Error activating AVAudioSession: %@", activeSetError);
  59. }
  60. }
  61. /**
  62. 启动后台运行
  63. */
  64. - (void)startBGRun{
  65. [self.playerBack play];
  66. [self applyforBackgroundTask];
  67. ///确保两个定时器同时进行
  68. dispatch_async(_queue, ^{
  69. self.timerLog = [[NSTimer alloc] initWithFireDate:[NSDate date] interval:1 target:self selector:@selector(log) userInfo:nil repeats:YES];
  70. self.timerAD = [[NSTimer alloc] initWithFireDate:[NSDate date] interval:_circulaDuration target:self selector:@selector(startAudioPlay) userInfo:nil repeats:YES];
  71. _runloopRef = CFRunLoopGetCurrent();
  72. [[NSRunLoop currentRunLoop] addTimer:self.timerAD forMode:NSDefaultRunLoopMode];
  73. [[NSRunLoop currentRunLoop] addTimer:self.timerLog forMode:NSDefaultRunLoopMode];
  74. CFRunLoopRun();
  75. });
  76. }
  77. /**
  78. 申请后台
  79. */
  80. - (void)applyforBackgroundTask{
  81. _task =[[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
  82. dispatch_async(dispatch_get_main_queue(), ^{
  83. [[UIApplication sharedApplication] endBackgroundTask:_task];
  84. _task = UIBackgroundTaskInvalid;
  85. });
  86. }];
  87. }
  88. /**
  89. 打印
  90. */
  91. - (void)log{
  92. _count = _count + 1;
  93. NSLog(@"_count = %ld",_count)
  94. }
  95. /**
  96. 检测后台运行时间
  97. */
  98. - (void)startAudioPlay{
  99. _count = 0;
  100. dispatch_async(dispatch_get_main_queue(), ^{
  101. if ([[UIApplication sharedApplication] backgroundTimeRemaining] < 61.0) {
  102. NSLog(@"后台快被杀死了");
  103. [self.playerBack play];
  104. [self applyforBackgroundTask];
  105. }
  106. else{
  107. NSLog(@"后台继续活跃呢");
  108. }///再次执行播放器停止,后台一直不会播放音乐文件
  109. [self.playerBack stop];
  110. });
  111. }
  112. /**
  113. 停止后台运行
  114. */
  115. - (void)stopBGRun{
  116. if (self.timerAD) {
  117. CFRunLoopStop(_runloopRef);
  118. [self.timerLog invalidate];
  119. self.timerLog = nil;
  120. // 关闭定时器即可
  121. [self.timerAD invalidate];
  122. self.timerAD = nil;
  123. [self.playerBack stop];
  124. }
  125. if (_task) {
  126. [[UIApplication sharedApplication] endBackgroundTask:_task];
  127. _task = UIBackgroundTaskInvalid;
  128. }
  129. }
  130. @end

在 AppDelegate.m 监控程序挂起的方法中:

  1. /// 后台运行
  2. - (void)applicationDidEnterBackground:(UIApplication *)application {
  3. NSLog(@"程序挂起了");
  4. [XKTelManager sharedManager].inBackgroundRun = YES;
  5. [[XKBGRunManager sharedManager] startBGRun];
  6. }

在 AppDelegate.m 监控回到APP的方法中:

  1. /// 回到APP
  2. - (void)applicationWillEnterForeground:(UIApplication *)application {
  3. [XKTelManager sharedManager].inBackgroundRun = NO;
  4. [[XKBGRunManager sharedManager] stopBGRun];
  5. }

 

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号