最近准备在app中加个功能,就是当app crash的时候将crash信息发送邮件给开发者。目前我们app中使用的异常检测工具是Bugly,这个工具还是挺好用的,界面很清晰,对crash分析也比较到位,还可以绑定微信,将每日app的崩溃情况定时发送给开发者。不得不说腾讯的产品还是很任性,符合咱们的用户习惯。但是没有即时发送崩溃信息的功能。所以决定自己想想办法看能不能找到方法。
在iOS中发送邮件系统自带有两个方式,openURL和MFMailComposeViewController,这两种方式都不是很好,因为要弹出别的界面,显然不符合我们静默的需求。网上说使用SKPSMTPMessage可以实现,试了一下,确实可以,但是里面坑很多,很多文章没有讲清楚,这里就介绍一下要注意的一些地方吧。最后给出了Demo[GHWSendEmail]
1. 配置发收邮箱相关信息
-(void)sendEmail:(NSString*)content
{
SKPSMTPMessage *myMessage = [[SKPSMTPMessage alloc] init];
myMessage.delegate = self;
myMessage.fromEmail = @"[email protected]";//发送者邮箱
myMessage.pass = @"********";//发送者邮箱的密码
myMessage.login = @"guohongwei719";//发送者邮箱的用户名
myMessage.toEmail = @"[email protected]";//收件邮箱
//myMessage.bccEmail = @"******@qq.com";//抄送
myMessage.relayHost = @"smtp.126.com";
myMessage.requiresAuth = YES;
myMessage.wantsSecure = YES;//为gmail邮箱设置 smtp.gmail.com
myMessage.subject = @"iOS崩溃日志";//邮件主题
/* >>>>>>>>>>>>>>>>>>>> * 设置邮件内容 * <<<<<<<<<<<<<<<<<<<< */
NSDictionary *plainPart = [NSDictionary dictionaryWithObjectsAndKeys:@"text/plain; charset=UTF-8",kSKPSMTPPartContentTypeKey, content,kSKPSMTPPartMessageKey,@"8bit",kSKPSMTPPartContentTransferEncodingKey,nil];
myMessage.parts = [NSArray arrayWithObjects:plainPart,nil];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[myMessage send];
});
}
发送邮件的时候配置信息非常关键,这里发送者邮箱要多试一试,有的可以,有的不可以,不要一个不行就放弃了,网上很多文章没强调这一点。我开始也是放弃了,后来发现别人可以,才多试了试发现是可以的,这里一定要注意。反正试了几个126的邮箱是可以的,163的有的不行。使用相应的邮箱要配置对应的代理服务器主机,比如126的邮箱就是smtp.126.com,163的邮箱是smtp.163.com,QQ邮箱是smtp.qq.com。最后还是强调下配置邮箱,用126多试试。
SKPSMTPMessageDelegate里面有两个方法,提供了发送邮件成功和失败的回调:
#pragma mark - SKPSMTPMessageDelegate
- (void)messageSent:(SKPSMTPMessage *)message
{
NSLog(@"发送邮件成功");
[[GHWCrashHandler sharedInstance] configDismissed];
}
- (void)messageFailed:(SKPSMTPMessage *)message error:(NSError *)error
{
NSLog(@"message - %@\nerror - %@", message, error);
}```
##2. App crash的时候发送邮件
iOS里面捕获异常的方法如下
void UncaughtExceptionHandler(NSException *exception) {
NSArray *arr = [exception callStackSymbols]; // 得到当前的调用栈信息
NSString *reason = [exception reason];//非常重要,就是崩溃的原因
NSString *name = [exception name];//异常类型
NSLog(@"exception type : %@ n crash reason : %@ n call stack info : %@", name, reason, arr);
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
NSSetUncaughtExceptionHandler (&UncaughtExceptionHandler);
return YES;
}
在上面我们已经配置好了相关信息,可以直接发送邮件了,那是不是在这里捕获到异常以后直接调用发送邮件就可以了呢?答案是错的。这里直接发邮件是发布出去的。最后在网上找了一个方法,可以解决这个问题,使用到了RunLoop。还是先建一个类了,代码如下:
GHWCrashHandler.h
import
import
@interface GHWCrashHandler : NSObject
{
BOOL dismissed;
}
- (void)configDismissed;
- (GHWCrashHandler *)sharedInstance;
void InstallCrashExceptionHandler();
@end```
GHWCrashHandler.m
#import "GHWCrashHandler.h"
#include
#include
#import "GHWEmailManager.h"
NSString * const YDCrashHandlerSignalExceptionName = @"YDCrashHandlerSignalExceptionName";
NSString * const YDCrashHandlerSignalKey = @"YDCrashHandlerSignalKey";
NSString * const YDCrashHandlerAddressesKey = @"YDCrashHandlerAddressesKey";
volatile int32_t UncaughtExceptionCount = 0;
const int32_t UncaughtExceptionMaximum = 10;
const NSInteger UncaughtExceptionHandlerSkipAddressCount = 4;
const NSInteger UncaughtExceptionHandlerReportAddressCount = 5;
@implementation GHWCrashHandler
+ (GHWCrashHandler *)sharedInstance
{
static GHWCrashHandler *crashHandler;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
crashHandler = [[GHWCrashHandler alloc] init];
});
return crashHandler;
}
- (void)configDismissed
{
dismissed = YES;
}
+ (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[i]]];
}
free(strs);
return backtrace;
}
- (void)alertView:(UIAlertView *)anAlertView clickedButtonAtIndex:(NSInteger)anIndex
{
dismissed = YES;
}
- (void)handleException:(NSException *)exception
{
NSArray *arr = [exception callStackSymbols];
NSString *reason = [exception reason];
NSString *name = [exception name];
NSDate *nowDate = [NSDate date];
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:@"yyyy-MM-dd HH:mm:ss"];
NSString *nowDateString = [formatter stringFromDate:nowDate];
NSString *strError = [NSString stringWithFormat:@"\n\n\n=============异常崩溃报告=============\n崩溃发生的时间:\n %@\n崩溃名称:\n%@\n崩溃原因:\n%@\n堆栈信息:\n%@" ,nowDateString,name,reason, arr];
[[GHWEmailManager shareInstance] sendEmail:strError];
CFRunLoopRef runLoop = CFRunLoopGetCurrent();
CFArrayRef allModes = CFRunLoopCopyAllModes(runLoop);
while (!dismissed)
{
for (NSString *mode in (NSArray *)CFBridgingRelease(allModes))
{
CFRunLoopRunInMode((CFStringRef)CFBridgingRetain(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:YDCrashHandlerSignalExceptionName])
{
kill(getpid(), [[[exception userInfo] objectForKey:YDCrashHandlerSignalKey] intValue]);
}
else
{
[exception raise];
}
}
@end
void HandleException(NSException *exception)
{
int32_t exceptionCount = OSAtomicIncrement32(&UncaughtExceptionCount);
if (exceptionCount > UncaughtExceptionMaximum) {
return;
}
NSArray *callStack = [GHWCrashHandler backtrace];
NSMutableDictionary *userInfo =
[NSMutableDictionary dictionaryWithDictionary:[exception userInfo]];
[userInfo setObject:callStack forKey:YDCrashHandlerAddressesKey];
[[[GHWCrashHandler alloc] init] performSelectorOnMainThread:@selector(handleException:)
withObject:[NSException exceptionWithName:[exception name]
reason:[exception reason]
userInfo:userInfo]
waitUntilDone:YES];
}
void SignalHandler(int signal)
{
int32_t exceptionCount = OSAtomicIncrement32(&UncaughtExceptionCount);
if (exceptionCount > UncaughtExceptionMaximum) {
return;
}
NSMutableDictionary *userInfo =
[NSMutableDictionary dictionaryWithObject:[NSNumber numberWithInt:signal] forKey:YDCrashHandlerSignalKey];
NSArray *callStack = [GHWCrashHandler backtrace];
[userInfo setObject:callStack forKey:YDCrashHandlerAddressesKey];
[[[GHWCrashHandler alloc] init] performSelectorOnMainThread:@selector(handleException:)
withObject:[NSException
exceptionWithName:YDCrashHandlerSignalExceptionName
reason:[NSString stringWithFormat:@"Signal %d was raised.", signal]
userInfo:[NSDictionary
dictionaryWithObject:[NSNumber numberWithInt:signal]
forKey:YDCrashHandlerSignalKey]]
waitUntilDone:YES];
}
void InstallCrashExceptionHandler()
{
NSSetUncaughtExceptionHandler(&HandleException);
signal(SIGABRT, SignalHandler);
signal(SIGILL, SignalHandler);
signal(SIGSEGV, SignalHandler);
signal(SIGFPE, SignalHandler);
signal(SIGBUS, SignalHandler);
signal(SIGPIPE, SignalHandler);
}
```
然后App启动的时候还要注册一下
```
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
InstallCrashExceptionHandler();
return YES;
}
```
这样就可以了,在SMTPLibrary的发送成功回调方法里面,我们还设置了
[[GHWCrashHandler sharedInstance] configDismissed];
这样app发送了邮件后就可以真正crash了,回到手机桌面,不会停留在当前界面,点不动。不过在我的demo中发现还是不行,在我的项目里面是可以的,后面我再找找原因。
![](http://upload-images.jianshu.io/upload_images/548341-83e5dbdac9ff6738.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)