iOS 应用崩溃日志分析

经过本教程,你将学习到一些常见的崩溃日志案例,以及如何从开发设备和iTunes Connect上获取崩溃日志文件。你还将学习到符号化( symbolication),从日志追踪到代码 。你还将学习调试一个在待定状况下会闪退的应用。
 
让咱们开始动手吧!
 
什么是崩溃日志,从哪里能得它?
iOS设备上的应用闪退时,操做系统会生成一个崩溃报告,也叫崩溃日志,保存在设备上。
 
崩溃日志上有不少有用的信息,包括应用是什么状况下闪退的。一般,上面有每一个正在执行线程的完整堆栈跟踪信息,因此你能从中了解到闪退发生时各线程都在作什么,并分辨出闪退发生在哪一个线程上。
 
有几种方法能够从设备上获取崩溃日志。
 
设备与电脑上的iTunes Store同步后,会将崩溃日志保存在电脑上。根据电脑操做系统的不一样,崩溃日志将保存在如下位置:
Mac OS X:~/Library/Logs/CrashReporter/MobileDevice/
 
Windows XP: C:Documents and Settings<USERNAME>Application DataApple ComputerLogsCrashReporterMobileDevice<DEVICE_NAME>
 
Windows Vista or 7:  C:Users<USERNAME>AppDataRoamingApple ComputerLogsCrashReporterMobileDevice<DEVICE_NAME>
 
当用户抱怨闪退时,你能够要求他让设备与iTunes同步,并根据操做系统的不一样,到上述位置把崩溃日志下载下来,而后经过电子邮件发送给你。
 
你必需尽可能获取用户设备生成的全部崩溃日志。由于崩溃日志越多,就越容易诊断问题所在!
 
另外,若是你装了Xcode,也能很容易经过Xcode从你的设备上得到崩溃日志。将iOS设备链接到电脑上,而后打开Xcode。从菜单栏上选择 Window  菜单, 而后选择 Organizer (快捷方式是 Shift-CMD-2).
在 Organizer 窗口上, 选中 Devices 标签栏. 在左侧的导航面板上,选中 Device Logs, 以下图所示:
 
看看上图,左侧有好几个 Device Logs 菜单项。 LIBRARY 下面的Device Logs是你全部设备(曾经链接到Xcode的)的日志 。每一个设备下面的 Device Logs 是对应设备的日志。
 
应用提交到App Store后,你也能从 iTunes Connect 获取到用户的崩溃日志. 登陆到 iTunes Connect 上, 选择Manage Your Applications, 点击相应的应用, 点击应用图标下面的 View Details 按钮, 而后点击右栏Links部分的  Crash Reports 。
若是没有崩溃日志,试试点击Refresh 按钮刷新一下。若是你的应用还卖得很少,或者刚上架不久,iTunes Connect帐号上也可能尚未任何崩溃日志。
 
若是iTunes Connect上有崩溃日志,你将看到以下图:
 
有时,尽管有用户报告闪退,你仍然看不到崩溃报告。这时,最好让用户直接把崩溃报告发送给你。
 
什么状况下会产生崩溃日志?
两种主要状况会产生崩溃日志:
1.应用违反操做系统规则。
2.应用中有Bug。
 
违反iOS规则包括在启动、恢复、挂起、退出时watchdog超时、用户强制退出和低内存终止。让咱们详细了解一下吧。
 
Watchdog 超时机制
从iOS 4.x开始,退出应用时,应用不会当即终止,而是退到后台。可是,若是你的应用响应不够快,操做系统有可能会终止你的应用,并产生一个崩溃日志。这些事件与下列UIApplicationDelegate方法相对应:
application:didFinishLaunchingWithOptions:
applicationWillResignActive:
applicationDidEnterBackground:
applicationWillEnterForeground:
applicationDidBecomeActive:
applicationWillTerminate:
 
上面全部这些方法,应用只有有限的时间去完成处理。若是花费时间太长,操做系统将终止应用。
 
注意: 若是你没有把须要花费时间比较长的操做(如网络访问)放在后台线程上就很容易发生这种状况。关于若是避免这种状况的信息,请参考咱们的另外两篇教程: Grand Central Dispatch 和 NSOperations。
 
