iOS中Mach异常和signal信号介绍,以及当APP崩溃时做线程保活弹出程序异常提示框
我们经常会遇到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 来使用:
在 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崩溃时做线程保活弹出程序异常提示框相关推荐
- ios swift5 弹出原生的提示框(弹框) UIAlertController
文章目录 1.从中间弹出,限制textfield的内容长度 1.1 截图 1.2 代码 1.3 参考 2.从下面弹出 2.1 截图 2.2 代码 3.其他举例 3.1 下面只有一个确定按钮(带截图和代 ...
- 黄聪:VS2010中如何让webbrowser不弹出JS异常错误窗口(c#.net)
黄聪:VS2010中如何让webbrowser不弹出JS异常错误窗口(c#.net) 参考文章: (1)黄聪:VS2010中如何让webbrowser不弹出JS异常错误窗口(c#.net) (2)ht ...
- Visual Studio2012打开时弹出“遇到异常:这可能是由某个扩展导致的”错误的解决办法...
Visual Studio2012打开时弹出"遇到异常:这可能是由某个扩展导致的"错误的解决办法: 具体问题如下: 分析原因:网上搜集了以下,出现异常的原因是安装了第三方控件,然后 ...
- 计算机窗口弹不出来桌面怎么弄,怎么在电脑中设置不再弹出程序的广告窗口
怎么在电脑中设置不再弹出程序的广告窗口 有的小伙伴在使用电脑过程中,总是会收到各种软件程序的弹出广告窗口,觉得特别打扰电脑的使用,因此想要设置不再弹出广告窗口,但是却不知道如何设置,那么小编就来为大家 ...
- iOS APP在前台收到push,弹出系统框
在iOS9以前,APP前台可以收到push的数据,但是没有UI展示,需要APP自己展示.iOS 10以后,苹果统一使用 UserNotifications ,以前的API都被标为弃用了. 在appde ...
- iOS Mach异常和signal信号
摘要: 本着探究下iOS Crash捕获的目的,学习了下Crash捕获相关的Mach异常和signal信号处理,记录下相关内容,并提供对应的测试示例代码.Mach为XNU的微内核,Mach异常为最底层 ...
- iOS 仿看了吗应用、指南针测网速等常用工具、自定义弹出视图框架、图片裁剪、内容扩展等源码...
iOS精选源码 扩展内容的cell - folding-cell 一个近乎完整的可识别中国身份证信息的Demo 可自动快速... JPImageresizerView 仿微信的图片裁剪 带年月和至今以 ...
- iOS精品源码,GHConsole图片浏览器圆形进度条音视频传输连击礼物弹出动画
2019独角兽企业重金招聘Python工程师标准>>> 1.可在app中显示的控制台框架GHConsole 2.GKPhotoBrowser--自定义图片浏览器 3.圆形进度条 4. ...
- iOS精品源码,GHConsole图片浏览器圆形进度条音视频传输连击礼物弹出动画 1
1.可在app中显示的控制台框架GHConsole 2.GKPhotoBrowser--自定义图片浏览器 3.圆形进度条 4.音视频实时传输 part2(补充上一贴) 5.RSChat(以前微信写的仿 ...
最新文章
- serverlet 区别_Servlet中/和/*的区别
- Linux代理后网页显示问题,项目部署到linux后出现的两个问题
- Git修改用户名和邮箱的方法(附Git常用命令)
- 字符串型String
- 漳州java,漳州学java,漳州学java学校,漳州学java效果怎么样
- linux下epoll网络编程模型,C++ - 网络编程模型 - Linux EPOLL
- 黑客帝国中代码雨如何实现?用 Python 就可以!
- 数仓建模—事实表和维度表设计规范
- 名师出高徒!请关注领英上这十位活跃的大神
- [FMG]ADT-eclipse升级为可以添加javaWeb
- 探索--appllo配置中心,如何动态加载配置
- C#交错数组和多维数组
- OpenCV和JavaCV--安装经验
- 下载网易云课堂和B站的视频
- win10安装usb转serial驱动(或Prolific USB-to-Serial Comm Port驱动)
- 26个英文字母对应数字的奇妙意义
- 区块链中的“链上”和“链下”
- 如何用python画雪人_pygame画雪人_函数与图形示例.py
- 网站空间如何选择?空间选择的标准是什么?
- hdu 2191 悼念512汶川大地震遇难同胞——珍惜现在,感恩生活