iOS中靜默發(fā)送郵件

最近準備在app中加個功能,就是當(dāng)app crash的時候?qū)rash信息發(fā)送郵件給開發(fā)者。目前我們app中使用的異常檢測工具是Bugly,這個工具還是挺好用的,界面很清晰,對crash分析也比較到位,還可以綁定微信,將每日app的崩潰情況定時發(fā)送給開發(fā)者。不得不說騰訊的產(chǎn)品還是很任性,符合咱們的用戶習(xí)慣。但是沒有即時發(fā)送崩潰信息的功能。所以決定自己想想辦法看能不能找到方法。

在iOS中發(fā)送郵件系統(tǒng)自帶有兩個方式,openURL和MFMailComposeViewController,這兩種方式都不是很好,因為要彈出別的界面,顯然不符合我們靜默的需求。網(wǎng)上說使用SKPSMTPMessage可以實現(xiàn),試了一下,確實可以,但是里面坑很多,很多文章沒有講清楚,這里就介紹一下要注意的一些地方吧。最后給出了Demo[GHWSendEmail]

1. 配置發(fā)收郵箱相關(guān)信息

-(void)sendEmail:(NSString*)content
{
    SKPSMTPMessage *myMessage = [[SKPSMTPMessage alloc] init];
    myMessage.delegate = self;
    myMessage.fromEmail = @"guohongwei719@126.com";//發(fā)送者郵箱
    myMessage.pass = @"********";//發(fā)送者郵箱的密碼
    myMessage.login = @"guohongwei719";//發(fā)送者郵箱的用戶名
    myMessage.toEmail = @"guohongwei719@126.com";//收件郵箱
    //myMessage.bccEmail = @"******@qq.com";//抄送
    myMessage.relayHost = @"smtp.126.com";
    myMessage.requiresAuth = YES;
    myMessage.wantsSecure = YES;//為gmail郵箱設(shè)置 smtp.gmail.com
    myMessage.subject = @"iOS崩潰日志";//郵件主題
    
    /* >>>>>>>>>>>>>>>>>>>> *   設(shè)置郵件內(nèi)容   * <<<<<<<<<<<<<<<<<<<< */
    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];
    });
}

發(fā)送郵件的時候配置信息非常關(guān)鍵,這里發(fā)送者郵箱要多試一試,有的可以,有的不可以,不要一個不行就放棄了,網(wǎng)上很多文章沒強調(diào)這一點。我開始也是放棄了,后來發(fā)現(xiàn)別人可以,才多試了試發(fā)現(xiàn)是可以的,這里一定要注意。反正試了幾個126的郵箱是可以的,163的有的不行。使用相應(yīng)的郵箱要配置對應(yīng)的代理服務(wù)器主機,比如126的郵箱就是smtp.126.com,163的郵箱是smtp.163.com,QQ郵箱是smtp.qq.com。最后還是強調(diào)下配置郵箱,用126多試試。
SKPSMTPMessageDelegate里面有兩個方法,提供了發(fā)送郵件成功和失敗的回調(diào):

#pragma mark - SKPSMTPMessageDelegate
- (void)messageSent:(SKPSMTPMessage *)message
{
    NSLog(@"發(fā)送郵件成功");
    [[GHWCrashHandler sharedInstance] configDismissed];

}
- (void)messageFailed:(SKPSMTPMessage *)message error:(NSError *)error
{
    NSLog(@"message - %@\nerror - %@", message, error);
}```
##2. App crash的時候發(fā)送郵件
iOS里面捕獲異常的方法如下

void UncaughtExceptionHandler(NSException *exception) {
NSArray *arr = [exception callStackSymbols]; // 得到當(dāng)前的調(diào)用棧信息
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;
    }
在上面我們已經(jīng)配置好了相關(guān)信息,可以直接發(fā)送郵件了,那是不是在這里捕獲到異常以后直接調(diào)用發(fā)送郵件就可以了呢?答案是錯的。這里直接發(fā)郵件是發(fā)布出去的。最后在網(wǎng)上找了一個方法,可以解決這個問題,使用到了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崩潰發(fā)生的時間:\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的發(fā)送成功回調(diào)方法里面,我們還設(shè)置了
[[GHWCrashHandler sharedInstance] configDismissed];
這樣app發(fā)送了郵件后就可以真正crash了,回到手機桌面,不會停留在當(dāng)前界面,點不動。不過在我的demo中發(fā)現(xiàn)還是不行,在我的項目里面是可以的,后面我再找找原因。

![](http://upload-images.jianshu.io/upload_images/548341-83e5dbdac9ff6738.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容