iPhone 接近感应器的坑及解决方案

原文发布于我的博客:http://blog.zyliu.com/ios-proximity-state-bug-and-solution/

实习的时候写公司产品,有个用到接近感应器的功能。就比如打电话,电话接通时开启接近感应器,侦测到接近状态改变(接近/离开)时执行相应的操作——当开启接近感应器时,系统会在接近时熄灭屏幕,离开时再点亮屏幕,等等。但是在这个过程中有bug存在,导致系统接口给的结果不一定是准确的。

先说理论上的实现

UIKit/UIDevice.h 中的 UIDevice 类(iOS 8.4 SDK),有如下属性

@property(nonatomic,getter=isProximityMonitoringEnabled) BOOL proximityMonitoringEnabled NS_AVAILABLE_IOS(3_0); // default is NO
@property(nonatomic,readonly)                            BOOL proximityState NS_AVAILABLE_IOS(3_0);  // always returns NO if no proximity detector

proximityMonitoringEnabled 用来标识是否开启接近感应器,如果为 YES 则开启。proximityState 为当前的接近状态,如果为 YES 则为接近(触发),否则为离开(未触发),需要的时候可以直接拿来用。

以及用于 UINotificationCenter 的键:

UIKIT_EXTERN NSString *const UIDeviceProximityStateDidChangeNotification NS_AVAILABLE_IOS(3_0);

于是如果需要在某个地方使用接近感应器,可以注册通知并且开启接近感应器:

UIDevice *device = [UIDevice currentDevice];
[device setProximityMonitoringEnabled:YES];
if ([device isProximityMonitoringEnabled]) {
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(proximityStateDidChange:)
                                                 name:UIDeviceProximityStateDidChangeNotification object:nil];
}else {
    NSLog(@"No Proximity Sensor");
}

然后在 -(void)proximityStateDidChange:(BOOL) 方法里面实现需要做的处理。当然记得在不需要的时候取消注册通知、停止接近检测。

这样看起来是没问题的。

突然问题来了

直到有一天发现了一个问题:在已经开启接近检测的情况下,同时触发接近感应器和进入后台(按 Home 键的同时捂住听筒),这样会在熄灭屏幕的情况下进入了后台。手离开听筒,屏幕再次点亮。再进入前台,发现 [UIDevice currentDevice].proximityState 的值为 YES 。也就是说,触发接近感应器的同时进入后台,在后台时离开接近感应器是不会刷新接近状态的(会保持在触发状态)。在这种情况下,系统提供的接口结果不正确。经验证,从 iOS 7iOS 9 都存在这个问题。只有再次触发接近感应器并离开时,才会收到 UIDeviceProximityStateDidChangeNotification 通知,也就是变回正常了。这样看来,UIDeviceproximityState 属性也是依赖于上面的通知更新吧。

解决方案

好了,既然 iOS 留下了这个 Bug,下面就是如何想办法解决它。对于接近感应器,我们需要的很简单,就是在任何情况下拿到的数据都是真实有效的。有这样一种思路:每次从后台进入前台时,是用户在屏幕亮着并且可操作的情况下进来的,进来之前的瞬间不可能是接近状态。所以在进入前台后到收到接近状态改变的通知前的这段时间,可以推测是非接近状态。

在这样的思路下,我们就可以做一个简单的接近检测的工具类,添加一个 proximityState 属性,将 get 方法写为:

- (BOOL)proximityState{
    if (self.isWaitingProximityStateUpdate) {
        return NO;
    }else {
        return [[UIDevice currentDevice] proximityState];
    }
}

其中,isWaitingProximityStateUpdate 表示是否为进入前台后到收到接近状态改变通知前的这段时间。每次进入前台就把这个属性置为 YES,收到接近状态改变通知就置为 NO

这样,通过这个工具类的 proximityState 属性,在任何情况下拿到的结果都是真实有效的。

你可能感兴趣的:(iPhone 接近感应器的坑及解决方案)