1. 前言
App 上线后,我们最怕出现的情况就是应用崩溃了。但是,我们线下测试好好的 App,为什么上线后就发生崩溃了呢?
2. 崩溃的几种情况
下面我们就先看看几个常见的编写代码时的小马虎,是如何让应用崩溃的。
野指针问题是我们需要重点关注的,因为它是导致 App 崩溃的最常见,也是最难定位的一种情况。
3. 崩溃信息的收集
注意:程序崩溃了,你的 App 就不可用了,对用户的伤害也是最大的。因此,每家公司都会非常重视自家产品的崩溃率,并且会将崩溃率(也就是一段时间内崩溃次数与启动次数之比)作为优先级最高的技术指标,比如千分位是生死线,万分位是达标线等,去衡量一个 App 的高可用性。
一般都是由崩溃监控系统来搜集。同时,崩溃监控系统收集到的堆栈信息,也为解决崩溃问题提供了最重要的信息。
但是,崩溃信息的收集却并没有那么简单。因为,有些崩溃日志是可以通过信号捕获到的,而很多崩溃日志却是通过信号捕获不到的。
4. 如何全面监控崩溃信息
注意:但是这种查看日志的方式,每次都是纯手工的操作,而且时效性较差。
很多公司的崩溃日志监控系统,都是通过 PLCrashReporter 这样的第三方开源库捕获崩溃日志,然后上传到自己服务器上进行整体监控的。
注意:没有服务端开发能力,或者对数据不敏感的公司,则会直接使用 Fabric 或者Bugly 来监控崩溃。
5. PLCrashReporter、Bugly 怎么监测崩溃
PLCrashReporter 和 Bugly 这类工具,是怎么知道 App 什么时候崩溃的?接下来,我就和你详细分析下。
例如在崩溃日志里,你经常会看到下面这段说明异常信息:
Exception Type: EXC_BAD_ACCESS (SIGSEGV)
注意:它表示的是,EXC_BAD_ACCESS 这个异常会通过 SIGSEGV 信号发现有问题的线程。
虽然信号的种类有很多,但是都可以通过注册 signalHandler 来捕获到。其实现代码,如下所示:
void registerSignalHandler(void) { signal(SIGSEGV, handleSignalException); signal(SIGFPE, handleSignalException); signal(SIGBUS, handleSignalException); signal(SIGPIPE, handleSignalException); signal(SIGHUP, handleSignalException); signal(SIGINT, handleSignalException); signal(SIGQUIT, handleSignalException); signal(SIGABRT, handleSignalException); signal(SIGILL, handleSignalException);}
void handleSignalException(int signal) { NSMutableString *crashString = [[NSMutableString alloc]init]; void* callstack[128]; int i, frames = backtrace(callstack, 128); char** traceChar = backtrace_symbols(callstack, frames); for (i = 0; i <frames; ++i) { [crashString appendFormat:@"%s\n", traceChar[I]]; } NSLog(crashString);}
注意:
上面这段代码对各种信号都进行了注册,捕获到异常信号后,在处理方法 handleSignalException 里通过 backtrace_symbols 方法就能获取到当前的堆栈信息。堆栈信息可以先保存在本地,下次启动时再上传到崩溃监控服务器就可以了。
先将捕获到的堆栈信息保存在本地,是为了实现堆栈信息数据的持久化存储。
注意:这是因为,在保存完这些堆栈信息以后,App 就崩溃了,崩溃后内存里的数据也就都没有了。而将数据保存在本地磁盘中,就可以在 App 下次启动时能够很方便地读取到这些信息。
你是不是经常会遇到这么一种情况,App 退到后台后,即使代码逻辑没有问题也很容易出现崩溃。而且,这些崩溃往往是因为系统强制杀掉了某些进程导致的,而系统强杀抛出的信号还由于系统限制无法被捕获到。
一般,在退后台时你都会把关键业务数据保存在内存中,如果保存过程中出现了崩溃就会丢失或损坏关键数据,进而数据损坏又会导致应用不可用。这种关键数据的损坏会给用户带来巨大的损失。
那就先说说后台保活的 5 种方式:
进程挂起后所有线程都会暂停,不管这个线程是文件读写还是内存读写都会被暂停。但是,数据读写过程无法暂停只能被中断,中断时数据读写异常而且容易损坏文件,所以系统会选择主动杀掉 App 进程。
延长时间的方式:
Background Task 这种方式,就是系统提供了beginBackgroundTaskWithExpirationHandler方法来延长后台执行时间,可以解决你退后台后还需要一些时间去处理一些任务的诉求。
代码如下:
- (void)applicationDidEnterBackground:(UIApplication *)application { __block UIBackgroundTaskIdentifier background_task; background_task = [application beginBackgroundTaskWithExpirationHandler: ^{ NSLog(@"task expired..."); [application endBackgroundTask:background_task]; background_task = UIBackgroundTaskInvalid; }];}
在这段代码中,yourTask 任务最多执行 3 分钟,3 分钟内 yourTask 运行完成,你的 App 就会挂起。
如果 yourTask在3分钟之内没有执行完的话,系统会强制杀掉进程,从而造成崩溃,这就是为什么 App 退后台容易出现崩溃的原因。
后台崩溃造成的影响是未知的。持久化存储的数据出现了问题,就会造成你的 App 无法正常使用。
你知道了, App 退后台后,如果执行时间过长就会导致被系统杀掉。那么,如果我们要想避免这种崩溃发生的话,就需要严格控制后台数据的读写操作。
比如,你可以先判断需要处理的数据的大小,如果数据过大,也就是在后台限制时间内或延长后台执行时间后也处理不完的话,可以考虑在程序下次启动或后台唤醒时再进行处理。
同时,App 退后台后,这种由于在规定时间内没有处理完而被系统强制杀掉的崩溃,是无法通过信号被捕获到的。这也说明了,随着团队规模扩大,要想保证 App 高可用的话,后台崩溃的监控就尤为重要了。
注意:那么,我们又应该怎么去收集退后台后超过保活阈值而导致信号捕获不到的那些崩溃信息呢?
采用 Background Task 方式时,我们可以根据beginBackgroundTaskWithExpirationHandler 会让后台保活3分钟这个阈值,先设置一个计时器,在接近3分钟时判断后台程序是否还在执行。如果还在执行的话,我们就可以判断该程序即将后台崩溃,进行上报、记录,以达到监控的效果。
其他捕获不到的崩溃情况还有很多,主要就是:
监控这两类崩溃的思路和监控后台崩溃类似,我们都先要找到它们的阈值,然后在临近阈值时还在执行的后台程序,判断为将要崩溃,收集信息并上报。
通过上面的内容,我们已经解决了崩溃信息采集的问题。现在,我们需要对这些信息进行分析,
进而解决 App 的崩溃问题。 我们采集到的崩溃日志,主要包含的信息为:
通常情况下,我们分析崩溃日志时最先看的是异常信息,分析出问题的是哪个线程,在线程回溯里找到那个线程; 然后,分析方法调用栈,符号化后的方法调用栈可以完整地看到方法调用的过程,从而知道问题发生在哪个方法的调用上。
方法调用栈顶,就是最后导致崩溃的方法调用。完整的崩溃日志里,除了线程方法调用栈还有异常编码。异常编码,就在异常信息里。
一些被系统杀掉的情况,我们可以通过异常编码来分析。 可以网上搜索一下44种异常编码,但常见的就是如下是三种:
0x8badf00d 这种情况是出现最多的。当出现被 watchdog 杀掉的情况时,我们就可以把范围控制在主线程被卡的情况。例如通过runloop原理去监控卡顿,获取堆栈信息。
0xdeadfa11 的情况,是用户的主动行为,我们不用太关注。
0xc00010ff 这种情况,就要对每个线程 CPU 进行针对性的检查和优化.例如:减少App的电量消耗。
除了崩溃日志外,崩溃监控平台还需要对所有采集上来的日志进行统计。我以腾讯的 Bugly 平台为例,和你一起看一下崩溃监控平台一般都会记录哪些信息,来辅助开发者追溯崩溃问题。
除了崩溃率,你还可以在这个平台上能查看次数、用户数等趋势。下图展示的是某一个 App 的崩溃在不同 iOS 系统、不同 iPhone 设备、App 版本的占比情况。这也是全局大盘观察,从不同维度来分析。
App崩溃在不通过的系统版本、设备、版本的占比.png
有了全局大盘信息,一旦出现大量崩溃,你就需要明白是哪些方法调用出现了问题,需要根据影响的用户数量按照从大到小的顺序排列出来,优先解决影响面大的问题。如下图所示:
同时,每个崩溃也都有自己的崩溃趋势图、iOS 系统分布图等信息,来辅助开发者跟踪崩溃修复效果。
有了崩溃的方法调用堆栈后,大部分问题都能够通过方法调用堆栈,来快速地定位到具体是哪个方法调用出现了问题。有些问题仅仅通过这些堆栈还无法分析出来,这时就需要借助崩溃前用户相关行为和系统环境状况的日志来进行进一步分析。
5. 小结
学习完今天的这篇文章,我相信你就不再是只能依赖现有工具来解决线上崩溃问题的 iOS 开发者了。在遇到那些工具无法提供信息的崩溃场景时,你也有了自己动手去收集崩溃信息的能力。
如果觉得不错,素质三连、或者点个「赞」、「在看」都是对笔者莫大的支持,谢谢各位大佬啦~
推荐阅读
如有问题请留言或扫码加微信交流
公众号推荐:Swift 社区
扫码关注腾讯云开发者
领取腾讯云代金券
Copyright © 2013 - 2025 Tencent Cloud. All Rights Reserved. 腾讯云 版权所有
深圳市腾讯计算机系统有限公司 ICP备案/许可证号:粤B2-20090059 深公网安备号 44030502008569
腾讯云计算(北京)有限责任公司 京ICP证150476号 | 京ICP备11018762号 | 京公网安备号11010802020287
Copyright © 2013 - 2025 Tencent Cloud.
All Rights Reserved. 腾讯云 版权所有