应用程序在后台时中断后恢复执行代码

Posted

技术标签:

【中文标题】应用程序在后台时中断后恢复执行代码【英文标题】:Resuming execution of code after interruption while app is in background 【发布时间】:2012-05-02 02:16:51 【问题描述】:

我花了几天时间在 SO 和其他网站上研究这个问题的答案,但没有任何运气。

基本上,我为自己设定的挑战是为 ios 创建一个无论用户在哪里(前台或后台)都能发出声音的闹钟应用。我已经通过使用 AVAudioPlayer 实例并在用户设置警报时开始播放空声音文件以使应用程序继续在后台运行来实现这一点。当警报响起时(即当 NSTimer 被触发时),已经启动并准备播放的第二个播放器开始播放用户醒来的铃声。

此外,我还通过实现 AVAudioSessionDelegate 方法 beginInterruption 和 endInterruptionWithFlags 来处理电话、系统计时器或闹钟的中断。它可以在后台和前台模式下工作,但最奇怪的事情发生了:

当中断结束时,AVAudioPlayer 会继续播放,但我无法在我的应用中执行任何其他代码,除非我再次将应用置于前台。

为了深入了解这一点,我已经尝试了一个更简单的项目,我将在下面发布。 这个应用程序所做的是,一旦您进入应用程序,AVAudioPlayer 类的一个实例就会开始循环播放某种声音。然后当你把它带到后台时,播放器会继续循环播放声音。当发生中断时,我暂停播放器,当它结束时,我使用调度等待几秒钟,然后它调用两个方法,即 (void)playPlayer,一个包含恢复播放文件的代码的方法和 (void)tester ,一种包含计时器的方法,该计时器设置为在中断结束后 5 秒(或准确地说是 7 秒)停止播放器。两种方法都被调用,正如我在它们中放入的 NSLogs 所示,但计时器永远不会被触发,玩家会继续无限期地玩。

这是 .h 文件的代码:

#import <UIKit/UIKit.h>
#import <AVFoundation/AVFoundation.h>
#import <AudioToolbox/AudioToolbox.h>

@interface InterruptionTest3ViewController : UIViewController <AVAudioSessionDelegate,      AVAudioPlayerDelegate>

    AVAudioSession *mySession;
    AVAudioPlayer *myPlayer;


-(void) playPlayer;
-(void) pausePlayer;
-(void) tester;

@end

这是 .m 文件的代码:

#import "InterruptionTest3ViewController.h"

@interface InterruptionTest3ViewController ()

@end

@implementation InterruptionTest3ViewController

- (void)viewDidLoad

    [super viewDidLoad];
    mySession = [AVAudioSession sharedInstance];
    NSError *setActiveError = nil;
    [mySession setActive:YES withFlags:AVAudioSessionSetActiveFlags_NotifyOthersOnDeactivation error:&setActiveError];
    if (setActiveError) 
        NSLog(@"Session failed to activate within viewDidLoad");
    
    else 
        NSLog(@"Session was activated within viewDidLoad");
    
    NSError *setCategoryError = nil;
    [mySession setCategory:AVAudioSessionCategoryPlayback error:&setCategoryError];
    if (setCategoryError) 
        NSLog(@"Category failed to be set");
    
    else 
        NSLog(@"Category has been set");
    

    [mySession setDelegate:self];


    NSString *path = [[NSBundle mainBundle] pathForResource:@"headspin" ofType:@"wav"];
    NSError *initMyPlayerError = nil;
    myPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:path]     error:&initMyPlayerError];
    if (initMyPlayerError) 
        NSLog(@"myPlayer failed to initiate");
    
    else 
        NSLog(@"myPlayer has been initiated");
    

    [myPlayer prepareToPlay];
    [self playPlayer];

    OSStatus propertySetError = 0;
    UInt32 allowMixing = true;

    propertySetError = AudioSessionSetProperty (
                                                kAudioSessionProperty_OverrideCategoryMixWithOthers, 
                                                sizeof (allowMixing),                                 
                                                &allowMixing                                          
                                                );

    [myPlayer setNumberOfLoops:-1];
    [[UIApplication sharedApplication] beginReceivingRemoteControlEvents];


