iOS监听耳机插拔的不使用系统通知实现的一种方式

前言

目前在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需要监听一些设备或者变量的状态的时候。可以使用这种方法来实现。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值