前言
目前在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需要監聽一些裝置或者變數的狀態的時候。可以使用這種方法來實現。