用户强制退出
iOS 4.x开始支持多任务。若是应用阻塞界面并中止响应, 用户能够经过在主屏幕上双击Home按钮来终止应用。此时,操做应用将生成一个崩溃日志。
 
注意: 双击Home按钮后,你将看到运行过的全部应用。那些应用不必定是正在运行,也不必定是被挂起。
 
一般,用户点击Home按钮时,应用将在后台保留约10分钟,而后操做系统自动将其终止。 因此双击Home按钮显示的应用列表只是代表那是一系列过去打开过的应用。删除那些应用的图标不会产生任何崩溃日志。
 
低内存终止
子类化UIViewController时,你或许已经注意到didReceiveMemoryWarning方法。
 
在前台运行的应用拥有访问和使用内存的最高优化级。然而,这并不意味着该应用能使用设备的全部可用内存 ——每一个应用只能使用一部分可用内存。
 
当内存使用达到必定程度时,操做系统将发出一个 UIApplicationDidReceiveMemoryWarningNotification 通知。同时,调用 didReceiveMemoryWarning 方法。
 
此时,为了让应用继续正常运行,操做系统开始终止在后台的其余应用以释放一些内存。全部后台应用被终止后,若是你的应用还须要更多内存,操做系统会将你的应用也终止掉,并产生一个崩溃日志。而在这种状况下被终止的后台应用,不会产生崩溃日志。
 
注意: 根据 苹果文档, Xcode不会自动添加低内存日志。你必需手动获取日志。 然而,根据个人我的经验,使用 Xcode 4.5.2, 低内存日志也会自动导入,只是”Process”和”Type”属性都被标为Unknown(未知)。
 
另外,值得一提的是在极短期内分配一大块内存将给系统内存带来巨大负担。这样,也会产生内存警告的通知。
 
应用中有Bug
如你所想,大多数闪退都是因为应用中有Bug,所以大多数崩溃日志的产生都是由于应用中的Bug。Bug的种类的有不少。
 
在本教程的后半部分,你将经过调试一个会产生崩溃日志的含有Bug的应用,学习如何找到问题所在并进行修复!
 
崩溃日志的实例
让咱们看看一个崩溃日志的实例,以使你在处理一些实际问题以前内心有谱。
 
事不宜迟,见见你的新朋友吧:
 
这报告看起来像天书。:) 咱们分几部分来解读吧:
 
(1) 进程信息
第一部分是闪退进程的相关信息。
 
Incident Identifier是崩溃报告的惟一标识符。
 
CrashReporter Key 是与设备标识相对应的惟一键值。虽然它不是真正的设备标识符,但也是一个很是有用的情报:若是你看到100个崩溃日志的CrashReporter Key值都是相同的,或者只有少数几个不一样的CrashReport值,说明这不是一个广泛的问题,只发生在一个或少数几个设备上。
 
Hardware Model 标识设备类型。 若是不少崩溃日志都是来自相同的设备类型,说明应用只在某特定类型的设备上有问题。上面的日志里,崩溃日志产生的设备是iPhone 4s。
 
Process 是应用名称。中括号里面的数字是闪退时应用的进程ID。
 
接下来几行不言自明,无需赘述。
 
(2) 基本信息
这部分给出了一些基本信息,包括闪退发生的日期和时间,设备的iOS版本。若是有不少崩溃日志都来自iOS 6.0,说明问题只发生在iOS 6.0上。
 
(3) 异常
在这部分,你能够看到闪退发生时抛出的异常类型。还能看到异常编码和抛出异常的线程。根据崩溃报告类型的不一样,在这部分你还能看到一些另外的信息。
 
(4) 线程回溯
这部分提供应用中全部线程的回溯日志。 回溯是闪退发生时全部活动帧清单。它包含闪退发生时调用函数的清单。看下面这行日志:
 
它包括四列:
帧编号—— 此处是2。
二进制库的名称 ——此处是 XYZLib.
调用方法的地址 ——此处是 0x34648e88.
第四列分为两个子列,一个基本地址和一个偏移量。此处是0×83000 + 8740, 第一个数字指向文件,第二个数字指向文件中的代码行。
 
