app上線后在某些情況下肯定會有崩潰現(xiàn)象發(fā)生
獲取崩潰信息的方式有很多,比較常見的是使用友盟統(tǒng)計、騰訊Bugly等.但是需要集成第三方框架,并且信息傳到第三方服務(wù)器上,如果公司很反感第三方框架,或者很注重數(shù)據(jù)安全和隱私,或者是資金問題,是不會采用這種方式的.
還有就是我目前這個項目使用的:使用原生框架應(yīng)用內(nèi)崩潰收集,并上傳服務(wù)器
崩潰信息處理主要分兩步:
1.在用戶app拋出異常的時候,我們捕獲到異常并處理(例如搞個彈窗)
2.將異常寫入沙盒中或上傳到服務(wù)器
如何捕獲異常
iOS 原生的類NSException可以用來做異常處理,但功能有限,而引起崩潰的大多數(shù)原因如:內(nèi)存訪問錯誤,重復(fù)釋放等錯誤就無能為力了
因為這種錯誤它拋出的是signal,所以還要做signal處理.
我目前的項目中做了兩種處理.下面代碼注釋很詳細(xì).
(關(guān)于NSException和signal詳細(xì)的請閱讀文末的參考鏈接,前輩們寫得很好我就不重復(fù)寫了)
// 將獲取崩潰信息函數(shù)寫在AppDelegate這個方法中.以保證在程序開始運行就具有獲取崩潰信息的功能
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
//code...
//調(diào)用自定義類中的收集崩潰信息的方法
[CJAppUncaughtExceptionHandler InstallUncaughtExceptionHandler];
//code...
return YES;
}
下面是在自定義類的.m文件中的代碼,.h中就一個方法聲明,下面有注釋
#include <libkern/OSAtomic.h>
#include <execinfo.h>
//未捕獲的異常處理器信號異常的名字
NSString * const UncaughtExceptionHandlerSignalExceptionName = @"UncaughtExceptionHandlerSignalExceptionName";
//異常處理程序信號保存到字典的key
NSString * const UncaughtExceptionHandlerSignalKey = @"UncaughtExceptionHandlerSignalKey";
NSString * const UncaughtExceptionHandlerAddressesKey = @"UncaughtExceptionHandlerAddressesKey";
//當(dāng)前處理異常個數(shù)
volatile int32_t UncaughtExceptionCount = 0;
//最大能夠處理的異常個數(shù)
const int32_t UncaughtExceptionMaximum = 10;
//這兩個預(yù)留的,暫時用不到
const NSInteger UncaughtExceptionHandlerSkipAddressCount = 4;
const NSInteger UncaughtExceptionHandlerReportAddressCount = 5;
#import "CJAppUncaughtExceptionHandler.h"
void InstallUncaughtExceptionHandler();
@implementation CJAppUncaughtExceptionHandler
NSUncaughtExceptionHandler* _uncaughtExceptionHandler = nil;
//暴露在.h中,在AppDelegate中調(diào)用,保證在程序開始運行就具有獲取崩潰信息的功能
+ (void)InstallUncaughtExceptionHandler
{
InstallUncaughtExceptionHandler();
}
//創(chuàng)建異常對象調(diào)用
+ (NSString* )getAppInfo
{
NSString *appInfo = [NSString stringWithFormat:@"App : %@ %@(%@)\nDevice : %@\nOS Version : %@ %@\n",
[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleDisplayName"],
[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"],
[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"],
[UIDevice currentDevice].model,
[UIDevice currentDevice].systemName,
[UIDevice currentDevice].systemVersion
];
return appInfo;
}
//ExceptionSignalHandler()會調(diào)用
+ (NSArray *)backtrace
{
//定義一個指針數(shù)組
void* callstack[128];
//該函數(shù)用于獲取當(dāng)前線程的調(diào)用堆棧,獲取的信息將會被存放在callstack中隆圆。
//參數(shù)128用來指定callstack中可以保存多少個void* 元素浩习。
//函數(shù)返回值是實際獲取的指針個數(shù),最大不超過128大小在callstack中的指針實際是從堆棧中獲取的返回地址,每一個堆椆罟遥框架有一個返回地址艇炎。
int frames = backtrace(callstack, 128);
//backtrace_symbols將從backtrace函數(shù)獲取的信息轉(zhuǎn)化為一個字符串?dāng)?shù)組.
//參數(shù)callstack應(yīng)該是從backtrace函數(shù)獲取的數(shù)組指針,frames是該數(shù)組中的元素個數(shù)(backtrace的返回值)
//函數(shù)返回值是一個指向字符串?dāng)?shù)組的指針,它的大小同callstack相同.每個字符串包含了一個相對于callstack中對應(yīng)元素的可打印信息.
char **strs = backtrace_symbols(callstack, frames);
NSMutableArray *backtrace = [NSMutableArray arrayWithCapacity:frames];
for (int i=0;i<frames;i++)
{
[backtrace addObject:[NSString stringWithUTF8String:strs[i]]];
}
//注意釋放
free(strs);
return backtrace;
}
//由InstallUncaughtExceptionHandler()調(diào)用
+ (void)InstallUncaughtExceptionHandlerB
{
//獲取崩潰統(tǒng)計的函數(shù)指針
_uncaughtExceptionHandler = NSGetUncaughtExceptionHandler();
//利用 NSSetUncaughtExceptionHandler樊展,當(dāng)程序異常退出的時候购城,可以先進行處理,然后做一些自定義的動作
//其實控制臺輸出的日志信息就是NSException產(chǎn)生的刨疼,一旦程序拋出異常泉唁,程序就會崩潰,控制臺就會有崩潰日志
NSSetUncaughtExceptionHandler(&UncaughtExceptionHandler);
}
//異常截獲處理方法
void UncaughtExceptionHandler(NSException *exception)
{
NSLog(@"CRASH: %@", exception);
NSLog(@"Stack Trace: %@", [exception callStackSymbols]);
// 異常的堆棧信息
NSArray *stackArray = [exception callStackSymbols];
// 出現(xiàn)異常的原因
NSString *reason = [exception reason];
// 異常名稱
NSString *name = [exception name];
//在沙盒的Documents目錄下創(chuàng)建文件保存//也可以上傳到服務(wù)器或者發(fā)送郵件之類的
NSDictionary *dic = [NSDictionary dictionaryWithObjectsAndKeys:name,@"exception_name",
reason,@"exception_reason",
stackArray,@"stack_info", nil];
NSArray *paths=NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask,YES);
NSString *path=[paths objectAtIndex:0];
NSString *fileName = [path stringByAppendingPathComponent:@"crash.plist"];
[dic writeToFile:fileName atomically:YES];
}
@end
//捕獲信號后的回調(diào)函數(shù),由InstallUncaughtExceptionHandler()調(diào)用
void ExceptionSignalHandler(int signalval)
{
int32_t exceptionCount = OSAtomicIncrement32(&UncaughtExceptionCount);
//如果太多不用處理
if (exceptionCount > UncaughtExceptionMaximum)
{
return;
}
NSMutableDictionary *userInfo = [NSMutableDictionary dictionaryWithObject:[NSNumber numberWithInt:signalval]
forKey:UncaughtExceptionHandlerSignalKey];
//此方法獲取的棧信息太少
NSArray *callStack = [CJAppUncaughtExceptionHandler backtrace];
[userInfo setObject:callStack forKey:UncaughtExceptionHandlerAddressesKey];
//創(chuàng)建一個oc異常對象
NSString *appInfo = [CJAppUncaughtExceptionHandler getAppInfo];
NSString *exceptionReason = [NSString stringWithFormat:@"Signal %d was raised.\n"@"%@", signalval, appInfo];
NSException *exception = [NSException exceptionWithName:UncaughtExceptionHandlerSignalExceptionName
reason:exceptionReason
userInfo:userInfo];
//在沙盒的Documents目錄下創(chuàng)建文件保存//也可以上傳到服務(wù)器或者發(fā)送郵件之類的
NSDictionary *dic = [NSDictionary dictionaryWithObjectsAndKeys:
exception,@"exception",
userInfo,@"stack_info", nil];
NSArray *paths=NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask,YES);
NSString *path=[paths objectAtIndex:0];
NSString *fileName = [path stringByAppendingPathComponent:@"crash.plist"];
[dic writeToFile:fileName atomically:YES];
//取消異常捕獲币狠,在應(yīng)用退出或者銷毀的時候設(shè)置游两,只需調(diào)用此函數(shù),參數(shù)為NULL
NSSetUncaughtExceptionHandler(NULL);
//signal函數(shù)中的信號處理函數(shù)handler漩绵,可以是用戶指定的一個信號處理函數(shù)贱案,也可以是內(nèi)核特定的函數(shù)指針SIG_IGN或SIG_DFL。
//若信號句柄是SIG_IGN或SIG_DFL止吐,則分別表示對捕獲的信號采取忽略操作或者默認(rèn)操作宝踪。
//其實對于大多數(shù)信號的系統(tǒng)默認(rèn)動作是終止該進程。這與不寫此處理函數(shù)是一樣的碍扔。
signal(SIGHUP, SIG_DFL);
signal(SIGINT, SIG_DFL);
signal(SIGQUIT,SIG_DFL);
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()可以用來送參數(shù)sig 指定的信號給參數(shù)pid 指定的進程瘩燥。
//getpid()用來取得目前進程的進程識別碼
//第二個參數(shù)sig即SIGHUP之類的信號
kill(getpid(), [[[exception userInfo] objectForKey:UncaughtExceptionHandlerSignalKey] intValue]);
}
else
{
//拋出異常,不做處理
[exception raise];
}
}
//注冊崩潰攔截
void InstallUncaughtExceptionHandler()
{
[CJAppUncaughtExceptionHandler InstallUncaughtExceptionHandlerB];
//signal信號函數(shù),第一個參數(shù)表示需要處理的信號值不同,第二個參數(shù)為處理函數(shù)
//由掛起導(dǎo)致的終止信號
signal(SIGHUP, ExceptionSignalHandler);
//由中斷導(dǎo)致的終止信號
signal(SIGINT, ExceptionSignalHandler);
//由退出導(dǎo)致的終止信號
signal(SIGQUIT, ExceptionSignalHandler);
//注冊程序由于abort()函數(shù)調(diào)用發(fā)生的程序終止信號
signal(SIGABRT, ExceptionSignalHandler);
//注冊程序由于非法指令產(chǎn)生的程序終止信號
signal(SIGILL, ExceptionSignalHandler);
//注冊程序由于無效內(nèi)存的引用導(dǎo)致的程序終止信號
signal(SIGSEGV, ExceptionSignalHandler);
//注冊程序由于浮點數(shù)異常導(dǎo)致的程序終止信號
signal(SIGFPE, ExceptionSignalHandler);
//注冊程序由于內(nèi)存地址未對齊導(dǎo)致的程序終止信號
signal(SIGBUS, ExceptionSignalHandler);
//程序通過端口發(fā)送消息失敗導(dǎo)致的程序終止信號
signal(SIGPIPE, ExceptionSignalHandler);
}
同時還要注意不要用try-catch語句捕捉NSException異常,
因為在Cocoa Touch 框架中,引發(fā)自己的異常然后再用try-catch語句塊來捕捉,這非常消耗資源,
但是捕捉系統(tǒng)引發(fā)的異常就不會
參考相關(guān)鏈接:
1.http://blog.csdn.net/yhhwatl/article/details/34432603?utm_source=tuicool&utm_medium=referral
2.http://www.reibang.com/p/77660e626874#
3.http://www.cnblogs.com/daxiaxiaohao/p/4466097.html?utm_source=tuicool&utm_medium=referral
4.http://www.cnblogs.com/mickole/p/3246702.html
5.http://blog.sina.com.cn/s/blog_8184e03301013ddz.html
6.http://baike.baidu.com/link?url=RT6L_RWOcmCOICq3BuEcdTQfRlqgsaNmLUnGn2QrGBtIkldsqhMBmOgSm-CQvcH-h3uqupCf8aS98Xl3Bm1Ctq
7.https://nianxi.net/ios/ios-crash-reporter.html