• iOS10网络权限数据


    参考地址:1.http://www.cocoachina.com/ios/20180723/24274.html   https://blog.csdn.net/wang_bo_justone/article/details/77320464

    方法2:

    iOS 10 之后,陆陆续续地有用户联系我们,说新机第一次安装、第一次启动的时候,app 首屏一片空白,完全没数据。kill 掉重新打开就好了。

    一开始以为是用户网络情况不好,但随着越来越多的用户报告这个问题,我意识到这并不是偶然情况。但是并非所有用户都如此。

    而且卸载掉之后,如果再装,也不会出现这现象。问题只会出现在这台设备第一次安装、第一次启动的情况下。如果把手机抹掉、重置,问题还能重现。

    定位问题

    这个问题真的很棘手,也很难定位。幸运的是,公司同事想到把手机抹掉重置,得以在我眼前重现问题。

    我发现的是,app 首次启动会弹出一个询问用户“是否允许应用访问数据”的弹框,类似下图:

     

    询问网络权限的弹框

    虽然 app 刚打开的时候是一片空白,但我发现进去之后,登录、下拉刷新等都没问题。因此很容易猜测出这样的结论:用户点“允许”之前,网络请求全都是失败的;而点“允许”之后,网络请求就能正常进行了。

    问题原因

    有了方向之后就好查了。很快查到了掘金的这篇文章,得知这个弹框来自于工信部的要求。这篇文章里还有如果弹框不出现,用户可以采取的解决方案。另外,从少数派的这篇文章 看到,只有国行手机有这个功能。这也就解释了为何有些用户出现、而有些用户没出现这个问题。

     

    蜂窝移动网络的两种界面

    进到手机的 设置->蜂窝移动网络,如果看到如左图就说明是不会弹框的机型,如果看到如右图,说明是会弹框的机型。

    那么这个新功能会为用户带来哪些问题呢?问题主要在于,用户点击“允许”之前,所有网络请求都是被禁止的。具体有两种表现:

    1. 少部分用户根本不显示弹框,所以网络请求一直被禁止。针对这部分用户,只能通过客服引导,按照的这篇文章(http://www.jianshu.com/p/28e8919a2cae),逐个尝试里面的解决方案;

    2. 对于绝大部分用户,弹框会正确显示;然而从 app 启动到用户点击“允许”需要一段时间,在这段时间内发出的网络请求全都会直接失败;

    如果用户点击“不允许”,app 永远无法访问网络,Wifi 和数据流量均不可以。当然,这是用户自己的选择,我们没什么可做的。我们主要需要解决的是上面的第二个问题。

    影响范围

    这个特性推出之后,大部分 app 应该都会受到不同程度的影响。可以着重在这几个方面检查一下自己的 app:

    1. 首屏数据。首屏几个 tab 的数据往往在 app 启动时即加载,也就是在用户点“允许”之前。很容易造成用户第一次进入时,首屏数据空白。

    2. 推送。通常的处理逻辑是,把注册设备远程推送的代码写在 appDelegate 里。经过测试发现,这种写法下允许推送的弹框和允许使用网络的弹框出现的顺序没有一定。如果先出允许推送的弹框,用户点击允许,此时注册 deviceToken 是不能成功的。当然如果用户允许访问网络,第二次打开 app 时也会走一遍注册远程推送方法,此时就能注册成功了。

    3. 其他首次启动的处理。诸如广告页、活动页之类,需要在启动时请求的数据。新版本的更新检查往往也在启动时进行,但这一点影响不大,因为首次打开的用户一般都是处于最新版。另外,常常会在新设备首次启动时,上传一个设备唯一标识用于统计目的,例如 IDFA。

    解决方案

    在重置过的手机上,尝试装了一些大大小小的 app,发现不少 app 在适配这个新特性上都存在一些小问题。而有些 app 也做了比较有特色的处理。

    不幸的是,苹果这个功能可能出得太仓促,并没有给开发者提供相应的 API。所以,我们没办法检测到用户点击“允许”或“不允许”网络请求的回调,也没法检测到当前用户是否授权的状态。只能通过一些特殊处理,来尽量减小对用户的影响。

    总体来说,主要有如下几个解决方案:

    1. 延迟请求。对于首次启动的所有接口,如果能延迟到用户点击“允许”之后再请求,或者重新请求一次,就能把对用户的影响降到最低,是一个比较好的解决方案。因为首次启动往往有几屏引导页,一个比较好的时机是引导页结束时。此时用户已经进行了授权,数据都能正确得到。所以我自己的做法是把请求推迟到了引导页。另外下面评论里饶志臻大神提了一个特别好的思路,就是用 AFN 监听网络状态,有网时开始请求。虽然没有试过(我自己手机不是国行,不太好实验),但感觉应该也能比较完美地处理这个问题。

    2. 允许用户手动重新请求。出现数据空白时,如果在空白页面上有“重新加载”的按钮,也可以让用户体验好一些。比较有趣的是,测试中发现网易严选的处理是这样的:

    网易严选的首屏界面

    加了一个“查看解决方案”的按钮。点击这个按钮会跳转到一个描述解决方案的页面,内容跟上面掘金的文章类似。很有意思的处理,虽然不能避免白屏,但用户会尝试重新打开,还可以帮到少部分始终不显示弹框的用户。

    3.稍后重新请求。网络框架如果做了请求失败时,定时重新请求的处理,应该也能解决首次请求失败的问题。另外,首次启动时各种处理的逻辑都可以写成一旦失败,下次启动重试。如每次启动都会注册远程推送。另一个例子是上传设备唯一标识的逻辑,可以写成类似这样:

    NSString *storedIDFA = [[NSUserDefaults standardUserDefaults] objectForKey:kIDFAKey];

    NSString *idfaString = [[[ASIdentifierManager sharedManager] advertisingIdentifier] UUIDString];

    if ([storedIDFA isEqualToString:idfaString]) {

        return;

    }

    [HAMCommonBusinessStore requestUploadIDFA:idfaString success:^(id response) {

            [[NSUserDefaults standardUserDefaults] saveObject:idfaString forKey:kIDFAKey];

    }];

    每次打开 app 都调用这段代码,而上传成功时才保存到本地。这样首次请求失败也无妨,下次打开时仍能重试上传,直到成功为止。

    一直都有用户反馈无法正常联网的问题,经过定位,发现很大一部分用户是因为网络权限被系统关闭,经过资料搜集和排除发现根本原因是:

    1. 第一次打开 app 不能访问网络,无任何提示

    2. 第一次打开 app 直接提示「已为“XXX”关闭网络」

    3. 第一次打开 app ,用户点错了选择了「不允许」或「WLAN」

    对于第 1 种情况,出现在 iOS 10 比较多,一旦出现后系统设置里也找不到「无线数据」这一配置选项,随着 iOS 的更新,貌似被 Apple 修复了,GitHub 上面有 ZIKCellularAuthorization 其进行分析和提出一种解决方案,强制让系统弹出那个询问框。

    但是第 2、3种情况现在 iOS 12 还经常有发生,对于这种情况,我们只要检测出来,并提示引导用户去打开网络权限即可,本文提出一新的方法来检测这种情况。

    CTCellularData 的局限性

    关于网络权限问题,网络上搜集的资料大多数提到了用 CTCellularData 的 cellularDataRestrictionDidUpdateNotifier 方法去判断网络权限关闭,但这样判断会有不完善的情况(后面提到)

    CoreTelephony 里的 CTCellularData 可以用来监测 app 的蜂窝网络权限,其定义如下:

    typedef NS_ENUM(NSUInteger, CTCellularDataRestrictedState) {
        kCTCellularDataRestrictedStateUnknown, 
        kCTCellularDataRestricted,            
        kCTCellularDataNotRestricted          
    };

    通过注册 cellularDataRestrictionDidUpdateNotifier 回调可以并判断其 state 可以判断蜂窝数据的权限

    CTCellularData *cellularData = [[CTCellularData alloc] init];
        cellularData.cellularDataRestrictionDidUpdateNotifier = ^(CTCellularDataRestrictedState restrictedState) {
               ...
            }
        };

    系统设置里 有三种选项分别对应:

    • 系统选项     CTCellularDataRestrictedState

    • 关闭     kCTCellularDataRestricted

    • WLAN     kCTCellularDataRestricted

    • WALN 与蜂窝移动网     kCTCellularDataNotRestricted

    实测发现:

    1、若用户此时用蜂窝数据上网,但在「允许“XXX”使用的数据」,选择了「WLAN」 或 「关闭」,回调拿到的值是

    kCTCellularDataRestricted ,此时我们可以确定是因为权限问题导致用户不能访问,应该去提示用户打开网络权限。

    2、若用户此时用 Wi-Fi 上网,但在「允许“XXX”使用的数据」设置中选择了 「关闭」,我们拿到的值是 kCTCellularDataRestricted ,这种情况下同样需要提示用户打开网络权限。

    3、若用户此时用 Wi-Fi 上网,但在「允许“XXX”使用的数据」设置中选择了 「WLAN」,我们拿到的值是 kCTCellularDataRestricted ,但是此时用户是有网络访问权限的,此时不应该去提示用户。

    判断思路

    结合 SCNetworkReachabilityRef 的回调,以及对网络状态的区分来判断:

    通过判断 SCNetworkReachabilityRef 回调的 flag 发现 kSCNetworkReachabilityFlagsReachable 为 0,则说明网络不通,此时可能有两种情况:

    1. 未打开任何数据连接(Wi-Fi 蜂窝数据)或者开启了飞行模式

    2. 网络权限被关闭

    所以我们的判断思路就是要判断出用户是否 开启了 Wi-Fi 或者 蜂窝数据,如果都不是那必定是网络权限被关闭。

    实现细节

    判断当前网络类型

    思路:

    1、先通过 CaptiveNetwork 去判断有没有开启 Wi-Fi,这个判断无论在网络权限是否打开下的判断都是准确的。

    2、由于在没有网络权限的情况下,没有办法直接去判断是否开启了蜂窝数据,这里只能通过一种比较 trick 的方式,通过状态栏去判断用户是否开启了蜂窝数据,但是在一些极端的情况下,不一定准确,比如用户同时开启 Wi-Fi 和蜂窝数据,此时先关闭 Wi-Fi 然后迅速关闭蜂窝数据,此时手机处于无网络状态,我们在第 1 步判断出了 Wi-Fi 不可用,但是通过状态栏的方式拿到却还是 Wi-Fi,在这种比较边界的情况下,只能延时一会儿再次检查。

    - (void)getCurrentNetworkType:(void(^)(ZYNetworkType))block {
        if ([self isWiFiEnable]) {
            return block(ZYNetworkTypeWiFi);
        }
       ZYNetworkType type = [self getNetworkTypeFromStatusBar];
        if (type == ZYNetworkTypeOffline) {
            block(ZYNetworkTypeOffline);
        } else if (type == ZYNetworkTypeWiFi) { // 这时候从状态栏拿到的是 Wi-Fi 说明状态栏没有刷新,延迟一会再获取
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                [self getCurrentNetworkType:block];
            });
        } else {
            block(ZYNetworkTypeCellularData);
        }
    }

     

    判断是否连接到 Wi-Fi

    判断 Wi-Fi 的方法比较简单,导入 SystemConfiguration/CaptiveNetwork.h 并使用下面方法判断即可

    - (BOOL)isWiFiEnable {
        NSArray *interfaces = (__bridge_transfer NSArray *)CNCopySupportedInterfaces();
        if (!interfaces) {
            return NO;
        }
        NSDictionary *info = nil;
        for (NSString *ifnam in interfaces) {
            info = (__bridge_transfer NSDictionary *)CNCopyCurrentNetworkInfo((__bridge CFStringRef)ifnam);
            if (info && [info count]) { break; }
        }
        return (info != nil);
    }

     

    从状态栏判断网络类型

    上面提到,由于在网络权限拒绝的情况下,我们唯一比较有效的方法是通过状态栏去判断,这个判断方法在网上可以找到,但是 在 iPhone X 会出现 crash 的情况,我针对 iPhone X 做了补充和适配。

    - (ZYNetworkType)getNetworkTypeFromStatusBar {
        NSInteger type = 0;
        @try {
            UIApplication *app = [UIApplication sharedApplication];
            UIView *statusBar = [app valueForKeyPath:@"statusBar"];

            if (statusBar == nil ){
                return ZYNetworkTypeUnknown;
            }
            BOOL isModernStatusBar = [statusBar isKindOfClass:NSClassFromString(@"UIStatusBar_Modern")];
            if (isModernStatusBar) { // 在 iPhone X 上 statusBar 属于 UIStatusBar_Modern ,需要特殊处理
                id currentData = [statusBar valueForKeyPath:@"statusBar.currentData"];
                BOOL wifiEnable = [[currentData valueForKeyPath:@"_wifiEntry.isEnabled"] boolValue];
                // 这里不能用 _cellularEntry.isEnabled 来判断,该值即使关闭仍然有是 YES
                BOOL cellularEnable = [[currentData valueForKeyPath:@"_cellularEntry.type"] boolValue];
                return  wifiEnable     ? ZYNetworkTypeWiFi :
                        cellularEnable ? ZYNetworkTypeCellularData : ZYNetworkTypeOffline;

            } else { // 传统的 statusBar
                NSArray *children = [[statusBar valueForKeyPath:@"foregroundView"] subviews];
                for (id child in children) {
                    if ([child isKindOfClass:[NSClassFromString(@"UIStatusBarDataNetworkItemView") class]]) {
                        type = [[child valueForKeyPath:@"dataNetworkType"] intValue];
                        // type == 1  => 2G
                        // type == 2  => 3G
                        // type == 3  => 4G
                        // type == 4  => LTE
                        // type == 5  => Wi-Fi
                    }
                }
                return type == 0 ? ZYNetworkTypeOffline :
                       type == 5 ? ZYNetworkTypeWiFi    : ZYNetworkTypeCellularData;
            }
        } @catch (NSException *exception) {
        }
        return 0;
    }

    整体判断代码

    - (void)startCheck {

        /* iOS 10 以下默认通过 **/

        /* 先用 currentReachable 判断,若返回的为 YES 则说明:
         1. 用户选择了 「WALN 与蜂窝移动网」并处于其中一种网络环境下。
         2. 用户选择了 「WALN」并处于 WALN 网络环境下。

         此时是有网络访问权限的,直接返回 ZYNetworkAccessible
        **/
        if ([UIDevice currentDevice].systemVersion.floatValue < 10.0 || [self currentReachable]) {
            [self notiWithAccessibleState:ZYNetworkAccessible];
            return;
        }

        CTCellularDataRestrictedState state = _cellularData.restrictedState;

        switch (state) {
            case kCTCellularDataRestricted: {// 系统 API 返回 无蜂窝数据访问权限

                [self getCurrentNetworkType:^(ZYNetworkType type) {
                    /*  若用户是通过蜂窝数据 或 WLAN 上网,走到这里来 说明权限被关闭**/

                    if (type == ZYNetworkTypeCellularData || type == ZYNetworkTypeWiFi) {
                        [self notiWithAccessibleState:ZYNetworkRestricted];
                    } else {  // 可能开了飞行模式,无法判断
                        [self notiWithAccessibleState:ZYNetworkUnknown];
                    }
                }];

                break;
            }
            case kCTCellularDataNotRestricted: // 系统 API 访问有有蜂窝数据访问权限,那就必定有 Wi-Fi 数据访问权限
                [self notiWithAccessibleState:ZYNetworkAccessible];
                break;
            case kCTCellularDataRestrictedStateUnknown:
                [self notiWithAccessibleState:ZYNetworkUnknown];
                break;
            default:
                break;
        };
    }

    方法2:

    ZYNetworkAccessibity

    GitHub : ZYNetworkAccessibity

    我已经把上面的方法做了封装,将 ZYNetworkAccessibity.h 和 ZYNetworkAccessibity.m 拖项目中,监听 ZYNetworkAccessibityChangedNotification 通知即可

    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(networkChanged:) name:ZYNetworkAccessibityChangedNotification object:nil];

    然后处理通知

    - (void)networkChanged:(NSNotification *)notification {

        ZYNetworkAccessibleState state = ZYNetworkAccessibity.currentState;

        if (state == ZYNetworkRestricted) {
            NSLog(@"网络权限被关闭");
        }
    }

    另外还实现了自动提醒用户打开权限,如果你需要,请打开

    [ZYNetworkAccessibity setAlertEnable:YES];
  • 相关阅读:
    使用bootstrap建立响应式网页——通栏轮播图(carousel)
    java的HashMap与ConcurrentHashMap
    JVM性能调优
    Spring注解@Component、@Repository、@Service、@Controller区别
    Java过滤器与SpringMVC拦截器之间的关系与区别
    Java线程(二):线程同步synchronized和volatile
    Java线程(一):线程安全与不安全
    MySQL 加锁处理分析
    Spring 事务机制详解
    使用jsonp跨域调用百度js实现搜索框智能提示,并实现鼠标和键盘对弹出框里候选词的操作【附源码和在线测试地址】
  • 原文地址:https://www.cnblogs.com/hualuoshuijia/p/9448597.html
Copyright © 2020-2023  润新知