(5) 线程状态
这部分是闪退时寄存器中的值。通常不须要这部分的信息,由于回溯部分的信息已经足够让你找出问题所在。
 
(6) 二进制映像
这部分列出了闪退时已经加载的二进制文件。
 
符号化Symbolication
第一次看到崩溃日志上的回溯时,你或许会以为它没什么意义。咱们习惯使用方法名和行数,而非像这样的神秘位置:
将这些十六进制地址转化成方法名称和行数的过程称之为符号化。
 
从Xcode的Organizer窗口获取崩溃日志后过几秒钟,崩溃日志将被自动符号化。上面那行被符号化后的版本以下 :
Xcode符号化崩溃日志时,须要访问与App Store上对应的应用二进制文件以及生成二进制文件时产生的 .dSYM 文件。必需彻底匹配才行。不然,日志将没法被彻底符号化。
 
因此,保留每一个分发给用户的编译版本很是重要。提交应用前进行归档时,Xcode将保存应用的二进制文件。能够在Xcode Organizer的Archives标签栏下找到全部已归档的应用文件。
 
在发现崩溃日志时,若是有相匹配的.dSYM文件和应用二进制文件,Xcode会自动对崩溃日志进行符号化。若是你换到别的电脑或建立新的帐户,务必将全部二进制文件移动到正确的位置,使Xcode能找到它们。
 
注意: 你必需同时保留应用二进制文件和.dSYM文件才能将崩溃日志完整符号化。每次提交到iTunes Connect的构建都必需归档。
 
.dSYM文件和二进制文件是特定绑定于每一次构建和后续构建的,即便来自相同的源代码文件,每一次构建也与其余构建不一样,不能相互替换。
 
若是你使用Build 和 Archive 命令,这些文件会自动放在适当位置。 若是不是使用Build 和 Archive命令,放在Spotlight可以搜索到的位置(好比Home目录)便可。)
 
低内存闪退
由于低内存崩溃日志与普通崩溃日志略有不一样,因此本教程特别分开说明一下。
 
iOS设备检测到低内存时,虚拟内存系统发出通知请求应用释放内存。这些通知发送到全部正在运行的应用和进程,试图收回一些内存。
 
若是内存使用依然居高不下,系统将会终止后台线程以缓解内存压力。若是可用内存足够,应用将可以继续运行而不会产生崩溃报告。不然,应用将被iOS终止,并产生低内存崩溃报告。
 
低内存崩溃日志上没有应用线程的堆栈回溯。相反,上面显示的是之内存页数为单位的各进程内存使用量。(在撰写本文的时候,一个内存页的大小是4KB。)
 
被iOS因释放内存页终止的进程名称后面你会看到jettisoned 字样。若是看到它出如今你的应用名称后面,说明你的应用因使用太多内存而被终止了。
 
低内存崩溃日志看起来像这样:
 
当应用发生低内存闪退时,你必需看看应用中内存使用的方式,以及是如何处理低内存警告的。你可使用Instruments工具中使用Allocations 和 Leaks来发现内存分配问题和内存泄漏问题。若是你不知道如何利用 Instruments 检查内存问题,能够看看这个教程 。
 
还有,别忘记虚拟内存! Instruments工具的Leaks 和 Allocations 不能跟踪显存使用状况。必需使用 VM Tracker 才能查看显存使用状况。
 
VM Tracker 默认是关闭的。打开Instrument,手动 选中Automatic Snapshotting 标志或者按下Snapshot Now 按钮。
 
本教程后面将会学习如何研究低内存崩溃日志。
 
异常编码
在研究真实闪退场景以前,还有一点须要重点介绍一下:就是那些有趣的异常编码 。
 
你能够在报告的异常部分——前面代码的第3部分找到异常编码。有些编码比较常见。
 
一般,异常编码以一些文字开头,紧接着是一个或多个十六进制值,此数值正是说明闪退根本性质的所在。  从这些编码中,能够区分出闪退是由于程序错误、非法内存访问或者是其余缘由。
下面是一些常见的异常编码:
 
