iOS中静默发送邮件

最近准备在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)

你可能感兴趣的:(iOS中静默发送邮件)