iOS程式異常Crash友好化處理
阿新 • • 發佈:2019-02-16
線上的app怎麼避免閃退?我們在正常開發的時候,需要考慮各種容錯機制,同時,也可以藉助NSSetUncaughtExceptionHandler
函式來捕獲異常,我們可以比較友好的處理程式異常,帶來的突然崩潰。
用法
- 將CatchedHelper資料夾拖入專案工程中。
- 在
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