-(void) beginInterruption

    [myPlayer pause];


-(void) endInterruptionWithFlags:(NSUInteger)flags

    if (flags) 
        if (AVAudioSessionInterruptionFlags_ShouldResume) 
        
            
                dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC),dispatch_get_main_queue(), ^

                    [self playPlayer];
                    [self tester];
                );

        
    


-(void) tester

    [NSTimer timerWithTimeInterval:5.0 target:self selector:@selector(pausePlayer) userInfo:nil repeats:NO];
    NSLog(@"tester method has been called");


-(void) playPlayer

    [NSTimer timerWithTimeInterval:5.0 target:myPlayer selector:@selector(stop) userInfo:nil repeats:NO];
    [myPlayer play];
    NSLog(@"playPlayer method has been called");


-(void) pausePlayer

    [myPlayer pause];




//viewDidUnload etc not listed.

所以,这就是伙计们。同样,为什么在应用程序处于后台时中断后计时器没有被触发?我需要在 applicationDidEnterBackground 方法中设置一些东西吗?

非常感谢您!

【问题讨论】:

【参考方案1】:

请使用“[NSTimer scheduledTimerWithTimeInterval:5.0 target:myPlayer selector:@selector(stop) userInfo:nil repeats:NO]”;

【讨论】:

感谢您的回复,永斌,但是我已经尝试过了,它并没有完成我正在寻找的任务。【参考方案2】:

不是为了回避这个问题,而是在后台播放空声音位是一个 hack。提供了在后台播放声音的功能,以便您可以播放音乐或其他声音以使用户受益,而不是保持后台进程处于活动状态。

如果您想在特定时间播放声音或以其他方式提醒用户,请考虑使用using local notifications。

为什么应用在后台时中断后定时器没有被触发?

如果我没记错的话,当您的应用进入后台时,计时器会被暂停或取消。文档明确指出,当您的应用程序被中断时,即当它被发送到后台时,您应该 "stop timers and other periodic tasks"。

【讨论】:

我理解您的担忧,Caleb,但据我所知,Apple 在警报应用程序方面往往更加宽容。使用 UILocalNotification 来提醒用户的问题可能会认为自己对我尝试创建的应用程序类型毫无用处。我对用户无法关闭或静音警报感兴趣,除非他们进入应用程序这样做。我不知道这是否是一个有价值的论点,但有些应用程序确实使用“空”播放器在后台任务中保留他们的位置,他们似乎已经侥幸逃脱了。示例:“Timegg”和“摇摇晃晃”。 其他一些应用程序这样做的事实并不意味着它不是黑客,他们进入应用商店的事实并不意味着您的应用程序也会这样做。用户应该负责应用程序,而不是相反;试图强迫用户做某事的应用程序设计得很糟糕。创建一个用户想要打开的有吸引力、有用的应用程序,而不是他们必须在设备在会议期间制造球拍时花费 10 秒或更长时间才能打开的应用程序,在剧院等场所。这样的体验会说服用户立即删除您的应用。 Timegg 一周前被 Apple 推荐在 appStore 上。如果您尝试更改,您可以通过设置警报、移动到后台以及从警报设置到警报响起之间的任何时刻来验证它是否使用“空声音播放”黑客(原文如此)音量,你改变的不是“铃声”音量,而是应用程序的音量。此外,他们成功地完成了我的问题,即在重大中断后在后台恢复执行。

以上是关于应用程序在后台时中断后恢复执行代码的主要内容,如果未能解决你的问题,请参考以下文章

在代码执行进入 Dll 或 Lib 空间时中断

linux进程的挂起和恢复

代码格式化 - 在规则被破坏时中断构建

在 iOS 中,后台提取多久执行一次?

转到后台后恢复应用程序

Eclipse - 在 Android 应用程序上引发未处理的异常时中断用户代码