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 = @"guohongwei719@126.com";//发送者邮箱
    myMessage.pass = @"********";//发送者邮箱的密码
    myMessage.login = @"guohongwei719";//发送者邮箱的用户名
    myMessage.toEmail = @"guohongwei719@126.com";//收件邮箱
    //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 <Foundation/Foundation.h>

import <UIKit/UIKit.h>

@interface GHWCrashHandler : NSObject
{
BOOL dismissed;
}

  • (void)configDismissed;
  • (GHWCrashHandler *)sharedInstance;
    void InstallCrashExceptionHandler();
    @end```
    GHWCrashHandler.m
#import "GHWCrashHandler.h"
#include <libkern/OSAtomic.h>
#include <execinfo.h>
#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)
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,258评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,335评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,225评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,126评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,140评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,098评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,018评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,857评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,298评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,518评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,678评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,400评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,993评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,638评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,801评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,661评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,558评论 2 352

推荐阅读更多精彩内容