0x8badf00d: 读作 “ate bad food”! (把数字换成字母,是否是很像 :p)该编码表示应用是由于发生watchdog超时而被iOS终止的。  一般是应用花费太多时间而没法启动、终止或响应用系统事件。
0xbad22222: 该编码表示 VoIP 应用由于过于频繁重启而被终止。
0xdead10cc: 读作 “dead lock”!该代码代表应用由于在后台运行时占用系统资源,如通信录数据库不释放而被终止 。
0xdeadfa11: 读作 “dead fall”! 该代码表示应用是被用户强制退出的。根据苹果文档, 强制退出发生在用户长按开关按钮直到出现 “滑动来关机”, 而后长按 Home按钮。强制退出将产生 包含0xdeadfa11 异常编码的崩溃日志, 由于大多数是强制退出是由于应用阻塞了界面。
 
注意: 在后台任务列表中关闭已挂起的应用不会产生崩溃日志。 一旦应用被挂起,它什么时候被终止都是合理的。因此不会产生崩溃日志。)
 
大展身手的时候到了!好了! 你已经学习了全部分析崩溃日志和修复错误的基础知识!
 
假设你刚进入Rage-O-Rage有限公司工做。该公司有一个在App Store上热销的应用,叫 Rage Masters。
 
你的老板安迪要你帮忙解决几个用户常常抱怨闪退问题。你的任务就是研究这些闪退,符号化用户提供的崩溃日志,查找问题所在,并修复之。
 
你能够从 这里下载应用的源代码。
 
注意: 若是你想本身从新生成崩溃报告,请遵守如下指引:
1.下载源码而后在Xcode中打开工程文件。
2.使用正确的provisioning profile链接到iOS设备。
3.从Xcode工具栏上选择iOS设备——不是模拟器做为target,而后构建应用。
4.当你在设备上到默认页面(应用的全屏图片)时,当即在Xcode上点击中止按钮。
5.关闭 Xcode。
6.在设备上直接打开应用。
7.测试场景,完成后链接设备到电脑上,经过Xcode获取崩溃日志。)
 
场景 1: 糟糕的代码
一封来自用户的邮件: “大哥,你的应用就是一坨屎! 我将其下载到我本身的iPod Touch和iPhone上,还下载到我儿子的iPod Touch上。在全部的设备上,都是还没打开就闪退了……”
 
别一封来自用户的邮件说, “我下载了大家的应用,一打开就闪退。真悲催…”
 
另外一封邮件说得更明确:”大家的应用不能运行。我把它下载到我和妻子的设备上。全部设备都是 一打开就闪退了…”
好吧,别灰心! 这些意见藏着什么玄机呢?让咱们看看崩溃日志吧:
 
发现问题了吗? 异常编码是0x000000008badf00d,还有后面的报告:
 
这说明应用在启动时就闪退了,iOS的watchdog机制终止了应用。帅! 找到问题了,可是为什会发生这样的事呢?
 
接着往下看日志。 从下向上读回溯日志。最底下的帧 (frame 25: libdyld.dylib)是最早调用的,而后是帧24, Rage Masters, main (main.m:16) ,依此类推。
 
跟应用源代码相关的帧是最重要的。忽略掉系统库和框架。下一个与代码相关的帧是:
 
应用在执行RMAppDelegate (RMAppDelegate.m:35)类application:didFinishLaunchingWithOptions: 方法第35 行代码时闪退。打开Xcode看看那行代码:
 
就是它了! 同步调用web服务?! 在主线程上?! 在 application:didFinishLaunchingWithOptions: 方法上?!! 谁写的代码呀?!
Network calls on the main thread makes kittens sad.
 
无论如何,问题得你来修复了。这个调用必需异步进行,甚至更理想的状况是,在application:didFinishLaunchingWithOptions:返回YES以后的其余部分再执行Web服务。
 
在其余地方调用可能须要比较多的修改。当下,咱们只要使应用不闪退就行。能够在往后再实现更好的设计。 将上面那行讨厌的代码(及其下面的三行代码)换成下面这个异步的版本吧:
 
  场景 2: 没法响应事件的按钮
一名用户说: “我不能将某个rage master添加到书签里面。我想添加的时候应用就闪退…”
 
用一名用户说 :”书签不能用 … 在详细页面上,点击书签按钮,应用就闪退了!”
 
