美文网首页Swift编程
iOS中静默发送邮件

iOS中静默发送邮件

作者: 黑化肥发灰 | 来源:发表于2017-07-21 16:37 被阅读32次

    最近准备在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:https://img.haomeiwen.com/i548341/83e5dbdac9ff6738.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

    相关文章

      网友评论

      本文标题:iOS中静默发送邮件

      本文链接:https://www.haomeiwen.com/subject/rkeekxtx.html