前言
目前在iOS中监听耳机插拔通常使用的方式是利用iOS系统提供的耳机通知事件 AVAudioSessionRouteChangeNotification 来实现。代码结构如下
系统通知方式
//添加观察消息
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(audioRouteChangeListenerCallback:) name:AVAudioSessionRouteChangeNotification object:nil];
//实现监听方法
- (void)audioRouteChangeListenerCallback:(NSNotification*)notification {
NSDictionary *interuptionDict = notification.userInfo;
NSInteger routeChangeReason = [[interuptionDict valueForKey:AVAudioSessionRouteChangeReasonKey] integerValue];
switch (routeChangeReason) {
case AVAudioSessionRouteChangeReasonNewDeviceAvailable: {//耳机插入
[UIView animateWithDuration:1 animations:^{
self.warningLabel.alpha = 0;
}];
}
break;
case AVAudioSessionRouteChangeReasonOldDeviceUnavailable: {//耳机拔出
[UIView animateWithDuration:1 animations:^{
self.warningLabel.alpha = 1;
}];
}
break;
case AVAudioSessionRouteChangeReasonCategoryChange:
// called at start - also when other audio wants to play
NSLog(@"AVAudioSessionRouteChangeReasonCategoryChange");
break;
}
}
这个方式的好处是结构清晰,代码量少。弊端是通知有时候速度比较慢,反应不够迅速,而且自己不能控制。其实监听的本质就是发起一个死循环,不停的测试耳机孔的状态。所以可以在自己的程序里面起一个线程,然后生成一个Runloop,不停的去探测耳机孔的状态。这种方式在AFNetworking框架里面也使过。为了不停的侦测网络状态,AFNetworking就是发起了一个自己的Runloop线程,来监听网络状态。
自定义方式
首先定义一个函数,用来判断耳机孔是否插有耳机。将来在自定义的线程里面,就是不停的调用这个函数
//判断耳机孔是否插有耳机
- (BOOL)isHeadsetPluggedIn {
AVAudioSessionRouteDescription* route = [[AVAudioSession sharedInstance] currentRoute];
for (AVAudioSessionPortDescription* desc in [route outputs]) {
if ([[desc portType] isEqualToString:AVAudioSessionPortHeadphones])
return YES;
}
return NO;
}
定义一个静态变量,用来保存RunLoop对象。定义为静态变量,就不要考虑这个变量的生命周期了。
static NSRunLoop *_HeadsetRunLoop;
定义侦测耳机的线程
- (NSThread *)startHeadsetThread {
static NSThread *_HeadsetThread = nil;
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
_HeadsetThread =
[[NSThread alloc] initWithTarget:self
selector:@selector(HeadsetThreadEntryPoint:)
object:nil];
[_HeadsetThread start];
});
return _HeadsetThread;
}
// 生成一个一直运行的RunLoop
- (void)HeadsetThreadEntryPoint:(id)__unused object {
@autoreleasepool {
[[NSThread currentThread] setName:@"com.olami.infraredTV"];
_HeadsetRunLoop = [NSRunLoop currentRunLoop];
[_HeadsetRunLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
[_HeadsetRunLoop run];
}
}
在这里使用NSMachPort来作为RunLoop的输入源,但是这个输入源什么也不做,作用就是让RunLoop一直活着,不会执行一次就退出。这个函数是整个方法实现的核心代码。
//利用自定义的线程侦测耳机
//利用自定义的线程侦测耳机
- (void)startDetectHeadset {
if (_HeadsetRunLoop) {
[self.timer invalidate];
_timer = [NSTimer timerWithTimeInterval:0.5 target:self selector:@selector(detectHeadset) userInfo:nil repeats:YES];
[_HeadsetRunLoop addTimer:_timer forMode:NSRunLoopCommonModes];
}else{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
if (_HeadsetRunLoop) {
[self.timer invalidate];
_timer = [NSTimer timerWithTimeInterval:0.5 target:self selector:@selector(detectHeadset) userInfo:nil repeats:YES];
[_HeadsetRunLoop addTimer:_timer forMode:NSRunLoopCommonModes];
}
});
}
}
在这里使用了一个定时器来监听耳机孔的插拔状态。如果想更迅速,可以使用CADisplayLink来实现。
把定时器的RunLoop设置为自定义的就行了
//耳机侦测回调函数
- (void)detectHeadset {
if ([self isHeadsetPluggedIn]) {
NSLog(@"耳机已经插入");
}else{
NSLog(@"耳机已经拔出");
}
}
当然了,这样实现还是把轮子造了一边。但是起码给了一个思路,当自己的APP需要监听一些设备或者变量的状态的时候。可以使用这种方法来实现。