最近客户有个要求:人家谁谁有crash日志捕获和上传,我们是不是也要做一个... 人家谁谁.....还有什么什么功能........
正好最近也在研究这方面东东,所以整理一下分享给大家:如何用程序获取Crash日志 并 可以上传Crash日志。
首先我们整理经常会闪退的异常哪些:数组越界、空引用、引用未定义方法、内存空间不足等等。
友盟分享后台是可以看到crash的日志,如下图:
开始研究的时候,我有两个疑问:
1.如何获取crash闪退日志(工具和程序两种方法);
2.解析crash;
说明:这里说的crash日志不是在联调的情况下(是生产环境,通俗的说就是发布了的产品)。
如何获取crash闪退日志-- 工具查看
先看第一个问题如何查看,我搜索的方法有以下几个:
第一个方法:XCode 的菜单Window->Organizer 选择Devices -> 选中的手机 -> 点击手机名称左边的箭头 会等到如下图
在右边竖蓝色矩形框中 Type里面出现两种类型:Unknown和Crash 这两种类型分别是 内存不够回收内存kill应用程序导致Crash和程序异常Crash的日志。
上图是我在刚打开日志(立马、马上)截的图,否则过了5秒中,会变成这样(自动解析):
注意对比一下红色框框内容,这个日志也基本上上告诉你crash的原因了。
第二种方法 打开手机 - > 设置 -> 通用 - > 关于本机 - > 诊断与用量 - > 诊断与用量数据 这里面就是所有应用的Crash日志。
第三种方法 通过iTunes Connect(Manage Your Applications - View Details - Crash Reports)获取用户的crash日志。方法很多这里不多列了。
解析crash
参见:http://stackoverflow.com/questions/1460892/symbolicating-iphone-app-crash-reports )
用程序获取crash日志
但是这里都是工具,没有用到程序获取,经过千方百计的查询(思路是:先找到存放crash的iphone系统路径:var/mobile/Library/Logs/CrashReporter)找到了crash存放的路径,唉,苦于无法读取(用程序读出来都是nil),当然如果是越狱手机就不一样是可以读取的。这个思路断掉了。
换个思路:自己用程序捕获crash,保存到本地可以吗?这么一试,果然........
第一步:新建一个继承自NSObject的类(Xcode新建一个空项目过程略),取名字CatchCrash,在h和m文件中写下:
.h文件
- #import<Foundation/Foundation.h>
- @interfaceCatchCrash:NSObject
- voiduncaughtExceptionHandler(NSException*exception);
- @end
.m(m)文件
- #import"CatchCrash.h"
- @implementationCatchCrash
- voiduncaughtExceptionHandler(NSException*exception)
- {
- //异常的堆栈信息
- NSArray*stackArray=[exceptioncallStackSymbols];
- //出现异常的原因
- NSString*reason=[exceptionreason];
- //异常名称
- NSString*name=[exceptionname];
- NSString*exceptionInfo=[NSStringstringWithFormat:@"Exceptionreason:%@nExceptionname:%@nExceptionstack:%@",name,reason,stackArray];
- NSLog(@"%@",exceptionInfo);
- NSMutableArray*tmpArr=[NSMutableArrayarrayWithArray:stackArray];
- [tmpArrinsertObject:reasonatIndex:0];
- //保存到本地--当然你可以在下次启动的时候,上传这个log
- [exceptionInfowriteToFile:[NSStringstringWithFormat:@"%@/Documents/error.log",NSHomeDirectory()]atomically:YESencoding:NSUTF8StringEncodingerror:nil];
- }
- @end
第二步:添加一个继承自UIViewcontroller的类,取名字为TestViewController。
第三步:注册CatchCrash异常处理方法,在Appdelegate写下如下代码:
- #import"AppDelegate.h"
- #import"CatchCrash.h"
- #import"TestViewController.h"
- @implementationAppDelegate
- -(BOOL)application:(UIApplication*)applicationdidFinishLaunchingWithOptions:(NSDictionary*)launchOptions
- {
- self.window=[[UIWindowalloc]initWithFrame:[[UIScreenmainScreen]bounds]];
- //Overridepointforcustomizationafterapplicationlaunch.
- //注册消息处理函数的处理方法
- NSSetUncaughtExceptionHandler(&uncaughtExceptionHandler);
- TestViewController*testVc=[[TestViewControlleralloc]init];
- self.window.rootViewController=testVc;
- self.window.backgroundColor=[UIColorwhiteColor];
- [self.windowmakeKeyAndVisible];
- returnYES;
- }
- -(void)applicationWillResignActive:(UIApplication*)application
- {}
- -(void)applicationDidEnterBackground:(UIApplication*)application
- {}
- -(void)applicationWillEnterForeground:(UIApplication*)application
- {}
- -(void)applicationDidBecomeActive:(UIApplication*)application
- {}
- -(void)applicationWillTerminate:(UIApplication*)application
- {}
第四部:在TestViewController的Xib上面添加一个按钮并给其添加一个单击事件,TestViewController.m文件中有如下代码:
- #import"TestViewController.h"
- @interfaceTestViewController()
- @end
- @implementationTestViewController
- -(id)initWithNibName:(NSString*)nibNameOrNilbundle:(NSBundle*)nibBundleOrNil
- {
- self=[superinitWithNibName:nibNameOrNilbundle:nibBundleOrNil];
- if(self){
- //Custominitialization
- }
- returnself;
- }
- -(void)viewDidLoad
- {
- [superviewDidLoad];
- //Doanyadditionalsetupafterloadingtheviewfromitsnib.
- }
- -(void)didReceiveMemoryWarning
- {
- [superdidReceiveMemoryWarning];
- //Disposeofanyresourcesthatcanberecreated.
- }
- #pragmamark-单击事件
- -(IBAction)crashTapped:(id)sender
- {
- //常见异常1---不存在方法引用
- //[selfperformSelector:@selector(thisMthodDoesNotExist)withObject:nil];
- //常见异常2---键值对引用nil
- //[[NSMutableDictionarydictionary]setObject:nilforKey:@"nil"];
- //常见异常3---数组越界
- [[NSArrayarray]objectAtIndex:1];
- //常见异常4---memorywarning级别3以上
- //[selfperformSelector:@selector(killMemory)withObject:nil];
- //其他大家去想吧
- }
- #pragmamark-custommethod
- -(void)killMemory
- {
- for(inti=0;i<300;i++)
- {
- UILabel*tmpLabel=[[UILabelalloc]initWithFrame:CGRectMake(0,0,320,200)];
- tmpLabel.layer.masksToBounds=YES;
- tmpLabel.layer.cornerRadius=10;
- tmpLabel.backgroundColor=[UIColorredColor];
- [self.viewaddSubview:tmpLabel];
- }
- }
- @end
运行代码:可以看到闪退,我们用iExplorer打开:
导出error日志,我们可以看到:
- Exceptionreason:NSRangeException
- <spanstyle="color:#FF0000;">Exceptionname:***-[__NSArrayIobjectAtIndex:]:index1beyondboundsforemptyarray</span>
- Exceptionstack:(
- 0CoreFoundation0x2f2edfeb<redacted>+154
- 1libobjc.A.dylib0x39b66ccfobjc_exception_throw+38
- 2CoreFoundation0x2f224a89<redacted>+176
- <spanstyle="color:#FF0000;">3TestCrash0x000e8077-[TestViewControllercrashTapped:]+126</span>
- 4UIKit0x31b3f057<redacted>+90
- 5UIKit0x31b3eff7<redacted>+30
- 6UIKit0x31b3efd1<redacted>+44
- 7UIKit0x31b2a737<redacted>+374
- 8UIKit0x31b3ea4f<redacted>+590
- 9UIKit0x31b3e721<redacted>+528
- 10UIKit0x31b396eb<redacted>+758
- 11UIKit0x31b0e8ed<redacted>+196
- 12UIKit0x31b0cf97<redacted>+7102
- 13CoreFoundation0x2f2b925b<redacted>+14
- 14CoreFoundation0x2f2b872b<redacted>+206
- 15CoreFoundation0x2f2b6f1f<redacted>+622
- 16CoreFoundation0x2f221f0fCFRunLoopRunSpecific+522
- 17CoreFoundation0x2f221cf3CFRunLoopRunInMode+106
- 18GraphicsServices0x3417a663GSEventRunModal+138
- 19UIKit0x31b6d16dUIApplicationMain+1136
- 20TestCrash0x000e810dmain+116
- 21libdyld.dylib0x3a073ab7<redacted>+2
- )
太清楚了,对吧,下次启动应用程序的时候可以把这个error.log(这个名字我临时写的,用什么日期也可以的)上传。可以在日志中加上类似与友盟的 iphone 的UUID、Bundle ID 等等。
现在猜测一下,友盟是不是这么做的呢?
进入一个引用友盟sdk项目的根目录,打开mac终端输入命令grep -r NSSetUncaughtExceptionHandler .(这个点不能少或者绝对路径替换这个点),得到如下图:
真的就找到了,什么什么 matches。当然这只是猜测。