beginReceivingRemoteControlEvents 和 endReceivingRemoteControlEvents 的调用时机
这两个方法需要在特定的生命周期节点调用,以确保远程控制功能正常工作且不会浪费系统资源。以下是详细的调用时机指南:
- 音频播放应用的标准调用时机
播放开始时调用 begin
- (void)playAudio {
// 1. 设置音频会话
AVAudioSession *session = [AVAudioSession sharedInstance];
[session setCategory:AVAudioSessionCategoryPlayback error:nil];
[session setActive:YES error:nil];
// 2. 开始播放前注册远程控制
[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
[self becomeFirstResponder];
// 3. 开始播放
[self.audioPlayer play];
// 4. 更新锁屏界面信息
[self updateNowPlayingInfo];
}
播放停止时调用 end
- (void)stopAudio {
// 1. 停止播放
[self.audioPlayer stop];
// 2. 停止接收远程控制事件
[[UIApplication sharedApplication] endReceivingRemoteControlEvents];
[self resignFirstResponder];
// 3. 清理锁屏界面信息
[[MPNowPlayingInfoCenter defaultCenter] setNowPlayingInfo:nil];
}
- ViewController 生命周期中的调用
视图控制器示例
@interface AudioPlayerViewController : UIViewController
@property (nonatomic, assign) BOOL isPlaying;
@end
@implementation AudioPlayerViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self setupAudioSession];
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
// 如果进入页面时正在播放,注册远程控制
if (self.isPlaying) {
[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
[self becomeFirstResponder];
}
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
// 离开页面时,如果不是正在播放,停止接收
if (!self.isPlaying) {
[[UIApplication sharedApplication] endReceivingRemoteControlEvents];
[self resignFirstResponder];
}
}
- (void)dealloc {
// 确保对象销毁时清理资源
[[UIApplication sharedApplication] endReceivingRemoteControlEvents];
}
@end
- AppDelegate 中的应用状态管理
应用生命周期回调
- (void)applicationDidBecomeActive:(UIApplication *)application {
// 应用进入前台
if ([self.audioManager isPlaying]) {
// 如果正在播放,确保注册远程控制
[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
}
}
- (void)applicationWillResignActive:(UIApplication *)application {
// 应用进入后台
if (![self.audioManager isPlaying]) {
// 如果没有播放,停止接收以节省资源
[[UIApplication sharedApplication] endReceivingRemoteControlEvents];
}
}
- (void)applicationDidEnterBackground:(UIApplication *)application {
// 应用进入后台
if ([self.audioManager isPlaying]) {
// 后台播放时需要保持远程控制
// 注意:需要设置后台音频模式
[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
// 启动后台任务
__block UIBackgroundTaskIdentifier bgTask = [application beginBackgroundTaskWithExpirationHandler:^{
[application endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}];
}
}
- (void)applicationWillEnterForeground:(UIApplication *)application {
// 应用回到前台
if (![self.audioManager isPlaying]) {
// 如果没有播放,停止接收
[[UIApplication sharedApplication] endReceivingRemoteControlEvents];
}
}
- 具体场景的调用时机矩阵
| 场景 | beginReceivingRemoteControlEvents | endReceivingRemoteControlEvents |
|---|---|---|
| 开始播放音频 | ✅ 立即调用 | ❌ 不调用 |
| 暂停播放 | ❌ 不调用 | ⚠️ 可选调用(如果长时间暂停) |
| 停止播放 | ❌ 不调用 | ✅ 立即调用 |
| 切换歌曲 | ❌ 不调用(已注册) | ❌ 不调用 |
| 应用进入后台(正在播放) | ✅ 确保已调用 | ❌ 不调用 |
| 应用进入后台(未播放) | ❌ 不调用 | ✅ 确保已调用 |
| 应用回到前台(正在播放) | ✅ 确保已调用 | ❌ 不调用 |
| 应用回到前台(未播放) | ❌ 不调用 | ✅ 确保已调用 |
| 电话打断 | ❌ 不调用 | ✅ 建议调用 |
| 其他音频打断 | ❌ 不调用 | ⚠️ 根据业务逻辑决定 |
- 完整的最佳实践示例
音频管理器完整实现
@interface AudioManager : NSObject
@property (nonatomic, strong) AVPlayer *player;
@property (nonatomic, assign) BOOL isPlaying;
@property (nonatomic, assign) BOOL isBackground;
@end
@implementation AudioManager
- (instancetype)init {
self = [super init];
if (self) {
[self setupNotifications];
[self setupAudioSession];
}
return self;
}
- (void)setupAudioSession {
AVAudioSession *session = [AVAudioSession sharedInstance];
[session setCategory:AVAudioSessionCategoryPlayback error:nil];
[session setActive:YES error:nil];
}
- (void)setupNotifications {
// 监听应用状态变化
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(appDidEnterBackground:)
name:UIApplicationDidEnterBackgroundNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(appWillEnterForeground:)
name:UIApplicationWillEnterForegroundNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(audioSessionInterrupted:)
name:AVAudioSessionInterruptionNotification
object:nil];
}
#pragma mark - 播放控制
- (void)play {
self.isPlaying = YES;
// 关键时机1:播放开始时注册
[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
[self.player play];
[self updateNowPlayingInfo];
}
- (void)pause {
self.isPlaying = NO;
[self.player pause];
// 暂停时:如果暂停时间较长(如超过5分钟),建议停止接收
// 这里可以根据业务逻辑决定
if (self.pauseDuration > 5 * 60) {
[[UIApplication sharedApplication] endReceivingRemoteControlEvents];
}
}
- (void)stop {
self.isPlaying = NO;
[self.player stop];
// 关键时机2:停止播放时取消注册
[[UIApplication sharedApplication] endReceivingRemoteControlEvents];
[[MPNowPlayingInfoCenter defaultCenter] setNowPlayingInfo:nil];
}
#pragma mark - 应用状态处理
- (void)appDidEnterBackground:(NSNotification *)notification {
self.isBackground = YES;
if (self.isPlaying) {
// 后台播放:保持远程控制注册
// 不需要重复调用begin,只需确保已经调用过
} else {
// 关键时机3:进入后台且未播放时停止接收
[[UIApplication sharedApplication] endReceivingRemoteControlEvents];
}
}
- (void)appWillEnterForeground:(NSNotification *)notification {
self.isBackground = NO;
if (self.isPlaying) {
// 回到前台且正在播放:确保远程控制注册
[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
} else {
// 关键时机4:回到前台且未播放时停止接收
[[UIApplication sharedApplication] endReceivingRemoteControlEvents];
}
}
- (void)audioSessionInterrupted:(NSNotification *)notification {
NSInteger interruptionType = [notification.userInfo[AVAudioSessionInterruptionTypeKey] integerValue];
if (interruptionType == AVAudioSessionInterruptionTypeBegan) {
// 音频被打断(如来电)
if (self.isPlaying) {
// 关键时机5:音频打断时暂停播放并停止接收
[self pause];
[[UIApplication sharedApplication] endReceivingRemoteControlEvents];
}
}
else if (interruptionType == AVAudioSessionInterruptionTypeEnded) {
// 打断结束
// 可以根据需要恢复播放和远程控制
}
}
- (void)dealloc {
// 关键时机6:对象销毁时确保清理
[[UIApplication sharedApplication] endReceivingRemoteControlEvents];
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
@end
- 特殊情况处理
多页面音频应用
// 当音频播放分散在多个页面时
@interface AppDelegate : UIResponder <UIApplicationDelegate>
@property (nonatomic, strong) AudioManager *audioManager;
@end
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
self.audioManager = [AudioManager sharedInstance];
return YES;
}
- (void)applicationDidBecomeActive:(UIApplication *)application {
// 统一由AppDelegate管理远程控制状态
if ([self.audioManager isPlaying]) {
[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
} else {
[[UIApplication sharedApplication] endReceivingRemoteControlEvents];
}
}
// 其他页面只需要控制播放,不需要单独管理远程控制
@interface PlaylistViewController : UIViewController
- (void)playSong:(Song *)song {
[[AudioManager sharedInstance] playSong:song];
// 不需要单独调用beginReceivingRemoteControlEvents
}
@end
错误处理和安全调用
// 安全的远程控制管理
- (void)safeBeginReceivingRemoteControlEvents {
@try {
[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
} @catch (NSException *exception) {
NSLog(@"beginReceivingRemoteControlEvents 异常: %@", exception);
}
}
- (void)safeEndReceivingRemoteControlEvents {
@try {
[[UIApplication sharedApplication] endReceivingRemoteControlEvents];
} @catch (NSException *exception) {
NSLog(@"endReceivingRemoteControlEvents 异常: %@", exception);
}
}
总结
-
必须调用 beginReceivingRemoteControlEvents 的时机:
- 开始播放音频时
- 应用进入后台且正在播放音频时
- 应用回到前台且正在播放音频时
-
必须调用 endReceivingRemoteControlEvents 的时机:
- 完全停止播放音频时
- 应用进入后台且没有播放音频时
- 应用回到前台且没有播放音频时
- 音频被其他应用打断时
- 对象销毁时
最佳实践原则:
- 配对使用:begin 和 end 应该成对出现
- 状态同步:远程控制状态应与音频播放状态保持一致
- 资源管理:及时释放不需要的远程控制资源
- 统一管理:建议在单一位置集中管理远程控制状态