我们经常会遇到APP闪退和崩溃的问题,那么我们应该通过什么变量去监听APP的异常呢?如何在程序崩溃时,保证程序不闪退,并给用户弹出一个提示框呢? 这是本文将要讲述的内容。

先介绍2个概念,Mach异常和Signal信号,如果想要监听异常其实就是去监听Mach异常和Signal信号。其实系统已经给我们提供了一个方法去监听程序产生的异常,通过NSSetUncaughtExceptionHandler(入参是一个C函数)方法就可以直接捕获异常信息。但是这个方法捕获的异常有限,不能捕获由于内存访问错误等产生的signal,所以如果想要监听绝大多数的异常需要我们自己通过注册signal(signal的类型,回调方法)来捕获信号进行监听。

一、Mach异常

Mach是Mac OS和iOS操作系统的微内核核心,Mach异常是指最底层的内核级异常 。每个thread,task都有一个异常端口数组,Mach的部分API暴露给了开发者,开发者可以直接通过Mach API设置thread,task,host的异常端口,来监听捕获Mach异常,抓取Crash事件。所以当APP中产生异常时,最先能监听到异常的就是Mach。

二、Signal信号

最先捕获到异常的Mach在接下来会将所有的异常转换为相应的Unix信号,并投递到出错的线程。之后就可以注册想要监听的signal类型,来捕获信号。如下,就是监听了SIGSEGV信号,当有SIGSEGV信号产生时,就会回调mySignalHandler方法:

signal(SIGSEGV,mySignalHandler)

为什么要将Mach转换成Unix信号呢?主要原因就是为了为了兼容更为流行的POSIX标准(SUS规范),这样不必了解Mach内核也可以通过Unix信号的方式来兼容开发。signal信号处理是UNIX操作系统机制,所以Android平台理论上也是使用的,可以基于signal来捕获Android Native Crash。

什么是Signal?

在计算机科学中,信号(英语:Signals)是Unix、类Unix以及其他POSIX兼容的操作系统中进程间通讯的一种有限制的方式。它是一种异步的通知机制,用来提醒进程一个事件已经发生。当一个信号发送给一个进程,操作系统中断了进程正常的控制流程,此时,任何非原子操作都将被中断。如果进程定义了信号的处理函数,那么它将被执行,否则就执行默认的处理函数。

如何使用Signal?

在项目工程中,要使用 Signal 时,通过引入 signal.h 来使用:

#include <sys/signal.h>

在 sys/signal 文件内定义了大量的系统信号标识

使用这些信号标识,要通过函数 void (*signal(int, void (*)(int)))(int); 来进行使用,如下所示:

//定义一个接收到信号的回调函数
void HandleException(int signo)
{printf("Lanou's sig is:%d",signo);
}
//注册Alerm信号的回调函数
signal(SIGALRM, HandleException);

信号处理函数可以通过 signal() 系统调用来设置。如果没有为一个信号设置对应的处理函数,就会使用默认的处理函数,否则信号就被进程截获并调用相应的处理函数。在没有处理函数的情况下,程序可以指定两种行为:忽略这个信号 SIG_IGN 或者用默认的处理函数 SIG_DFL 。但是有两个信号是无法被截获并处理的: SIGKILL、SIGSTOP 。

Signal信号类型:

  • SIGABRT--程序中止命令中止信号
  • SIGALRM--程序超时信号
  • SIGFPE--程序浮点异常信号
  • SIGILL--程序非法指令信号
  • SIGHUP--程序终端中止信号
  • SIGINT--程序键盘中断信号
  • SIGKILL--程序结束接收中止信号
  • SIGTERM--程序kill中止信号
  • SIGSTOP--程序键盘中止信号 
  • SIGSEGV--程序无效内存中止信号
  • SIGBUS--程序内存字节未对齐中止信号
  • SIGPIPE--程序Socket发送失败中止信号

三、当APP崩溃时做线程保活,弹出程序异常提示框

UncaughtExceptionHandler里面是利用 iOS SDK中提供的现成函数NSSetUncaughtExceptionHandler 加上 注册想要监听的signal类型,来做异常处理的,通过抛出的Signal,专门对Signal处理。

