iOS App连续闪退检测及对应处理

线上出了一次事故, 在退出登录时, 正常的用户在退出登录时会清除成功userDefault中的数据,一般只会crash一次,再次打开就会正常,但是有个用户比较特殊, 出现了连续闪退, 主要原因就是userDefault中的数据没有清除成功, 下次再启动app, 从userDefault中获取数据还认为是登录中的状态, 然后再次crash, 这样就陷入了死循环,导致只要打开app就会crash, 只有卸载重装才能解决问题.

先来说说为什么这个用户比较特殊, 这是一个公司内部的用户, app一直都是覆盖安装, 随着版本的迭代,迭代了3,4年, userDefault中数据越来越多, 查看此用户的沙盒文件, 系统默认的userDefault大小竟然有20M,文件太大导致退出登录时,清除某个key太慢, 此时发生了crash,userDefault中的数据抹除失败, 陷入死循环.

自己写了一个demo试了一下, 当数据量很大时,确实会出现这样的情况, 前2个key写入成功, 第三个key还是原来的旧值, 即使加上了-synchronize也不管用.

确认了问题, 除了正常解决crash之外, 还想到了2种方式来补救.

NSUserDefault 用法优化

userdefault中不适合存较大数据, 但是业务上也确实需要这么多key来标记用户的某些状态, 比如当天是否签到, 比如是否展示过引导图, ...

发现系统在NSUserDefault中提供其他的初始化方法, 这样就可以按照不同的业务线或者按照功能来区分, 不要一股脑的都往默认的standardUserDefaults写入数据.

- (instancetype)initWithSuiteName:(NSString *)suitename NS_DESIGNATED_INITIALIZER;

比如

// 设置值
    NSUserDefaults *userDefault = [[NSUserDefaults alloc] initWithSuiteName:@"abc"];
    [userDefault setObject:@"1" forKey:@"1"];

// 另一个方法中获取值
    NSUserDefaults *userDefault = [[NSUserDefaults alloc] initWithSuiteName:@"abc"];
    NSString *value = [userDefault objectForKey:@"1"];
    NSLog(@"SuiteName:abc -- %@",value);

这个方法会在和默认的standardUserDefaults平级目录下记录一个文件, 其他用法和standardUserDefaults一模一样. 这样就达到了减少standardUserDefaults大小的目的了.

 连续闪退的优化

这个的做法就是检测到发生连续闪退时, 手动抹掉沙盒路径下的所有文件, 把所有的文件抹除后, 这个app其实就和刚从商店下载的一样了.

首先我们需要一种比较可靠的方式,可以在 app 启动时判断上次是否发生了启动 crash。介绍一个可行的思路。

如何检测连续闪退

连续闪退包含两个元素,闪退和连续。只有这两个元素同时具备时,才会判定为YES。闪退的定义可以简单为连续2次启动的间隔小于5s

app 本次启动时间 -  app上次启动时间 <= 5s (或者其他 threshold)

连续的定义为,至少接连出现两次或者以上。一般 2 次就够了,很多时候用户连续经历两次闪退,就会放弃尝试。

我们可以通过记录若干个特殊的时间点 timestamp 来试图还原 App crash 场景下的生命周期。

  • App 启动 timestamp,定义为 launchTs

    App 每次启动时,记录当前时间,写入时间数组。

  • App 正常退出 timestamp,定义为 terminateTs

    App 在接收到 UIApplicationWillTerminateNotification 通知时,记录当前时间戳,写入时间数组。注意,还有很多种 App 退出行为的时间戳是无法被准确记录的。

之所以要记录 terminateTs,是为了排除一种特殊情况,即用户启动 App 之后立即手动 kill app。如果我们正确记录了上面的时间戳,那么我们可以得到一个与 App crash 行为相关的时间线。比如:

launchTs => launchTs => launchTs

或者

launchTs => launchTs => terminateTs => launchTs

第一种情况下, 如果launch3-launch2<5 && launch2-launch1<5  , 那就判定为触发了2次闪退.

第二种情况, 由于launch2和launch3之间有一个terminateTs, 判定为只触发了一次闪退, 不满足连续闪退的条件.

