目錄
- 問題描述
- 修復(fù)方法
- 彈出授權(quán)框
- 調(diào)用方式
- 讓系統(tǒng)更新蜂窩網(wǎng)絡(luò)權(quán)限數(shù)據(jù)
- 調(diào)用方式
- 出現(xiàn)了玄學(xué)
- 用控制臺跟蹤進程間通信
- 彈出授權(quán)框
- 檢查網(wǎng)絡(luò)權(quán)限情況
- 檢測國行機型和是否有蜂窩功能
- 測試修復(fù)是否成功的方法
- 工具代碼和Demo
- 參考
問題描述
iOS 10有一個系統(tǒng)bug:app在第一次安裝時英妓,第一次聯(lián)網(wǎng)操作會彈出一個授權(quán)框贿衍,提示"是否允許xxx訪問數(shù)據(jù)干旁?"唠梨。而有時候系統(tǒng)并不會彈出授權(quán)框涧郊,導(dǎo)致app無法聯(lián)網(wǎng)。
詳細(xì)情況見:
iOS 10 的坑:新機首次安裝 app缩多,請求網(wǎng)絡(luò)權(quán)限“是否允許使用數(shù)據(jù)”
iOS 10 不提示「是否允許應(yīng)用訪問數(shù)據(jù)」拆又,導(dǎo)致應(yīng)用無法使用的解決方案
關(guān)鍵點總結(jié):
- 只有iOS 10以上、國行機型温兼、有蜂窩網(wǎng)絡(luò)功能的設(shè)備存在這個授權(quán)問題庆揪,WiFi版的iPad沒有這個問題;
- 由于授權(quán)框是在有網(wǎng)絡(luò)操作時才彈出的妨托,這就導(dǎo)致app第一次網(wǎng)絡(luò)訪問必定失敻组弧吝羞;
- 當(dāng)出現(xiàn)不彈出授權(quán)框的bug時,去設(shè)置里更改任意app的蜂窩網(wǎng)絡(luò)權(quán)限内颗,或者打開無線局域網(wǎng)助理钧排,讓系統(tǒng)更新一下蜂窩網(wǎng)絡(luò)相關(guān)的數(shù)據(jù),可以解決這個bug均澳。
這個系統(tǒng)bug出現(xiàn)時恨溜,對用戶來說是很麻煩的,app也需要提供詳細(xì)的提示語來應(yīng)對這種情況找前,十分不優(yōu)雅糟袁。
修復(fù)方法
春節(jié)有點空,找到了幾個相關(guān)的私有API來修復(fù)這個bug躺盛。
彈出授權(quán)框
首先找到的是一個能直接彈出授權(quán)框的API项戴。
//Image: /System/Library/PrivateFrameworks/FTServices.framework/FTServices
@interface FTNetworkSupport : NSObject
+ (id)sharedInstance;
- (bool)dataActiveAndReachable;
@end
頭文件參考:FTNetworkSupport.h
當(dāng)app之前沒有請求過網(wǎng)絡(luò)權(quán)限時,調(diào)用dataActiveAndReachable
會彈出"是否允許xxx訪問數(shù)據(jù)槽惫?"的授權(quán)框周叮,如果網(wǎng)絡(luò)權(quán)限已經(jīng)確定,則不會彈出界斜。
調(diào)用方式
由于FTNetworkSupport
是在PrivateFrameworks
目錄下仿耽,app并沒有加載這個庫,所以要使用里面的類前各薇,需要用dlopen
加載FTServices.framework
,簡單示意如下:
#import <dlfcn.h>
//加載FTServices.framework
void * FTServicesHandle = dlopen("/System/Library/PrivateFrameworks/FTServices.framework/FTServices", RTLD_LAZY);
Class NetworkSupport = NSClassFromString(@"FTNetworkSupport");
id networkSupport = [NetworkSupport performSelector:NSSelectorFromString(@"sharedInstance")];
[networkSupport performSelector:NSSelectorFromString(@"dataActiveAndReachable")];
//卸載FTServices.framework
dlclose(FTServicesHandle);
這個API能解決網(wǎng)絡(luò)權(quán)限導(dǎo)致第一個聯(lián)網(wǎng)操作失敗的問題项贺,但是它還是存在有時候不會彈出授權(quán)框的bug。
讓系統(tǒng)更新蜂窩網(wǎng)絡(luò)權(quán)限數(shù)據(jù)
既然更改任意app的蜂窩網(wǎng)絡(luò)權(quán)限后峭判,能讓app彈出授權(quán)框敬扛,那么只要找到一個方法,能讓系統(tǒng)更新一下網(wǎng)絡(luò)權(quán)限相關(guān)的數(shù)據(jù)就可以了朝抖。
用hopper
反編譯一下系統(tǒng)的設(shè)置app用到的庫PreferencesUI.framework
,找到了里面修改app網(wǎng)絡(luò)權(quán)限的API谍珊。用到的是CoreTelephony.framework
里的兩個私有C函數(shù):
CTServerConnection* _CTServerConnectionCreateOnTargetQueue(CFAllocatorRef, NSString *, dispatch_queue_t, void*/*一個block類型的參數(shù)*/)
void _CTServerConnectionSetCellularUsagePolicy(CTServerConnection *, NSString *, NSDictionary *)
大部分時間都花在測試這兩個函數(shù)上了治宣。幾個月前我也研究過這兩個函數(shù)嘗試修復(fù)這個bug,但是那時候發(fā)現(xiàn)沒什么作用砌滞,就不了了之了侮邀。
調(diào)用方式
要調(diào)用私有C函數(shù),需要用dlsym
贝润,簡單示意如下:
void *CoreTelephonyHandle = dlopen("/System/Library/Frameworks/CoreTelephony.framework/CoreTelephony", RTLD_LAZY);
//用函數(shù)指針來調(diào)用私有C函數(shù)绊茧,用符號名從庫里尋找函數(shù)地址
CFTypeRef (*connectionCreateOnTargetQueue)(CFAllocatorRef, NSString *, dispatch_queue_t, void*) = dlsym(CoreTelephonyHandle, "_CTServerConnectionCreateOnTargetQueue");
int (*changeCellularPolicy)(CFTypeRef, NSString *, NSDictionary *) = dlsym(CoreTelephonyHandle, "_CTServerConnectionSetCellularUsagePolicy");
//使用設(shè)置app的bundle id進行偽裝
CFTypeRef connection = connectionCreateOnTargetQueue(kCFAllocatorDefault,@"com.apple.Preferences",dispatch_get_main_queue(),NULL);
//請求修改本app的網(wǎng)絡(luò)權(quán)限為allowed,不會真的修改打掘,只能觸發(fā)系統(tǒng)更新一下相關(guān)的數(shù)據(jù)
changeCellularPolicy(connection, @"需要授權(quán)的app的bundle id", @{@"kCTCellularUsagePolicyDataAllowed":@YES});
dlclose(CoreTelephonyHandle);
注意华畏,在聲明connectionCreateOnTargetQueue和changeCellularPolicy函數(shù)指針時鹏秋,參數(shù)類型要嚴(yán)格對應(yīng),如果類型錯誤亡笑,可能會導(dǎo)致系統(tǒng)對參數(shù)執(zhí)行錯誤的內(nèi)存管理侣夷,出現(xiàn)crash。CTServerConnection
是私有的仑乌,是CFTypeRef
的子類百拓,所以這里可以用CFTypeRef
來代替。
出現(xiàn)了玄學(xué)
_CTServerConnectionSetCellularUsagePolicy
函數(shù)的第二個參數(shù)是需要修改的app的bundle id晰甚。在測試時衙传,發(fā)現(xiàn)傳入這個參數(shù)時,對象必須是用字面量語法創(chuàng)建的NSString
厕九,例如@"com.who.testDemo"
蓖捶,當(dāng)傳入[NSBundle mainBundle].bundleIdentifier
這種動態(tài)生成的NSString
時,仍然會出現(xiàn)不彈出授權(quán)框的bug止剖,也就是并沒有修復(fù)成功腺阳。連續(xù)測試5-10次就能重現(xiàn)。
不過穿香,用
NSMutableString *bundleIdentifier = [NSMutableString stringWithString:@"com.who"];
[bundleIdentifier appendString:@".testDemo"];
這樣的字符串也沒問題亭引。相同點是最終都是來自字面量語法創(chuàng)建的NSString
。
這個玄學(xué)問題目前還沒有找到原因皮获。
研究了一下字面量創(chuàng)建出的NSString
焙蚓,的確是有些特殊的。參考:Constant Strings in Objective-C洒宝。它是一個__NSCFConstantString
類型的字符串购公,在app的整個生命周期內(nèi),這個對象的內(nèi)存都不會被釋放雁歌。難道iOS的XPC對使用到的字符串還有要求宏浩?
時間有限,這個問題以后再研究吧靠瞎。
<a name="debug-trace"></a>用控制臺跟蹤進程間通信
這幾個私有API都用了進程間通信比庄,要進行調(diào)試跟蹤有點麻煩。
可以使用Mac上的控制臺查看設(shè)備的實時log乏盐,尋找通信行為佳窑。打開控制臺app,在左側(cè)選擇連接到Mac的iOS設(shè)備父能,就可以看到設(shè)備log了神凑。
下面是調(diào)用了_CTServerConnectionSetCellularUsagePolicy
之后的log,傳入bundle id時用的是字面量創(chuàng)建的字符串:
高亮的那行是測試demo打的log何吝,可以認(rèn)為就是在這里調(diào)用了
_CTServerConnectionSetCellularUsagePolicy
溉委,可以看到鹃唯,調(diào)用之后系統(tǒng)更新了本app的權(quán)限狀態(tài)。
CommCenter
就是這幾個私有API通信的對應(yīng)進程薛躬,用于管理設(shè)備的網(wǎng)絡(luò)俯渤。參考CommCenter - The iPhone Wiki。
下面是用[NSBundle mainBundle].bundleIdentifier
傳入_CTServerConnectionSetCellularUsagePolicy
的第二個參數(shù)時的log:
沒有看到系統(tǒng)更新app權(quán)限的相關(guān)log型宝,進程間通信可能失敗了八匠。因此可以確定,使用
_CTServerConnectionSetCellularUsagePolicy
時必須傳入字面量語法創(chuàng)建的字符串趴酣。
檢查網(wǎng)絡(luò)權(quán)限情況
由于dataActiveAndReachable
里面有異步操作梨树,所以不能立即用dlclose
卸載FTServices.framework
。解決方法是監(jiān)聽到蜂窩權(quán)限開啟時再卸載岖寞。
CoreTelephony
里的CTCellularData
可以用來監(jiān)測app的蜂窩網(wǎng)絡(luò)權(quán)限抡四,并且這不是個私有API。你也可以用它來幫助用戶檢測蜂窩權(quán)限是否被關(guān)閉仗谆,并給出提示指巡,防止出現(xiàn)用戶關(guān)了網(wǎng)絡(luò)權(quán)限導(dǎo)致app無法聯(lián)網(wǎng)的情況。
CTCellularData
的頭文件如下:
typedef NS_ENUM(NSUInteger, CTCellularDataRestrictedState) {
kCTCellularDataRestrictedStateUnknown,//權(quán)限未知
kCTCellularDataRestricted,//蜂窩權(quán)限被關(guān)閉隶垮,有 網(wǎng)絡(luò)權(quán)限完全關(guān)閉 or 只有WiFi權(quán)限 兩種情況
kCTCellularDataNotRestricted//蜂窩權(quán)限開啟
};
@interface CTCellularData : NSObject
///權(quán)限更改時的回調(diào)
@property (copy, nullable) CellularDataRestrictionDidUpdateNotifier cellularDataRestrictionDidUpdateNotifier;
///當(dāng)前的蜂窩權(quán)限
@property (nonatomic, readonly) CTCellularDataRestrictedState restrictedState;
@end
使用方法:
#import <CoreTelephony/CTCellularData.h>
CTCellularData *cellularDataHandle = [[CTCellularData alloc] init];
cellularDataHandle.cellularDataRestrictionDidUpdateNotifier = ^(CTCellularDataRestrictedState state) {
//蜂窩權(quán)限更改時的回調(diào)
};
使用時需要注意的關(guān)鍵點:
-
CTCellularData
只能檢測蜂窩權(quán)限藻雪,不能檢測WiFi權(quán)限。 - 一個
CTCellularData
實例新建時狸吞,restrictedState
是kCTCellularDataRestrictedStateUnknown
勉耀,之后在cellularDataRestrictionDidUpdateNotifier
里會有一次回調(diào),此時才能獲取到正確的權(quán)限狀態(tài)蹋偏。 - 當(dāng)用戶在設(shè)置里更改了app的權(quán)限時便斥,
cellularDataRestrictionDidUpdateNotifier
會收到回調(diào),如果要停止監(jiān)聽威始,必須將cellularDataRestrictionDidUpdateNotifier
設(shè)置為nil
枢纠。 - 賦值給
cellularDataRestrictionDidUpdateNotifier
的block并不會自動釋放,即便你給一個局部變量的CTCellularData
實例設(shè)置監(jiān)聽黎棠,當(dāng)權(quán)限更改時晋渺,還是會收到回調(diào),所以記得將block置nil
葫掉。
檢測國行機型和是否有蜂窩功能
非國行機型,以及沒有蜂窩功能的設(shè)備是不需要進行修復(fù)的跟狱。因此也要尋找相關(guān)的私有API進行檢測俭厚。
用到的私有API如下:
//Image: /System/Library/PrivateFrameworks/AppleAccount.framework/AppleAccount
@interface AADeviceInfo : NSObject
///是否有蜂窩功能
- (bool)hasCellularCapability;
///設(shè)備的區(qū)域代碼,例如國行機就是CH
- (id)regionCode;
@end
頭文件參考:AADeviceInfo.h
使用方式和FTServices.framework
類似驶臊,不再重復(fù)挪挤。
測試修復(fù)是否成功的方法
我的測試方式是每次運行都修改項目的bundle identifier
和display name
叼丑,讓系統(tǒng)每次都把它當(dāng)做一個新app,使用Release
模式扛门,測試是否每次都能夠彈出授權(quán)框鸠信。由于需要不斷修改bundle identifier
,寫了個腳本在每次build時自動運行论寨,會自動累加幾個地方的bundle identifier
后面的數(shù)字星立。demo里已經(jīng)附帶了這個腳本。
你也可以測試一下不執(zhí)行修復(fù)時葬凳,進行聯(lián)網(wǎng)操作是否會彈出授權(quán)框绰垂。我的測試結(jié)果是大約運行5-10次時,就會出現(xiàn)不彈出授權(quán)框的bug火焰。需要把項目改為Release
模式才能出現(xiàn)劲装,Debug
模式下不會出bug。
注意昌简,由于build后自動累加的關(guān)系占业,ZIKCellularAuthorization.h
里的AppBundleIdentifier
是下一次app運行時的值。如果你覺得這個腳本把你搞暈了纯赎,可以在Build Phases/Run Script
里關(guān)掉谦疾,在sh ${PROJECT_DIR}/IncreaseBundleId.sh
前面加個#
注釋掉就行了。
沒有測試覆蓋安裝同一個bundle identifier
的app址否,或者更新了版本號的app是否也會出現(xiàn)這個bug餐蔬,現(xiàn)在是認(rèn)為只有第一次安裝時才會出現(xiàn)bug。
工具代碼和Demo
地址在ZIKCellularAuthorization佑附,用到的私有API已經(jīng)經(jīng)過混淆樊诺。測試前記得先把Build Configuration
改為Release
模式。有幫助請點個Star~
參考
iOS 10 的坑:新機首次安裝 app音同,請求網(wǎng)絡(luò)權(quán)限“是否允許使用數(shù)據(jù)”
iOS 10 不提示「是否允許應(yīng)用訪問數(shù)據(jù)」词爬,導(dǎo)致應(yīng)用無法使用的解決方案