Java自学者论坛

 找回密码
 立即注册

手机号码,快捷登录

恭喜Java自学者论坛(https://www.javazxz.com)已经为数万Java学习者服务超过8年了!积累会员资料超过10000G+
成为本站VIP会员,下载本站10000G+会员资源,会员资料板块,购买链接:点击进入购买VIP会员

JAVA高级面试进阶训练营视频教程

Java架构师系统进阶VIP课程

分布式高可用全栈开发微服务教程Go语言视频零基础入门到精通Java架构师3期(课件+源码)
Java开发全终端实战租房项目视频教程SpringBoot2.X入门到高级使用教程大数据培训第六期全套视频教程深度学习(CNN RNN GAN)算法原理Java亿级流量电商系统视频教程
互联网架构师视频教程年薪50万Spark2.0从入门到精通年薪50万!人工智能学习路线教程年薪50万大数据入门到精通学习路线年薪50万机器学习入门到精通教程
仿小米商城类app和小程序视频教程深度学习数据分析基础到实战最新黑马javaEE2.1就业课程从 0到JVM实战高手教程MySQL入门到精通教程
查看: 703|回复: 0

IOS应用之一--异常处理(UncaughtExceptionHandler)

[复制链接]
  • TA的每日心情
    奋斗
    2024-4-6 11:05
  • 签到天数: 748 天

    [LV.9]以坛为家II

    2034

    主题

    2092

    帖子

    70万

    积分

    管理员

    Rank: 9Rank: 9Rank: 9

    积分
    705612
    发表于 2021-9-8 10:02:07 | 显示全部楼层 |阅读模式

    使用原因

          iOS开发中我们会遇到程序抛出异常退出的情况,如果是在调试的过程中,异常的信息是一目了然,但是如果是在已经发布的程序中,获取异常的信息有时候是比较困难的。

    好处与缺点  

         iOS提供了异常发生的处理API,我们在程序启动的时候可以添加这样的Handler,这样的程序发生异常的时候就可以对这一部分的信息进行必要的处理,适时的反馈给开发者。  

         不足的地方是,并不是所有的程序崩溃都是由于发生可以捕捉的异常的,有些时候是因为内存等一些其他的错误导致程序的崩溃,这样的信息是不在这里体现的。  

    常用的处理方式

         第一种方式:作基本的操作,可以添加和获取Handler,捕获到异常后将信息写入到app的Documens下的Exception.txt中。  

         第二种方式:比如可以在程序下一次起来的时候读取这个异常文件发生到服务端

         第三种方式:或者直接就是在处理代码中用openurl的方式(mailto:)调用发送邮件的方式,将异常信息直接变成邮件发送到指定地址,其实还有很多的处理的办法。  

    实例 :

    #pragma mark  代理类中的写法

    - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

    [window makeKeyAndVisible];
    [NdUncaughtExceptionHandler setDefaultHandler];
    NSArray *array = [NSArray arrayWithObject:@"there is only one objective in this arary,call index one, app will crash and throw an exception!"];
    NSLog(@"%@", [array objectAtIndex:1]);

    return YES;
    }

    异常基本接口展示:

    #import <Foundation/Foundation.h>

    @interface NdUncaughtExceptionHandler : NSObject {

    }

    + (void)setDefaultHandler;
    + (NSUncaughtExceptionHandler*)getHandler;

    @end
    //还可以选择设置自定义的handler,让用户取选择

    接口实现展示
    #import "NdUncaughtExceptionHandler.h"

    NSString *applicationDocumentsDirectory() {
    return [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
    }

    void UncaughtExceptionHandler(NSException *exception) {
    NSArray *arr = [exception callStackSymbols];
    NSString *reason = [exception reason];
    NSString *name = [exception name];

    NSString *url = [NSString stringWithFormat:@"=============异常崩溃报告=============\nname:\n%@\nreason:\n%@\ncallStackSymbols:\n%@",
    name,reason,[arr componentsJoinedByString:@"\n"]];
    NSString *path = [applicationDocumentsDirectory() stringByAppendingPathComponent:@"Exception.txt"];
    [url writeToFile:path atomically:YES encoding:NSUTF8StringEncoding error:nil];
    //除了可以选择写到应用下的某个文件,通过后续处理将信息发送到服务器等
    //还可以选择调用发送邮件的的程序,发送信息到指定的邮件地址
    //或者调用某个处理程序来处理这个信息
    }

    @implementation NdUncaughtExceptionHandler

    -(NSString *)applicationDocumentsDirectory {
    return [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
    }

    + (void)setDefaultHandler
    {
    NSSetUncaughtExceptionHandler (&UncaughtExceptionHandler);
    }

    + (NSUncaughtExceptionHandler*)getHandler
    {
    return NSGetUncaughtExceptionHandler();
    }

    @end

    异常崩溃报告:
    =============异常崩溃报告=============
    name:
    NSRangeException
    reason:
    *** -[NSArray objectAtIndex:]: index 1 beyond bounds [0 .. 0]
    callStackSymbols:
    0 CoreFoundation 0x02393919 __exceptionPreprocess + 185
    1 libobjc.A.dylib 0x024e15de objc_exception_throw + 47
    2 CoreFoundation 0x0238958c -[__NSArrayI objectAtIndex:] + 236
    3 UncaughtE 0x000022e8 -[UncaughtEAppDelegate application:didFinishLaunchingWithOptions:] + 157
    4 UIKit 0x002b8543 -[UIApplication _callInitializationDelegatesForURL:payload:suspended:] + 1163
    5 UIKit 0x002ba9a1 -[UIApplication _runWithURL:payload:launchOrientation:statusBarStyle:statusBarHidden:] + 346
    6 UIKit 0x002c4452 -[UIApplication handleEvent:withNewEvent:] + 1958
    7 UIKit 0x002bd074 -[UIApplication sendEvent:] + 71
    8 UIKit 0x002c1ac4 _UIApplicationHandleEvent + 7495
    9 GraphicsServices 0x02bf9afa PurpleEventCallback + 1578
    10 CoreFoundation 0x02374dc4 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__ + 52
    11 CoreFoundation 0x022d5737 __CFRunLoopDoSource1 + 215
    12 CoreFoundation 0x022d29c3 __CFRunLoopRun + 979
    13 CoreFoundation 0x022d2280 CFRunLoopRunSpecific + 208
    14 CoreFoundation 0x022d21a1 CFRunLoopRunInMode + 97
    15 UIKit 0x002ba226 -[UIApplication _run] + 625
    16 UIKit 0x002c5b58 UIApplicationMain + 1160
    17 UncaughtE 0x00002228 main + 102
    18 UncaughtE 0x000021b9 start + 53

    不足的地方是,并不是所有的程序崩溃都是由于发生可以捕捉的异常的,有些时候引起崩溃的大多数原因如:内存访问错误,重复释放等错误就无能为力了,因为这种错误它抛出的是Signal,所以必须要专门做Signal处理。首先定义一个UncaughtExceptionHandler类,.h头文件的代码如下:


    UncaughtExceptionHandler类,.h头文件的代码如下:

    1
    2
    3
    4
    5
    6
    #import

    @interface UncaughtExceptionHandler : NSObject{
    BOOL dismissed;
    }
    @end
    1
    void InstallUncaughtExceptionHandler();
    然后在.mm文件实现InstallUncaughtExceptionHandler(),如下:
    void InstallUncaughtExceptionHandler(){
    signal(SIGABRT, MySignalHandler);
    signal(SIGILL, MySignalHandler);
    signal(SIGSEGV, MySignalHandler);
    signal(SIGFPE, MySignalHandler);
    signal(SIGBUS, MySignalHandler);
    signal(SIGPIPE, MySignalHandler);
    }
    这样,当应用发生错误而产生上述Signal后,就将会进入我们自定义的回调函数MySignalHandler。为了得到崩溃时的现场信息,还可以加入一些获取CallTrace及设备信息的代码,.mm文件的完整代码如下:

    #import "UncaughtExceptionHandler.h"
    #include #include

    NSString * const UncaughtExceptionHandlerSignalExceptionName = @"UncaughtExceptionHandlerSignalExceptionName";

    NSString * const UncaughtExceptionHandlerSignalKey = @"UncaughtExceptionHandlerSignalKey";

    NSString * const UncaughtExceptionHandlerAddressesKey = @"UncaughtExceptionHandlerAddressesKey";

    volatile int32_t UncaughtExceptionCount = 0;

    const int32_t UncaughtExceptionMaximum = 10;

    const NSInteger UncaughtExceptionHandlerSkipAddressCount = 4;

    const NSInteger UncaughtExceptionHandlerReportAddressCount = 5;

    @implementation UncaughtExceptionHandler

    + (NSArray *)backtrace

    {

    void* callstack[128];

    int frames = backtrace(callstack, 128);

    char **strs = backtrace_symbols(callstack, frames);

    int i;

    NSMutableArray *backtrace = [NSMutableArray arrayWithCapacity:frames];

    for (

    i = UncaughtExceptionHandlerSkipAddressCount;

    i < UncaughtExceptionHandlerSkipAddressCount +

    UncaughtExceptionHandlerReportAddressCount;

    i++)

    {

    [backtrace addObject:[NSString stringWithUTF8String:strs]];

    }

    free(strs);

    return backtrace;

    }

    - (void)alertView:(UIAlertView *)anAlertView clickedButtonAtIndex:(NSInteger)anIndex

    {

    if (anIndex == 0)

    {

    dismissed = YES;

    }

    }

    - (void)handleException:(NSException *)exception

    {

    UIAlertView *alert =

    [[[UIAlertView alloc]

    initWithTitle:NSLocalizedString(@"Unhandled exception", nil)

    message:[NSString stringWithFormat:NSLocalizedString(

    @"You can try to continue but the application may be unstable.\n"

    @"%@\n%@", nil),

    [exception reason],

    [[exception userInfo] objectForKey:UncaughtExceptionHandlerAddressesKey]]

    delegate:self

    cancelButtonTitle:NSLocalizedString(@"Quit", nil)

    otherButtonTitles:NSLocalizedString(@"Continue", nil), nil]

    autorelease];

    [alert show];

    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])

    {
    <span style="white-space:pre"> </span>kill(getpid(), [[[exception userInfo] objectForKey:UncaughtExceptionHandlerSignalKey] intValue]);
    }
    else
    {
    [exception raise];
    }
    }
    @end

    NSString* getAppInfo() { NSString *appInfo = [NSString stringWithFormat:@"App : %@ %@(%@)\nDevice : %@\nOS Version : %@ %@\nUDID : %@\n", [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleDisplayName"], [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"], [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"], [UIDevice currentDevice].model, [UIDevice currentDevice].systemName, [UIDevice currentDevice].systemVersion, [UIDevice currentDevice].uniqueIdentifier]; NSLog(@"Crash!!!! %@", appInfo); return appInfo; } void MySignalHandler(int signal) { int32_t exceptionCount = OSAtomicIncrement32(&UncaughtExceptionCount); if (exceptionCount > UncaughtExceptionMaximum)
    {
    return;
    }

    NSMutableDictionary *userInfo =

    [NSMutableDictionary

    dictionaryWithObject:[NSNumber numberWithInt:signal]

    forKey:UncaughtExceptionHandlerSignalKey];

    NSArray *callStack = [UncaughtExceptionHandler backtrace];

    [userInfo

    setObject:callStack

    forKey:UncaughtExceptionHandlerAddressesKey];

    [[[[UncaughtExceptionHandler alloc] init] autorelease]

    performSelectorOnMainThread:@selector(handleException:)

    withObject:

    [NSException

    exceptionWithName:UncaughtExceptionHandlerSignalExceptionName

    reason:

    [NSString stringWithFormat:

    NSLocalizedString(@"Signal %d was raised.\n"

    @"%@", nil),

    signal, getAppInfo()]

    userInfo:

    [NSDictionary

    dictionaryWithObject:[NSNumber numberWithInt:signal]

    forKey:UncaughtExceptionHandlerSignalKey]]

    waitUntilDone:YES];

    }

    void InstallUncaughtExceptionHandler()

    {

    signal(SIGABRT, MySignalHandler);

    signal(SIGILL, MySignalHandler);

    signal(SIGSEGV, MySignalHandler);

    signal(SIGFPE, MySignalHandler);

    signal(SIGBUS, MySignalHandler);

    signal(SIGPIPE, MySignalHandler);

    }
    在应用自身的 didFinishLaunchingWithOptions 前,加入一个函数:

    - (void)installUncaughtExceptionHandler
    {
    InstallUncaughtExceptionHandler();
    }
    最后,在 didFinishLaunchingWithOptions 中加入这一句代码就行了:

    1
    [self InstallUncaughtExceptionHandler];
    现在,基本上所有崩溃都能Hold住了。崩溃时将会显示出如下的对话框:

    这样在崩溃时还能从容地弹出对话框,比起闪退来,用户也不会觉得那么不爽。然后在下次启动时还可以通过邮件来发送Crash文件到邮箱,这就看各个应用的需求了。

    哎...今天够累的,签到来了1...
    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    QQ|手机版|小黑屋|Java自学者论坛 ( 声明:本站文章及资料整理自互联网,用于Java自学者交流学习使用,对资料版权不负任何法律责任,若有侵权请及时联系客服屏蔽删除 )

    GMT+8, 2024-4-26 19:08 , Processed in 0.077369 second(s), 29 queries .

    Powered by Discuz! X3.4

    Copyright © 2001-2021, Tencent Cloud.

    快速回复 返回顶部 返回列表