原文發(fā)布于我的博客:http://blog.zyliu.com/ios-proximity-state-bug-and-solution/
實(shí)習(xí)的時(shí)候?qū)懝井a(chǎn)品,有個(gè)用到接近感應(yīng)器的功能。就比如打電話(huà),電話(huà)接通時(shí)開(kāi)啟接近感應(yīng)器,偵測(cè)到接近狀態(tài)改變(接近/離開(kāi))時(shí)執(zhí)行相應(yīng)的操作——當(dāng)開(kāi)啟接近感應(yīng)器時(shí),系統(tǒng)會(huì)在接近時(shí)熄滅屏幕,離開(kāi)時(shí)再點(diǎn)亮屏幕,等等。但是在這個(gè)過(guò)程中有bug存在,導(dǎo)致系統(tǒng)接口給的結(jié)果不一定是準(zhǔn)確的。
先說(shuō)理論上的實(shí)現(xiàn)
在 UIKit/UIDevice.h 中的 UIDevice 類(lèi)(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 用來(lái)標(biāo)識(shí)是否開(kāi)啟接近感應(yīng)器,如果為 YES 則開(kāi)啟。proximityState 為當(dāng)前的接近狀態(tài),如果為 YES 則為接近(觸發(fā)),否則為離開(kāi)(未觸發(fā)),需要的時(shí)候可以直接拿來(lái)用。
以及用于 UINotificationCenter 的鍵:
UIKIT_EXTERN NSString *const UIDeviceProximityStateDidChangeNotification NS_AVAILABLE_IOS(3_0);
于是如果需要在某個(gè)地方使用接近感應(yīng)器,可以注冊(cè)通知并且開(kāi)啟接近感應(yīng)器:
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) 方法里面實(shí)現(xiàn)需要做的處理。當(dāng)然記得在不需要的時(shí)候取消注冊(cè)通知、停止接近檢測(cè)。
這樣看起來(lái)是沒(méi)問(wèn)題的。
突然問(wèn)題來(lái)了
直到有一天發(fā)現(xiàn)了一個(gè)問(wèn)題:在已經(jīng)開(kāi)啟接近檢測(cè)的情況下,同時(shí)觸發(fā)接近感應(yīng)器和進(jìn)入后臺(tái)(按 Home 鍵的同時(shí)捂住聽(tīng)筒),這樣會(huì)在熄滅屏幕的情況下進(jìn)入了后臺(tái)。手離開(kāi)聽(tīng)筒,屏幕再次點(diǎn)亮。再進(jìn)入前臺(tái),發(fā)現(xiàn) [UIDevice currentDevice].proximityState 的值為 YES 。也就是說(shuō),觸發(fā)接近感應(yīng)器的同時(shí)進(jìn)入后臺(tái),在后臺(tái)時(shí)離開(kāi)接近感應(yīng)器是不會(huì)刷新接近狀態(tài)的(會(huì)保持在觸發(fā)狀態(tài))。在這種情況下,系統(tǒng)提供的接口結(jié)果不正確。經(jīng)驗(yàn)證,從 iOS 7 到 iOS 9 都存在這個(gè)問(wèn)題。只有再次觸發(fā)接近感應(yīng)器并離開(kāi)時(shí),才會(huì)收到 UIDeviceProximityStateDidChangeNotification 通知,也就是變回正常了。這樣看來(lái),UIDevice 的 proximityState 屬性也是依賴(lài)于上面的通知更新吧。
解決方案
好了,既然 iOS 留下了這個(gè) Bug,下面就是如何想辦法解決它。對(duì)于接近感應(yīng)器,我們需要的很簡(jiǎn)單,就是在任何情況下拿到的數(shù)據(jù)都是真實(shí)有效的。有這樣一種思路:每次從后臺(tái)進(jìn)入前臺(tái)時(shí),是用戶(hù)在屏幕亮著并且可操作的情況下進(jìn)來(lái)的,進(jìn)來(lái)之前的瞬間不可能是接近狀態(tài)。所以在進(jìn)入前臺(tái)后到收到接近狀態(tài)改變的通知前的這段時(shí)間,可以推測(cè)是非接近狀態(tài)。
在這樣的思路下,我們就可以做一個(gè)簡(jiǎn)單的接近檢測(cè)的工具類(lèi),添加一個(gè) proximityState 屬性,將 get 方法寫(xiě)為:
- (BOOL)proximityState{
if (self.isWaitingProximityStateUpdate) {
return NO;
}else {
return [[UIDevice currentDevice] proximityState];
}
}
其中,isWaitingProximityStateUpdate 表示是否為進(jìn)入前臺(tái)后到收到接近狀態(tài)改變通知前的這段時(shí)間。每次進(jìn)入前臺(tái)就把這個(gè)屬性置為 YES,收到接近狀態(tài)改變通知就置為 NO。
這樣,通過(guò)這個(gè)工具類(lèi)的 proximityState 屬性,在任何情況下拿到的結(jié)果都是真實(shí)有效的。