这个时间线只是记录的 App 启动和退出行为,还有很多特殊的时间点没有记录,比如 App 在 前台发生 out of memory(FOOM),App 在前台 main thread 卡住被系统 Watch Dog 杀掉,iOS 系统升级时 App 被强杀,App 从 AppStore 升级时被强杀等等,这些特殊的时间点都没有记录,不过这些并不影响我们的 App 连续闪退检测,所以可以忽略。

这里指的注意的是,因为启动时要从 disk 读取时间线记录,涉及磁盘读写,会对 App 的启动时间产生影响,一个优化点是有一个独立的USerDefault存贮这些信息,同时在每次写入时间点移除掉较老的 timestamp,比如只记录最近 5 个时间戳。

有了这个判定之后, 在didFinishLaunchingWithOptions中就可以处理连续闪退, 可以弹窗让用户选择,或者到一个新页面进行处理都可以.

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

    if (连续发生闪退) {
        // 弹窗让用户选择是否要抹除app的所有数据,
        // 如果用户选择了YES, 清理沙盒下的所有数据, 并上报服务器
        exit(0);
    }
    // 正常的业务代码

    return YES;
}

最后, 清除沙盒数据只能是解决本地数据导致的问题, 如果是代码本身的问题, 那就是只有通过发版来解决了. 

bugly本身也提供了类似的方法,连续crash3次的会返回YES,  3次感觉有点多了, 可以使用2种方式相结合作为触发连续crash的条件. 如果我们的判断失效, bugly 的方法也可以作为连续闪退修复的触发场景

参考文章: iOS App 连续闪退时如何上报crash日志

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
iOS越狱开发中,常用的Hook检测包括以下几种: 1. 检测当前进程是否被其他进程注入 ``` void anti_injection() { char path[1024]; int ret = proc_pidpath(getpid(), path, sizeof(path)); if (ret <= 0) { NSLog(@"anti_injection: proc_pidpath failed"); exit(1); } if (strstr(path, "/Library/MobileSubstrate") != NULL) { NSLog(@"anti_injection: MobileSubstrate detected"); exit(1); } if (strstr(path, "/Library/Frameworks/CydiaSubstrate.framework") != NULL) { NSLog(@"anti_injection: CydiaSubstrate detected"); exit(1); } } ``` 2. 检测是否被调试 ``` void anti_debugging() { int name[4]; struct kinfo_proc info; size_t info_size = sizeof(info); name[0] = CTL_KERN; name[1] = KERN_PROC; name[2] = KERN_PROC_PID; name[3] = getpid(); if (sysctl(name, 4, &info, &info_size, NULL, 0) == -1) { NSLog(@"anti_debugging: sysctl failed"); exit(1); } if (info.kp_proc.p_flag & P_TRACED) { NSLog(@"anti_debugging: traced"); exit(1); } } ``` 3. 检测是否被hook ``` void anti_hooking() { const char *functionName = "ptrace"; void *handle = dlopen("/usr/lib/libc.dylib", RTLD_GLOBAL | RTLD_NOW); if (handle == NULL) { NSLog(@"anti_hooking: dlopen failed"); exit(1); } void *ptrace_func = dlsym(handle, functionName); if (ptrace_func == NULL) { NSLog(@"anti_hooking: dlsym failed"); exit(1); } if (ptrace_func != (void *)&ptrace) { NSLog(@"anti_hooking: hook detected"); exit(1); } } ``` 4. 检测是否被注入Cycript等调试工具 ``` void anti_cycript() { char *cycript = strstr(getenv("DYLD_INSERT_LIBRARIES"), "cycript"); if (cycript != NULL) { NSLog(@"anti_cycript: cycript detected"); exit(1); } } ``` 此外,还可以在代码中添加闪退检测断点,当程序发生闪退时就可以打断点进行调试,例如: ``` void crash_handler(int signal) { signal(SIGTRAP, NULL); NSLog(@"crash_handler: signal=%d", signal); exit(1); } void set_crash_handler() { signal(SIGTRAP, crash_handler); } ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值