因收到Google相关通知,网站将会择期关闭。相关通知内容 12 iOS 崩溃千奇百怪,如何全面监控? 你好,我是戴铭。今天我要跟你说的是崩溃监控。 App上线后,我们最怕出现的情况就是应用崩溃了。但是,我们线下测试好好的App,为什么上线后就发生崩溃了呢?这些崩溃日志信息是怎么采集的?能够采集的全吗?采集后又要怎么分析、解决呢? 接下来,通过今天这篇文章,你就可以了解到造成崩溃的情况有哪些,以及这些崩溃的日志都是如何捕获收集到的。 App 上线后,是很脆弱的,导致其崩溃的问题,不仅包括编写代码时的各种小马虎,还包括那些被系统强杀的疑难杂症。 下面,我们就先看看几个常见的编写代码时的小马虎,是如何让应用崩溃的。 数组越界:在取数据索引时越界,App会发生崩溃。还有一种情况,就是给数组添加了 nil 会崩溃。 多线程问题:在子线程中进行 UI 更新可能会发生崩溃。多个线程进行数据的读取操作,因为处理时机不一致,比如有一个线程在置空数据的同时另一个线程在读取这个数据,可能会出现崩溃情况。 主线程无响应:如果主线程超过系统规定的时间无响应,就会被 Watchdog 杀掉。这时,崩溃问题对应的异常编码是0x8badf00d。关于这个异常编码,我还会在后文和你说明。 野指针:指针指向一个已删除的对象访问内存区域时,会出现野指针崩溃。野指针问题是需要我们重点关注的,因为它是导致App崩溃的最常见,也是最难定位的一种情况。关于野指针等内存相关问题,我会在第14篇文章“临近 OOM,如何获取详细内存分配信息,分析内存问题?”里和你详细说明。 程序崩溃了,你的 App 就不可用了,对用户的伤害也是最大的。因此,每家公司都会非常重视自家产品的崩溃率,并且会将崩溃率(也就是一段时间内崩溃次数与启动次数之比)作为优先级最高的技术指标,比如千分位是生死线,万分位是达标线等,去衡量一个App的高可用性。 而崩溃率等技术指标,一般都是由崩溃监控系统来搜集。同时,崩溃监控系统收集到的堆栈信息,也为解决崩溃问题提供了最重要的信息。 但是,崩溃信息的收集却并没有那么简单。因为,有些崩溃日志是可以通过信号捕获到的,而很多崩溃日志却是通过信号捕获不到的。 你可以看一下下面这幅图,我列出了常见的部分崩溃情况: 图1 常见的部分崩溃情况分类 通过这张图片,我们可以看到, KVO问题、NSNotification 线程问题、数组越界、野指针等崩溃信息,是可以通过信号捕获的。但是,像后台任务超时、内存被打爆、主线程卡顿超阈值等信息,是无法通过信号捕捉到的。 但是,只有捕获到所有崩溃的情况,我们才能实现崩溃的全面监控。也就是说,只有先发现了问题,然后才能够分析问题,最后解决问题。接下来,我就一起分析下如何捕获到这两类崩溃信息。 我们先来看看信号可捕获的崩溃日志收集 收集崩溃日志最简单的方法,就是打开 Xcode 的菜单选择 Product -> Archive。如下图所示: 图2 收集崩溃日志最简单的方法 然后,在提交时选上“Upload your app’s symbols to receive symbolicated reports from Apple”,以后你就可以直接在 Xcode 的 Archive 里看到符号化后的崩溃日志了。 但是这种查看日志的方式,每次都是纯手工的操作,而且时效性较差。所以,目前很多公司的崩溃日志监控系统,都是通过PLCrashReporter 这样的第三方开源库捕获崩溃日志,然后上传到自己服务器上进行整体监控的。 而没有服务端开发能力,或者对数据不敏感的公司,则会直接使用 Fabric或者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