void InstallUncaughtExceptionHandler(void) {NSSetUncaughtExceptionHandler(&HandleException); //系统的方法//添加想要监听的signal类型,当发出相应类型的signal时,会回调SignalHandler方法signal(SIGABRT, SignalHandler);signal(SIGILL, SignalHandler);signal(SIGSEGV, SignalHandler);signal(SIGFPE, SignalHandler);signal(SIGBUS, SignalHandler);signal(SIGPIPE, SignalHandler);
}
void HandleException(NSException *exception)
{// 递增的一个全局计数器,很快很安全,防止并发数太大int32_t exceptionCount = OSAtomicIncrement32(&UncaughtExceptionCount);if (exceptionCount > UncaughtExceptionMaximum) return;// 获取 堆栈信息的数组NSArray *callStack = [UncaughtExceptionHandler backtrace];// 设置该字典NSMutableDictionary *userInfo =[NSMutableDictionary dictionaryWithDictionary:[exception userInfo]];// 给 堆栈信息 设置 地址 Key[userInfosetObject:callStackforKey:UncaughtExceptionHandlerAddressesKey];// 假如崩溃了执行 handleException: ,并且传出 NSException[[[UncaughtExceptionHandler alloc] init] performSelectorOnMainThread:@selector(handleException:) withObject:[NSException exceptionWithName:[exception name] reason:[exception reason]userInfo:userInfo]waitUntilDone:YES];
}
void SignalHandler(int signal)
{// 递增的一个全局计数器,很快很安全,防止并发数太大int32_t exceptionCount = OSAtomicIncrement32(&UncaughtExceptionCount);if (exceptionCount > UncaughtExceptionMaximum) return;// 设置是哪一种 single 引起的问题NSMutableDictionary *userInfo =[NSMutableDictionarydictionaryWithObject:[NSNumber numberWithInt:signal]forKey:UncaughtExceptionHandlerSignalKey];// 获取堆栈信息数组NSArray *callStack = [UncaughtExceptionHandler backtrace];// 写入地址[userInfo setObject:callStack forKey:UncaughtExceptionHandlerAddressesKey];//  假如崩溃了执行 handleException: ,并且传出 NSException[[[UncaughtExceptionHandler alloc] init] performSelectorOnMainThread:@selector(handleException:)withObject: [NSException exceptionWithName:UncaughtExceptionHandlerSignalExceptionName reason: [NSString stringWithFormat:NSLocalizedString(@"Signal %d was raised.", nil),signal]userInfo:[NSDictionary dictionaryWithObject:[NSNumber numberWithInt:signal] forKey:UncaughtExceptionHandlerSignalKey]] waitUntilDone:YES];
}

此处注意OSAtomicIncrement32的使用,它此处是一个递增的一个全局计数器,效果又快又安全,是为了防止并发数太大出现错误的情况。

+ (NSArray *)backtrace
{void* callstack[128];//  该函数用来获取当前线程调用堆栈的信息,获取的信息将会被存放在buffer中(callstack),它是一个指针数组。int frames = backtrace(callstack, 128);//  backtrace_symbols将从backtrace函数获取的信息转化为一个字符串数组.char **strs = backtrace_symbols(callstack, frames);NSMutableArray *backtrace = [NSMutableArray arrayWithCapacity:frames];for (int i = UncaughtExceptionHandlerSkipAddressCount;i < UncaughtExceptionHandlerSkipAddressCount + UncaughtExceptionHandlerReportAddressCount;i++){[backtrace addObject:[NSString stringWithUTF8String:strs[i]]];}free(strs); // 记得freereturn backtrace;
}

这边值得注意的是下面这两个函数方法

int backtrace(void**,int) __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0);
char** backtrace_symbols(void* const*,int) __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0);

该函数用来获取当前线程调用堆栈的信息,并且转化为字符串数组。

最后再来处理,此处涉及到 CFRunLoopRunInMode, kill值得注意!

- (void)handleException:(NSException *)exception
{// 打印或弹出框// TODO :// 接到程序崩溃时的信号进行自主处理CFRunLoopRef runLoop = CFRunLoopGetCurrent();CFArrayRef allModes = CFRunLoopCopyAllModes(runLoop);while (!dismissed){        //循环for (NSString *mode in (NSArray *)allModes) {CFRunLoopRunInMode((CFStringRef)mode, 0.001, false);}}CFRelease(allModes);// 下面等同于清空之前设置的NSSetUncaughtExceptionHandler(NULL);signal(SIGABRT, SIG_DFL);signal(SIGILL, SIG_DFL);signal(SIGSEGV, SIG_DFL);signal(SIGFPE, SIG_DFL);signal(SIGBUS, SIG_DFL);signal(SIGPIPE, SIG_DFL);// 杀死 或 唤起if ([[exception name] isEqual:UncaughtExceptionHandlerSignalExceptionName]) {kill(getpid(), [[[exception userInfo] objectForKey:UncaughtExceptionHandlerSignalKey] intValue]);} else {[exception raise];}
}

当异常产生时,上边代码就可以捕获Crash异常,然后弹出一个程序异常提示框。

注意:NSSetUncaughtExceptionHandler方法在纯Swift工程中不好使,无法监听到异常。

参考文章:

Signal介绍

漫谈iOS Crash收集框架

Mach异常和signal信号

UncaughtExceptionHandler 捕捉异常

