iPhone X 上获取状态栏网络状态的方法

iPhone X 上获取状态栏网络状态的方法

获取网络状态常用的库是Reachability。但是这个库存在一些问题,替代的方法是从状态栏获取网络状态。一般的写法是:

typedef enum {
    NetWorkType_None = 0,
    NetWorkType_2G,
    NetWorkType_3G,
    NetWorkType_4G,
    NetWorkType_LTE,
    NetWorkType_WiFi,
} NetWorkType;

UIApplication *application = [UIApplication sharedApplication];
NSArray *subviews = [[[application valueForKey:@"statusBar"] valueForKey:@"foregroundView"]subviews];
 
NSNumber *dataNetWorkItemView = nil;
 
for (id subView in subviews) {
  if ([subView isKindOfClass:[NSClassFromString(@"UIStatusBarDataNetworkItemView") class]]) {
            dataNetWorkItemView = subView;
            break;
        }
    }
 
    NetWorkType networkType = NetWorkType_None;
    switch ([[dataNetWorkItemView valueForKey:@"dataNetworkType"] integerValue]) {
        case 1:
            networkType = NetWorkType_2G;
            break;
        case 2:
            networkType = NetWorkType_3G;
            break;
        case 3:
            networkType = NetworkType_4G;
            break;
        case 4:
        	networkType = NetworkType_LTE;
        	break;
        case 5:
        	networkType = NetworkType_WiFi;
        	break;
        default:
            networkType = NetWorkType_None;
            break;
    }
    return networkType;
}

这样的方法在iPhone X上会闪退。因为iPhone X上导航栏会改用UIStatusBar_Modern类,结构跟之前大不相同。为了在iPhone X上获取状态栏上的网络状态,试过运行时查看状态栏里的内容,然而模拟器的网络状态只有WiFi。没有真机,无法获知移动网络时的区别。那么只能视图获取状态栏的数据源,用反编译工具Hopper里查看状态栏初始化,找到了数据源+[UIStatusBarServer getStatusBarData]。方法返回值是结构体,可以运行时去获取具体结构,推荐使用https://github.com/nst/RuntimeBrowser工具,得到返回的结构体如下:

struct StatusBarData {
    /* 0 时间
     * 2 飞行模式
     * 6 网络类型
     * 8 电池
     */
    bool itemIsEnabled[35];
    char timeString[64];
    char shortTimeString[64];
    int gsmSignalStrengthRaw;
    int gsmSignalStrengthBars;
    char serviceString[100];
    char serviceCrossfadeString[100];
    char serviceImages[2][100];
    char operatorDirectory[1024];
    unsigned int serviceContentType;
    int wifiSignalStrengthRaw;
    int wifiSignalStrengthBars;
    unsigned int dataNetworkType;
    int batteryCapacity;
    unsigned int batteryState;
    char batteryDetailString[150];
    int bluetoothBatteryCapacity;
    int thermalColor;
    unsigned int thermalSunlightMode : 1;
    unsigned int slowActivity : 1;
    unsigned int syncActivity : 1;
    char activityDisplayId[256];
    unsigned int bluetoothConnected : 1;
    unsigned int displayRawGSMSignal : 1;
    unsigned int displayRawWifiSignal : 1;
    unsigned int locationIconType : 1;
    unsigned int quietModeInactive : 1;
    unsigned int tetheringConnectionCount;
    unsigned int batterySaverModeActive : 1;
    unsigned int deviceIsRTL : 1;
    unsigned int lock : 1;
    char breadcrumbTitle[256];
    char breadcrumbSecondaryTitle[256];
    char personName[100];
    unsigned int electronicTollCollectionAvailable : 1;
    unsigned int wifiLinkWarning : 1;
    unsigned int wifiSearching : 1;
    double backgroundActivityDisplayStartDate;
};

其中有些字段明显是字符串,解析出来是bool,我手动修改了下类型。那么就可以写出获取状态栏的网络状态的方法:

+ (int)networkType
{
    Class cls = NSClassFromString(@"UIStatusBarServer");
    SEL selector = NSSelectorFromString(@"getStatusBarData");
    NSMethodSignature *signature = [cls methodSignatureForSelector:selector];
    NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
    invocation.target = cls;
    invocation.selector = selector;
    [invocation invoke];
    struct {
        bool x1[35];
        BOOL x2[64];
        BOOL x3[64];
        int x4;
        int x5;
        BOOL x6[100];
        BOOL x7[100];
        BOOL x8[2][100];
        BOOL x9[1024];
        unsigned int x10;
        int x11;
        int x12;
        unsigned int x13;
        int x14;
        unsigned int x15;
        BOOL x16[150];
        int x17;
        int x18;
        unsigned int x19 : 1;
        unsigned int x20 : 1;
        unsigned int x21 : 1;
        BOOL x22[256];
        unsigned int x23 : 1;
        unsigned int x24 : 1;
        unsigned int x25 : 1;
        unsigned int x26 : 1;
        unsigned int x27 : 1;
        unsigned int x28;
        unsigned int x29 : 1;
        unsigned int x30 : 1;
        unsigned int x31 : 1;
        BOOL x32[256];
        BOOL x33[256];
        BOOL x34[100];
        unsigned int x35 : 1;
        unsigned int x36 : 1;
        unsigned int x37 : 1;
        double x38;
    } * data;
    [invocation getReturnValue:&data];
    int networkType = data->x13;
    return networkType;
}

之所以结构体内字段没有按照原来的名字,是担心审核不通过。加入这个方法的应用已经在审核中,还没有结果审核通过。需要注意的是这个方法在iOS 11下才可以这样调用。因为返回的结构体在不同的iOS版本中不同。如果希望在各个版本都用这样的方法,那么需要了解所有支持的版本的返回结构体构造。

接下来探索下修改状态栏。用Method Swizzling的方法修改这个方法的返回值。

@implementation NSObject (Hook)

+ (void)load
{
    Method m1 = class_getClassMethod(NSClassFromString(@"UIStatusBarServer"), @selector(getStatusBarData));
    Method m2 = class_getClassMethod(self, @selector(hook_getStatusBarData));
    method_exchangeImplementations(m1, m2);
}

+ (struct StatusBarData *)hook_getStatusBarData
{
    
    struct StatusBarData *ret = [self hook_getStatusBarData];
    ret->dataNetworkType = 3;
    strcpy(ret->breadcrumbTitle, "hahaha");
    strcpy(ret->breadcrumbSecondaryTitle, "huhuhu");
    strcpy(ret->personName, "personName");
    ret->thermalSunlightMode = YES;
    ret->batterySaverModeActive = YES;
    ret->deviceIsRTL = YES;
    ret->batteryState = 3;
    ret->batteryCapacity = 500;
    ret->serviceContentType = 5;
    ret->electronicTollCollectionAvailable = YES;
    return ret;
}

@end

这段代码跑起来,状态栏已经被改得乱七八糟了😝。

Comments

comments powered by Disqus