上面的抱怨说得不是很清楚,引发问题的缘由确定有多样。看看崩溃日志:
 
异常代码是SIGABRT。一般,  SIGABRT 异常是因为某个对象接收到未实现的消息引发的。 或者,用简单的话说,在某个对象上调用了不存在的方法。
 
这种状况通常不会发生,由于A对象调用了B方法,若是B方法不存在,编译器会报错。可是,若是你是使用selector间接调用方法的,编译器则没法检测对象是否存在该方法了。
 
回到崩溃日志。它指出闪退发生在编号为0的线程上。 这意味着极可能是在主线程上调用了某个对象没有实现的方法。
 
若是你接着阅读回溯日志,会发现跟你的代码相关的只有帧22, main.m:16. 这没有多大帮助。 
 
继续向上查看框架调用,出现这个:
这不是你本身写的代码。但至少它确认了是对象调用了一个没有实现的方法。
 
回到RMDetailViewController.m文件, 由于那是书签按钮实现动做的地方。 找到书签功能代码:
 
看起来没什么问题,再检查一下storyboard (XIB文件) ,确认按钮链接的正确性。
就是它了! 在 MainStoryboard.storyboard,按钮链接的是 bookmarkButtonPressed: 而不是bookmarkButtonPressed (注意后面的分号说明方法有一个参数)。 只要将上面的方法签名修改为这样就能修复问题了:
 
固然,你也能够简单地在XIB文件上删除错误的链接,而后从新链接方法,使XIB文件链接到正确的方法上。二者方法都行。
 
又处理了一个闪退问题,好样的。
 
场景 3: 表格上的Bug
另外一用户抱怨道, “在书签视图上没法删除书签…” 还有另外一用户抱怨一样的问题, “当我试图删除书签时,应用闪退…”
 
这些邮件没什么做用,仍是看看崩溃日志!
 
这看起来跟前面那个崩溃日志很像。是另外一个SIGABRT 异常。 你可能想知道是不是相同的问题:发送信息到一个没有实现相应方法的对象?
 
让咱们从回溯日志看看哪些方法被调用了。从底部开始,你的源代码最后被调用的是帧 6:
这是UITableViewDataSource 的一个方法. 呵呵?! 毫无疑问苹果已经实现了该方法 —— 你能够重载它, 但不像是尚未实现。并且,这是个可选的委派方法。 因此问题不是调用了一个没有实现的方法。
再看看上面的几个帧:
 
帧 5, UITableView调用了它本身的另外一个方法 deleteRowsAtIndexPaths:withRowAnimation: 而后是看起来像苹果内部方法的_endCellAnimationsWithContext: 被调用。而后Foundation framework发生异常handleFailureInMethod:object:file:lineNumber:description:.
 
这些分析结合用户的抱怨,看起来是你在处理UITableView删除行过程当中有Bug。回到Xcode。你知道看哪里吗 ? 能从崩溃日志中判断出来? 就是RMBookmarksViewController.m文件的第68行:
 
发现问题了吗? 给你点时间,仔细看一下。
 
找到了吧! 数据源呢? 代码在表格视图上删除了一行,但并无修改背后的数据源。把上面的代码替换成下面的就能修复问题了:
 
搞定了!走起,讨厌的 bug!!
 
场景 4: 吃棒棒糖时闪退!
用户邮件说, “当rage master吃棒棒糖时应用就闪退…” 另外一用户说, “我让rage master 吃棒棒糖,没几回应用就闪退了!”
 
崩溃日志以下:
 
这日志跟咱们前面见到的相差不少。
 
这个一个来自iOS 6的低内存崩溃日志。正如咱们前面所说的,低内存崩溃日志与其余类型的崩溃日志很不同,它们不指向特定的文件和代码行。相反,它们画出了闪退时设备上的内存使用状况的图表。
 
至少,头部仍是跟其余崩溃日志很像的:  提供了 Incident Identifier, CrashReporter Key, Hardware Model, OS Version等信息。
 
