我发现这都是关于在 AppDelegate applicationDidEnterBackground: 中停用 AVAudioSession 的,但通常它会因错误而失败(没有停用效果):
Error Domain=NSOSStatusErrorDomain Code=560030580 "The operation couldn’t be completed. (OSStatus error 560030580.)
这仍然会导致此处描述的崩溃:Spritekit crash when entering background。
因此,仅 setActive:NO 是不够的 - 我们必须有效地停用它(没有那个错误)。我通过向 AppDelegate 添加专用实例方法来制作一个简单的解决方案,只要没有错误,它就会停用 AVAudioSession。
简而言之,它看起来像这样:
- (void)applicationDidEnterBackground:(UIApplication *)application {
NSLog(@"%s", __FUNCTION__);
[self stopAudio];
}
- (void)stopAudio {
NSError *error = nil;
[[AVAudioSession sharedInstance] setActive:NO error:&error];
NSLog(@"%s AudioSession Error: %@", __FUNCTION__, error);
if (error) [self stopAudio];
}
NSLog 证明:
2014-01-25 11:41:48.426 MyApp[1957:60b] -[ATWAppDelegate applicationDidEnterBackground:]
2014-01-25 11:41:48.431 MyApp[1957:60b] -[ATWAppDelegate stopAudio] AudioSession Error: Error Domain=NSOSStatusErrorDomain Code=560030580 "The operation couldn’t be completed. (OSStatus error 560030580.)"
2014-01-25 11:41:48.434 MyApp[1957:60b] -[ATWAppDelegate stopAudio] AudioSession Error: Error Domain=NSOSStatusErrorDomain Code=560030580 "The operation couldn’t be completed. (OSStatus error 560030580.)"
2014-01-25 11:41:48.454 MyApp[1957:60b] -[ATWAppDelegate stopAudio] AudioSession Error: Error Domain=NSOSStatusErrorDomain Code=560030580 "The operation couldn’t be completed. (OSStatus error 560030580.)"
2014-01-25 11:41:49.751 MyApp[1957:60b] -[ATWAppDelegate stopAudio] AudioSession Error: (null)
这真的很短,因为它不关心 stackoverflow :) 如果 AVAudioSession 不想在数千次尝试后关闭(崩溃也是不可避免的)。因此,在 Apple 修复它之前,这只能被视为一种 hack。顺便说一句,控制启动 AVAudioSession 也是值得的。
完整的解决方案可能如下所示:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
NSLog(@"%s", __FUNCTION__);
[self startAudio];
return YES;
}
- (void)applicationDidEnterBackground:(UIApplication *)application {
NSLog(@"%s", __FUNCTION__);
// SpriteKit uses AVAudioSession for [SKAction playSoundFileNamed:]
// AVAudioSession cannot be active while the application is in the background,
// so we have to stop it when going in to background
// and reactivate it when entering foreground.
// This prevents audio crash.
[self stopAudio];
}
- (void)applicationWillEnterForeground:(UIApplication *)application {
NSLog(@"%s", __FUNCTION__);
[self startAudio];
}
- (void)applicationWillTerminate:(UIApplication *)application {
NSLog(@"%s", __FUNCTION__);
[self stopAudio];
}
static BOOL isAudioSessionActive = NO;
- (void)startAudio {
AVAudioSession *audioSession = [AVAudioSession sharedInstance];
NSError *error = nil;
if (audioSession.otherAudioPlaying) {
[audioSession setCategory: AVAudioSessionCategoryAmbient error:&error];
} else {
[audioSession setCategory: AVAudioSessionCategorySoloAmbient error:&error];
}
if (!error) {
[audioSession setActive:YES error:&error];
isAudioSessionActive = YES;
}
NSLog(@"%s AVAudioSession Category: %@ Error: %@", __FUNCTION__, [audioSession category], error);
}
- (void)stopAudio {
// Prevent background apps from duplicate entering if terminating an app.
if (!isAudioSessionActive) return;
AVAudioSession *audioSession = [AVAudioSession sharedInstance];
NSError *error = nil;
[audioSession setActive:NO error:&error];
NSLog(@"%s AVAudioSession Error: %@", __FUNCTION__, error);
if (error) {
// It's not enough to setActive:NO
// We have to deactivate it effectively (without that error),
// so try again (and again... until success).
[self stopAudio];
} else {
isAudioSessionActive = NO;
}
}
然而,与 SpriteKit app 中的 AVAudioSession 中断相比,这个问题是小菜一碟。如果我们不处理它,迟早我们会遇到内存泄漏和 CPU 99% 的大麻烦(56% 来自 [SKSoundSource queuedBufferCount] 和 34% 来自 [SKSoundSource isPlaying] - 请参阅 Instruments),因为 SpriteKit 很顽固并且“播放”的声音,即使它们无法播放 :)
据我发现,最简单的方法是 setCategory:AVAudioSessionCategoryPlayAndRecord withOptions:AVAudioSessionCategoryOptionMixWithOthers。我认为,任何其他 AVAudioSession 类别都需要完全避免 playFileNamed:。这可以通过制作您自己的 SKNode runAction: 用于播放声音方法的类别来完成,例如使用 AVAudioPlayer。但这是单独的话题。
我与 AVAudioPlayer 实施的完整的一体化解决方案在这里:http: //iknowsomething.com/ios-sdk-spritekit-sound/
编辑:修复了缺少的括号。