國行iPhone的獨(dú)有無奈
iOS9之后蘋果針對所有的APP權(quán)限新增蜂窩網(wǎng)絡(luò)訪問權(quán)限汇鞭,默認(rèn)都是允許訪問狀態(tài)霍骄,用戶可以自行去“設(shè)置-蜂窩移動網(wǎng)絡(luò)”里關(guān)閉該權(quán)限。
iOS10之后蘋果針對國行手機(jī)的APP在蜂窩網(wǎng)絡(luò)訪問權(quán)限的基礎(chǔ)上新增一個無線局域網(wǎng)權(quán)限的選擇。這是由于中國大陸相關(guān)部門出臺的新規(guī)定指出米间,應(yīng)用在未經(jīng)用戶允許的前提下车伞,系統(tǒng)不能授予其使用聯(lián)網(wǎng)的功能喻喳,這其中就包括無線局域網(wǎng)(WIFI)表伦。因此許多應(yīng)用在第一次安裝的時候會自動彈出一個彈窗詢問用戶是否允許該應(yīng)用使用包括無線局域網(wǎng)和蜂窩移動數(shù)據(jù)蹦哼。
但是纲熏,這一新增的無線網(wǎng)絡(luò)授權(quán)僅僅在手機(jī)系統(tǒng)層面,在應(yīng)用開發(fā)的層面上蘋果并未做任何相關(guān)的調(diào)整勺拣,也就是說:開發(fā)者無法通過系統(tǒng)API獲取到當(dāng)前用戶對于某個應(yīng)用的無線局域網(wǎng)授權(quán)情況鱼填,因此苹丸,如果用戶在上圖中不注意選擇了不允許或者由于系統(tǒng)BUG的原因(原因詳見:https://juejin.im/post/57e229880e3dd90069867129)無法獲得對應(yīng)的授權(quán),則很容易被用戶誤解是該APP出現(xiàn)BUG宦言,用戶體驗也會大打折扣蜡励。
解決思路
首先,我們明確一點(diǎn):蘋果官方?jīng)]有提供對應(yīng)的API供開發(fā)人員獲取到應(yīng)用的無線局域網(wǎng)的授權(quán)情況兼都。我們解決問題的方法是要引導(dǎo)用戶到相應(yīng)的系統(tǒng)界面進(jìn)行開啟權(quán)限操作(在“設(shè)置-無線局域網(wǎng)/蜂窩移動網(wǎng)絡(luò)-使用無線局域網(wǎng)與蜂窩移動的應(yīng)用”里找到對應(yīng)的APP開啟權(quán)限)扮碧。
接下來慎王,思考一下:能不能對這一種場景進(jìn)行代碼層面的推斷呢?我們嘗試收集相關(guān)的信息:
- 該權(quán)限被關(guān)閉的結(jié)果:在WIFI可以訪問網(wǎng)絡(luò)的情況下宏侍,應(yīng)用內(nèi)無法訪問網(wǎng)絡(luò)
- 無線局域網(wǎng)權(quán)限是在蜂窩移動網(wǎng)絡(luò)授權(quán)的基礎(chǔ)上新增一個選項而來
-
國行iOS10的網(wǎng)絡(luò)授權(quán)選項包括“關(guān)閉”赖淤、“無線局域網(wǎng)”、“無線局域網(wǎng)與蜂窩移動數(shù)據(jù)”
應(yīng)用網(wǎng)絡(luò)授權(quán)界面
以上信息提煉一下:
- 已連接到某個無線局域網(wǎng)(成功連接某個網(wǎng)絡(luò)并且能獲取到SSID信息)
- 應(yīng)用內(nèi)網(wǎng)絡(luò)不可觸達(dá)
- 網(wǎng)絡(luò)訪問權(quán)限被關(guān)閉
針對以上三點(diǎn)谅河,就能推斷出應(yīng)用的無線網(wǎng)絡(luò)權(quán)限被關(guān)閉咱旱,那么我們開始對這三點(diǎn)“優(yōu)雅”地書寫代碼。
1. 判斷當(dāng)前手機(jī)成功連接某個網(wǎng)絡(luò)并且能獲取到SSID信息
以下代碼若能成功返回非空信息绷耍,則說明成功連接到某個網(wǎng)絡(luò)(返回的內(nèi)容為SSID信息)
- (NSDictionary *)fetchSSIDInfo {
NSArray *ifs = (__bridge_transfer NSArray *)CNCopySupportedInterfaces();
if (!ifs) {
return nil;
}
NSDictionary *info = nil;
for (NSString *ifnam in ifs) {
info = (__bridge_transfer NSDictionary *)CNCopyCurrentNetworkInfo((__bridge CFStringRef)ifnam);
if (info && [info count]) { break; }
}
return info;
}
2. 判斷應(yīng)用內(nèi)網(wǎng)絡(luò)不可觸達(dá)
這里使用 AFNetworking 的 AFNetworkReachabilityManager 進(jìn)行監(jiān)測吐限,可以使用蘋果官方的 Reachability (https://developer.apple.com/library/ios/#samplecode/Reachability/Introduction/Intro.html)褂始,二者同源诸典。
PS:這里的判斷方法并不是真正意義上的判斷網(wǎng)絡(luò)是否可以觸達(dá),該方法僅僅判斷應(yīng)用能否連接上手機(jī)網(wǎng)絡(luò)崎苗,網(wǎng)絡(luò)類型如何狐粱,并不能判斷手機(jī)連接到無線局域網(wǎng)之后是否可以訪問外網(wǎng)的情況,不過用這種方法已經(jīng)滿足我們的需求胆数,因為權(quán)限限制是在應(yīng)用能否訪問手機(jī)網(wǎng)絡(luò)這一節(jié)點(diǎn)脑奠。
- (void)startAFNetworkMonitoring {
// 這里sharedHTTPSessionManager使用只是對AFHTTPSessionManager進(jìn)行單例封裝,因為對默認(rèn)的AFHTTPSessionManager直接使用有內(nèi)存泄漏的問題
AFHTTPSessionManager *manager = [KCSharedSessionManager sharedHTTPSessionManager];
// 應(yīng)用網(wǎng)絡(luò)狀態(tài)改變時執(zhí)行異步回調(diào)
[manager.reachabilityManager setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status) {
switch (status) {
// 蜂窩網(wǎng)絡(luò)
case AFNetworkReachabilityStatusReachableViaWWAN:
break;
case AFNetworkReachabilityStatusReachableViaWiFi:
_netState = YES;
break;
case AFNetworkReachabilityStatusNotReachable:
_netState = NO;
break;
case AFNetworkReachabilityStatusUnknown:
_netState = NO;
break;
default:
_netState = NO;
break;
}
}];
[manager.reachabilityManager startMonitoring];
}
3. 判斷網(wǎng)絡(luò)訪問權(quán)限被關(guān)閉
使用iOS9新增的系統(tǒng)庫CoreTelephony.framework進(jìn)行判斷(蜂窩網(wǎng)絡(luò)權(quán)限授權(quán)也是iOS9才增加的)幅慌。
這里帶一個思考:無線局域網(wǎng)權(quán)限授權(quán)是在蜂窩網(wǎng)絡(luò)權(quán)限授權(quán)的基礎(chǔ)上新增一項宋欺,我們可以嘗試下通過這個庫,獲取到的僅僅只有無線局域網(wǎng)權(quán)限而無蜂窩網(wǎng)絡(luò)權(quán)限時的授權(quán)狀態(tài)值是怎樣的胰伍。
PS:值得一提的是齿诞,CTCellularData的block屬性cellularDataRestrictionDidUpdateNotifier并不會自動釋放,而且即使對應(yīng)的CTCellularData實(shí)例釋放了骂租,該block屬性也不會釋放祷杈,注意使用即可
@import CoreTelephony;
- (void)startCellularDataAuthMonitoring {
if (!self.cellularData) self.cellularData = [[CTCellularData alloc] init];
self.cellularData.cellularDataRestrictionDidUpdateNotifier = nil;
if (self.cellularData) {
// 該block為異步回調(diào)
self.cellularData.cellularDataRestrictionDidUpdateNotifier = ^(CTCellularDataRestrictedState state) {
// 獲取應(yīng)用聯(lián)網(wǎng)授權(quán)狀態(tài)
switch (state) {
case kCTCellularDataRestricted: NSLog(@"Restricrted"); // 權(quán)限受限
break;
case kCTCellularDataNotRestricted: NSLog(@"Not Restricted"); // 權(quán)限不受限
break;
case kCTCellularDataRestrictedStateUnknown: NSLog(@"Unknown"); // 未知,第一次請求
break;
default:
break;
}
};
};
}
經(jīng)過測試渗饮,發(fā)現(xiàn)如下圖的狀態(tài)值對應(yīng)的網(wǎng)絡(luò)受限情況:
總結(jié)使用
現(xiàn)在邏輯已經(jīng)很清晰但汞,就是當(dāng)以上三個判斷都成立的時候宿刮,可以推斷出用戶關(guān)閉了應(yīng)用訪問無線互聯(lián)網(wǎng)的權(quán)限,這時候就可以彈出引導(dǎo)用戶打開相關(guān)權(quán)限的彈窗了私蕾。
這里有一個需要注意的地方是:CTCellularData和AFNetworkReachabilityManager用戶監(jiān)測的回調(diào)都是異步的僵缺,也就是說,當(dāng)發(fā)生對應(yīng)的狀態(tài)改變時踩叭,回調(diào)才會生效磕潮,這里需要分別對狀態(tài)值的改變進(jìn)行監(jiān)聽或者設(shè)置依賴才能正確執(zhí)行我們的判斷邏輯,這里我使用了RAC進(jìn)行信號的監(jiān)聽容贝。
@weakify(self);
// 當(dāng)網(wǎng)絡(luò)權(quán)限和網(wǎng)絡(luò)狀態(tài)值發(fā)生改變(開機(jī)啟動時狀態(tài)值從空改變到有值)時自脯,觸發(fā)信號監(jiān)聽邏輯
[[[RACSignal merge:@[RACObserve(self.cellularData, restrictedState),
RACObserve([KCSharedSessionManager sharedHTTPSessionManager].reachabilityManager, networkReachabilityStatus)]]
bufferWithTime: 0 onScheduler: [RACScheduler mainThreadScheduler]]
subscribeNext: ^(id value) {
@strongify(self);
if (self.cellularData.restrictedState == kCTCellularDataRestricted && [KCSharedSessionManager sharedHTTPSessionManager].reachabilityManager.networkReachabilityStatus == AFNetworkReachabilityStatusNotReachable && [self fetchSSIDInfo]) {
// 回到主線程
dispatch_async_on_main_queue(^{
// 顯示彈窗
});
}
}];