iOS中Mach异常和signal信号介绍,以及当APP崩溃时做线程保活弹出程序异常提示框相关推荐

  1. ios swift5 弹出原生的提示框(弹框) UIAlertController

    文章目录 1.从中间弹出,限制textfield的内容长度 1.1 截图 1.2 代码 1.3 参考 2.从下面弹出 2.1 截图 2.2 代码 3.其他举例 3.1 下面只有一个确定按钮(带截图和代 ...

  2. 黄聪:VS2010中如何让webbrowser不弹出JS异常错误窗口(c#.net)

    黄聪:VS2010中如何让webbrowser不弹出JS异常错误窗口(c#.net) 参考文章: (1)黄聪:VS2010中如何让webbrowser不弹出JS异常错误窗口(c#.net) (2)ht ...

  3. Visual Studio2012打开时弹出“遇到异常:这可能是由某个扩展导致的”错误的解决办法...

    Visual Studio2012打开时弹出"遇到异常:这可能是由某个扩展导致的"错误的解决办法: 具体问题如下: 分析原因:网上搜集了以下,出现异常的原因是安装了第三方控件,然后 ...

  4. 计算机窗口弹不出来桌面怎么弄,怎么在电脑中设置不再弹出程序的广告窗口

    怎么在电脑中设置不再弹出程序的广告窗口 有的小伙伴在使用电脑过程中,总是会收到各种软件程序的弹出广告窗口,觉得特别打扰电脑的使用,因此想要设置不再弹出广告窗口,但是却不知道如何设置,那么小编就来为大家 ...

  5. iOS APP在前台收到push,弹出系统框

    在iOS9以前,APP前台可以收到push的数据,但是没有UI展示,需要APP自己展示.iOS 10以后,苹果统一使用 UserNotifications ,以前的API都被标为弃用了. 在appde ...

  6. iOS Mach异常和signal信号

    摘要: 本着探究下iOS Crash捕获的目的,学习了下Crash捕获相关的Mach异常和signal信号处理,记录下相关内容,并提供对应的测试示例代码.Mach为XNU的微内核,Mach异常为最底层 ...

  7. iOS 仿看了吗应用、指南针测网速等常用工具、自定义弹出视图框架、图片裁剪、内容扩展等源码...

    iOS精选源码 扩展内容的cell - folding-cell 一个近乎完整的可识别中国身份证信息的Demo 可自动快速... JPImageresizerView 仿微信的图片裁剪 带年月和至今以 ...

  8. iOS精品源码,GHConsole图片浏览器圆形进度条音视频传输连击礼物弹出动画

    2019独角兽企业重金招聘Python工程师标准>>> 1.可在app中显示的控制台框架GHConsole 2.GKPhotoBrowser--自定义图片浏览器 3.圆形进度条 4. ...

  9. iOS精品源码,GHConsole图片浏览器圆形进度条音视频传输连击礼物弹出动画 1

    1.可在app中显示的控制台框架GHConsole 2.GKPhotoBrowser--自定义图片浏览器 3.圆形进度条 4.音视频实时传输 part2(补充上一贴) 5.RSChat(以前微信写的仿 ...

最新文章

  1. serverlet 区别_Servlet中/和/*的区别
  2. Linux代理后网页显示问题,项目部署到linux后出现的两个问题
  3. Git修改用户名和邮箱的方法(附Git常用命令)
  4. 字符串型String
  5. 漳州java,漳州学java,漳州学java学校,漳州学java效果怎么样
  6. linux下epoll网络编程模型,C++ - 网络编程模型 - Linux EPOLL
  7. 黑客帝国中代码雨如何实现?用 Python 就可以!
  8. 数仓建模—事实表和维度表设计规范
  9. 名师出高徒!请关注领英上这十位活跃的大神
  10. [FMG]ADT-eclipse升级为可以添加javaWeb
  11. 探索--appllo配置中心,如何动态加载配置
  12. C#交错数组和多维数组
  13. OpenCV和JavaCV--安装经验
  14. 下载网易云课堂和B站的视频
  15. win10安装usb转serial驱动(或Prolific USB-to-Serial Comm Port驱动)
  16. 26个英文字母对应数字的奇妙意义
  17. 区块链中的“链上”和“链下”
  18. 如何用python画雪人_pygame画雪人_函数与图形示例.py
  19. 网站空间如何选择?空间选择的标准是什么?
  20. hdu 2191 悼念512汶川大地震遇难同胞——珍惜现在,感恩生活

热门文章

  1. Apple 基于蓝牙的iBeacon技术
  2. 经典解压缩软件 WinRAR 5.71 无广告版
  3. 网上商城SSH三者间的牵线
  4. 每日统计部门人员考勤打卡情况并汇总通知
  5. 1.2 Eight Great Ideas in computer Architecture
  6. 仓库防霉防潮作业指导书
  7. 【软考题目】假设某磁盘的每个磁道划分成11个物理块,每块存放1个逻辑记录。
  8. 影视动画设计有些SCI期刊推荐? - 易智编译EaseEditing
  9. Ls-Dyna对预应力钢筋混凝土结构的抗爆模拟
  10. java实现按比例缩放图片技巧