1. 程式人生 > >iOS程式異常Crash友好化處理

iOS程式異常Crash友好化處理

線上的app怎麼避免閃退?我們在正常開發的時候,需要考慮各種容錯機制,同時,也可以藉助NSSetUncaughtExceptionHandler 函式來捕獲異常,我們可以比較友好的處理程式異常,帶來的突然崩潰。

這裡寫圖片描述

用法

  1. 將CatchedHelper資料夾拖入專案工程中。
  2. AppDelegate.m中找到以下方法並如下新增程式碼:
 [UncaughtExceptionHandler installUncaughtExceptionHandler:YES showAlert:YES];

程式碼步驟如下:

1.傳送異常訊號
2.處理異常
3.無法捕獲的signal處理


4.堆疊呼叫
5.使用UIAlerView進行友好化提示

程式碼註釋
UncaughtExceptionHandler.m檔案主要的程式碼如下:

1.傳送異常訊號

/*
 *  異常的處理方法
 *
 *  @param install   是否開啟捕獲異常
 *  @param showAlert 是否在發生異常時彈出alertView
 *//*  *  異 
+ (void)installUncaughtExceptionHandler:(BOOL)install showAlert:(BOOL)showAlert {

    if (install && showAlert) {
        [[self alloc] alertView:showAlert];
    }

    NSSetUncaughtExceptionHandler(install
? HandleException : NULL); signal(SIGABRT, install ? SignalHandler : SIG_DFL); signal(SIGILL, install ? SignalHandler : SIG_DFL); signal(SIGSEGV, install ? SignalHandler : SIG_DFL); signal(SIGFPE, install ? SignalHandler : SIG_DFL); signal(SIGBUS, install ? SignalHandler : SIG_DFL); signal(SIGPIPE, install
? SignalHandler : SIG_DFL); }

NSSetUncaughtExceptionHandler就是iOS SDK中提供的一個現成的函式,但它不能捕獲丟擲的signal,所以定義了SignalHandler方法。產生上述的signal的時候就會呼叫我們定義的SignalHandler來處理異常。

2.處理異常

該方法就是對應NSSetUncaughtExceptionHandler的處理,只要方法關聯到這個函式,那麼發生相應錯誤時會自動呼叫該函式,呼叫時會傳入exception引數。獲取異常後會將捕獲的異常傳入最終呼叫處理的handleException函式。

void HandleException(NSException *exception) {


    int32_t exceptionCount = OSAtomicIncrement32(&UncaughtExceptionCount);
    // 如果太多不用處理
    if (exceptionCount > UncaughtExceptionMaximum) {
        return;
    }

    //獲取呼叫堆疊
    NSArray *callStack = [exception callStackSymbols];
    NSMutableDictionary *userInfo = [NSMutableDictionary dictionaryWithDictionary:[exception userInfo]];
    [userInfo setObject:callStack forKey:UncaughtExceptionHandlerAddressesKey];

    //在主執行緒中,執行制定的方法, withObject是執行方法傳入的引數
    [[[UncaughtExceptionHandler alloc] init]
     performSelectorOnMainThread:@selector(handleException:)
     withObject:
     [NSException exceptionWithName:[exception name]
                             reason:[exception reason]
                           userInfo:userInfo]
     waitUntilDone:YES];

}

3.無法捕獲的signal處理

以下方法是對於捕獲不到的signal訊號進行處理,列出常見的異常型別。

//處理signal報錯
void SignalHandler(int signal) {

    int32_t exceptionCount = OSAtomicIncrement32(&UncaughtExceptionCount);
    // 如果太多不用處理
    if (exceptionCount > UncaughtExceptionMaximum) {
        return;
    }
    NSString* description = nil;
    switch (signal) {
        case SIGABRT:
            description = [NSString stringWithFormat:@"Signal SIGABRT was raised!\n"];
            break;
        case SIGILL:
            description = [NSString stringWithFormat:@"Signal SIGILL was raised!\n"];
            break;
        case SIGSEGV:
            description = [NSString stringWithFormat:@"Signal SIGSEGV was raised!\n"];
            break;
        case SIGFPE:
            description = [NSString stringWithFormat:@"Signal SIGFPE was raised!\n"];
            break;
        case SIGBUS:
            description = [NSString stringWithFormat:@"Signal SIGBUS was raised!\n"];
            break;
        case SIGPIPE:
            description = [NSString stringWithFormat:@"Signal SIGPIPE was raised!\n"];
            break;
        default:
            description = [NSString stringWithFormat:@"Signal %d was raised!",signal];
    }

    NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
    NSArray *callStack = [UncaughtExceptionHandler backtrace];
    [userInfo setObject:callStack forKey:UncaughtExceptionHandlerAddressesKey];
    [userInfo setObject:[NSNumber numberWithInt:signal] forKey:UncaughtExceptionHandlerSignalKey];

    //在主執行緒中,執行指定的方法, withObject是執行方法傳入的引數
    [[[UncaughtExceptionHandler alloc] init]
     performSelectorOnMainThread:@selector(handleException:)
     withObject:
     [NSException exceptionWithName:UncaughtExceptionHandlerSignalExceptionName
                             reason: description
                           userInfo: userInfo]
     waitUntilDone:YES];
}

4.堆疊呼叫

backtrace是Linux下用來追蹤函式呼叫堆疊以及定位段錯誤的函式。

//獲取呼叫堆疊
+ (NSArray *)backtrace {

    //指標列表
    void* callstack[128];
    //backtrace用來獲取當前執行緒的呼叫堆疊,獲取的資訊存放在這裡的callstack中
    //128用來指定當前的buffer中可以儲存多少個void*元素
    //返回值是實際獲取的指標個數
    int frames = backtrace(callstack, 128);
    //backtrace_symbols將從backtrace函式獲取的資訊轉化為一個字串陣列
    //返回一個指向字串陣列的指標
    //每個字串包含了一個相對於callstack中對應元素的可列印資訊,包括函式名、偏移地址、實際返回地址
    char **strs = backtrace_symbols(callstack, frames);

    int i;
    NSMutableArray *backtrace = [NSMutableArray arrayWithCapacity:frames];
    for (i = 0; i < frames; i++) {

        [backtrace addObject:[NSString stringWithUTF8String:strs[i]]];
    }
    free(strs);

    return backtrace;
}

5.使用UIAlerView進行友好化提示

在這裡你可以做自己的crash收集操作,例如上傳伺服器等。

- (void)handleException:(NSException *)exception {

    [self validateAndSaveCriticalApplicationData:exception];

    if (!showAlertView) {
        return;
    }

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
    UIAlertView *alert =
    [[UIAlertView alloc]
     initWithTitle:@"出錯啦"
     message:[NSString stringWithFormat:@"你可以嘗試繼續操作,但是應用可能無法正常執行.\n"]
     delegate:self
     cancelButtonTitle:@"退出"
     otherButtonTitles:@"繼續", nil];
    [alert show];
#pragma clang diagnostic pop

    CFRunLoopRef runLoop = CFRunLoopGetCurrent();
    CFArrayRef allModes = CFRunLoopCopyAllModes(runLoop);

    while (!self.dismissed) {
        //點選繼續
        for (NSString *mode in (__bridge NSArray *)allModes) {
            //快速切換Mode
            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];
    }
}

這個Demo中同時集成了騰訊的Bugly

這裡寫圖片描述