接下来部分是低内存崩溃日志特有的:
Free pages 指可用内存页数。每页大小约是4KB, 上面的日志中,可用内存约为3,872 KB (或者说 3.9 MB)。
Purgeable pages 是那部分可被清除或重用的内存。在上面的日志中,是0KB。
Largest process是闪退时使用大部份内存的应用名称,在上面的日志中,正是你的应用!
Processes显示了闪退时各进程列表,还包含内存使用量。包含进程名 (第一列), 进程惟一标识符(第二名), 进程使用的内存页数(第三列)。最后一列是每一个应用的状态。一般,发生闪退的应用的状态是 frontmost。 这里是 Rage Masters, 使用28591 页 (or 114.364 MB) 内存——这内存太多了!
 
经过,最大进程和frontmost状态的应用是相同的, 并且也是引发低内存闪退的应用进程。可是也可能看到最大进程和 frontmost状态应用不一样的例子。好比,若是最大进程是SpringBoard, 忽略它 , 由于 SpringBoard 进程是显示主屏幕的应用,出如今你双击home按钮等状况,并且它是一直活动的。
 
低内存发生时,iOS向活动的应用发出低内存警告并终止后台应用。若是前台应用仍然继续增加内存,iOS将终止它。
 
为了查找低内存问题的缘由,你必需使用Instruments剖析应用。若是你不知道怎么作,能够看一下咱们 一篇关于这个方面的教程.。 :] 另外, 你也能够走捷径,响应低内存警告通知,以解决部分闪退问题。
回到Xcode查看RMLollipopLicker.m文件。 这是实现吃棒棒糖的视图控制器。看看源代码:
 
当用户点击运行按钮, 应用开始一个背景线程,调用 lickLollipop 方法若干次,而后更新界面反映吃棒棒糖的数量。 lickLollipop 方法从属性列表文件(PLIST)文件读取一个长字符串,而后添加到数组上。这些数据并不重要, 能在不影响用户体验的前提下从新建立。
 
利用每种可以清除和重建数据而不影响用户体验的状况是好习惯。这样可以方便地释放内存,减小低内存警告。
 
那么,如何提升代码质量呢? 实现 didReceiveMemoryWarning 方法,像下面这样处理数据:
 

 
经过崩溃捕获和收集,能够收集到已发布应用(游戏)的异常,以便开发人员发现和修改bug,对于提升软件质量有着极大的帮助。本文介绍了iOS和android平台下崩溃捕获和收集的原理及步骤,不过若是是我的开发应用或者没有特殊限制的话,就不用往下看了,直接把友盟sdk(一个统计分析sdk)加入到工程中就万事大吉了,其中的错误日志功能彻底可以知足需求,并且不须要额外准备接收服务器。  可是若是你对其原理更感兴趣,或者像我同样必需要兼容公司现有的bug收集系统,那么下面的东西就值得一看了。

       要实现崩溃捕获和收集的困难主要有这么几个:php

       一、如何捕获崩溃(好比c++常见的野指针错误或是内存读写越界,当发生这些状况时程序不是异常退出了吗,咱们如何捕获它呢)java

       二、如何获取堆栈信息(告诉咱们崩溃是哪一个函数,甚至是第几行发生的,这样咱们才可能重现并修改问题)linux

       三、将错误日志上传到指定服务器(这个最好办)android

        咱们先进行一个简单的综述。会引起崩溃的代码本质上就两类,一个是c++语言层面的错误,好比野指针,除零,内存访问异常等等;另外一类是未捕获异常(Uncaught Exception),iOS下面最多见的就是objective-c的NSException(经过@throw抛出,好比,NSArray访问元素越界),android下面就是java抛出的异常了。这些异常若是没有在最上层try住,那么程序就崩溃了。  不管是iOS仍是android系统,其底层都是unix或者是类unix系统,对于第一类语言层面的错误,能够经过信号机制来捕获(signal或者是sigaction,不要跟qt的信号插槽弄混了),即任何系统错误都会抛出一个错误信号,咱们能够经过设定一个回调函数,而后在回调函数里面打印并发送错误日志。c++

      1、iOS平台的崩溃捕获和收集web

一、设置开启崩溃捕获objective-c

 

[cpp]  view plain copy
  1. static int s_fatal_signals[] = {  
  2.     SIGABRT,  
  3.     SIGBUS,  
  4.     SIGFPE,  
  5.     SIGILL,  
  6.     SIGSEGV,  
  7.     SIGTRAP,  
  8.     SIGTERM,  
  9.     SIGKILL,  
  10. };  
  11.   
  12. static const char* s_fatal_signal_names[] = {  
  13.     "SIGABRT",  
  14.     "SIGBUS",  
  15.     "SIGFPE",  
  16.     "SIGILL",  
  17.     "SIGSEGV",  
  18.     "SIGTRAP",  
  19.     "SIGTERM",  
  20.     "SIGKILL",  
  21. };  
  22.   
  23. static int s_fatal_signal_num = sizeof(s_fatal_signals) / sizeof(s_fatal_signals[0]);  
  24.   
  25. void InitCrashReport()  
  26. {  
  27.         // 1     linux错误信号捕获  
  28.     for (int i = 0; i < s_fatal_signal_num; ++i) {  
  29.         signal(s_fatal_signals[i], SignalHandler);  
  30.     }  
  31.       
  32.         // 2      objective-c未捕获异常的捕获  
  33.     NSSetUncaughtExceptionHandler(&HandleException);  
  34. }  

在游戏的最开始调用InitCrashReport()函数来开启崩溃捕获。  注释1处对应上文所说的第一类崩溃,注释2处对应objective-c(或者说是UIKit Framework)抛出可是没有被处理的异常。数据库

二、打印堆栈信息数组

 

[cpp]  view plain copy
  1. + (NSArray *)backtrace  
  2. {  
  3.     void* callstack[128];  
  4.     int frames = backtrace(callstack, 128);  
  5.     char **strs = backtrace_symbols(callstack, frames);  
  6.       
  7.     int i;  
  8.     NSMutableArray *backtrace = [NSMutableArray arrayWithCapacity:frames];  
  9.     for (i = kSkipAddressCount;  
  10.          i < __min(kSkipAddressCount + kReportAddressCount, frames);  
  11.          ++i) {  
  12.         [backtrace addObject:[NSString stringWithUTF8String:strs[i]]];  
  13.     }  
  14.     free(strs);  
  15.       
  16.     return backtrace;  
  17. }  

幸亏,苹果的iOS系统支持backtrace,经过这个函数能够直接打印出程序崩溃的调用堆栈。优势是,什么符号函数表都不须要,也不须要保存发布出去的对应版本,直接查看崩溃堆栈。缺点是,不能打印出具体哪一行崩溃,不少问题知道了是哪一个函数崩的,可是仍是查不出是由于什么崩的

 

三、日志上传,这个须要看实际需求,好比咱们公司就是把崩溃信息http post到一个php服务器。这里就很少作声明了。服务器

四、技巧---崩溃后程序保持运行状态而不退出

 

[cpp]  view plain copy
  1. CFRunLoopRef runLoop = CFRunLoopGetCurrent();  
  2.     CFArrayRef allModes = CFRunLoopCopyAllModes(runLoop);  
  3.       
  4.     while (!dismissed)  
  5.     {  
  6.         for (NSString *mode in (__bridge NSArray *)allModes)  
  7.         {  
  8.             CFRunLoopRunInMode((__bridge CFStringRef)mode, 0.001, false);  
  9.         }  
  10.     }  
  11.       
  12.     CFRelease(allModes);  

在崩溃处理函数上传完日志信息后,调用上述代码,能够从新构建程序主循环。这样,程序即使崩溃了,依然能够正常运行(固然,这个时候是处于不稳定状态,可是因为手持游戏和应用大可能是短时间操做,不会有挂机这种说法,因此稳定与否就可有可无了)。玩家甚至感觉不到崩溃。

 

这里要在说明一个感念,那就是“可重入(reentrant)”。简单来讲,当咱们的崩溃回调函数是可重入的时候,那么再次发生崩溃的时候,依然能够正常运行这个新的函数;可是若是是不可重入的,则没法运行(这个时候就完全死了)。要实现上面描述的效果,而且还要保证回调函数是可重入的几乎不可能。因此,我测试的结果是,objective-c的异常触发多少次均可以正常运行。可是若是屡次触发错误信号,那么程序就会卡死。  因此要慎重决定是否要应用这个技巧。

相关文章
相关标签/搜索