宝宝再也不用担心APP闪退了

以前天天没事逛知乎,现在没事就来逛,发现生活真真真的美好。

去年的貌似是以社科类的文章为主,技术类的文章想要上首页,那是特别特别难得,不过现在很明显看到很多技术大神的文章活跃在首页,他们技术不错,文笔也不差,写得东西都挺新鲜,与时俱进,相信很多人像我一样在此都得到了不少帮助。虽然我经常逛知乎,可是我真的做到了一个回答也没有,一个提问也没有的记录,真的是一个超级路人甲,可是我怎么就在这里留下了脚印呢?!(可能是给我一种很简约,有韵味的感觉,那就是我内心深处所向往的吧......who know......)

我们的项目从零到有之后,他们觉得APP偶尔闪退体验不好,可能是因为后台变化数据格式导致的崩溃,或者是因为ProtocolBuffer的类库中隐患,或者就是我写的代码有问题。 那么我能不能捕捉这样的异常呢?把异常告诉用户,总比突然就退出来得优雅吧。

去百度一下NSException,还真有一些比较好的方法。
我总结了以下两种方法:
第一种方法未经过封装,容易理解其中的原理
第二种方法已封装好(感谢那位大神,代码写的很好,有的地方我确实看不懂,还需努力学习,crying~~~),需要花时间理解底层实现,但是使用起来方便简单,推荐使用

第一种方法:

主要使用:

@try
{
 
}
@catch (NSException *exception)
{

}
@finally
{

}
  • 首先自定义一个捕捉异常的类,继承自NSException,
    .h文件代码如下:
宝宝再也不用担心APP闪退了_第1张图片
8B03FDBA-A579-46C2-9DF0-0FDEEA4D6AFD.png

.m文件代码如下:

宝宝再也不用担心APP闪退了_第2张图片
CE81FC74-F8DD-4A69-89CC-92311DBBD12C.png

没错,你看到了,noSuchMethod这个方法没有实现,我们需要调用此方法来测试

  • 然后把以下这个方法写在Appdelegate里面
@try//在这里面放入需要进行捕捉异常的方法
{
         
    [vc noSuchMethod];

    NSLog(@"上面这个方法实际上只是在.h文件中声明,但是.m文件并没有实现,现在调用这个方法必然会导致程序崩溃,也就是闪退");
}
@catch (NSException *exception)//一旦捕捉到异常就会进入这个方法里面,对捕捉到的异常进行处理
{
  
    NSLog(@"Caught %@%@", [exception name], [exception reason]);
    
    [RLException popAlertWithTitle:@"错误信息提示"  message:[NSString stringWithFormat:@"%@\n%@",[exception name],[exception reason]]];
    
    NSLog(@"已将异常反馈到APP上,用户会受到这个提示,而且APP不会闪退,可以继续使用APP)");
}
@finally// 异常发生或者不发生,这里都会执行
{
    RLLog(@"finally");
}
宝宝再也不用担心APP闪退了_第3张图片
3D528682-A33C-4A26-BB5A-99C7A50BCEF7.png
  • command+R试试看
宝宝再也不用担心APP闪退了_第4张图片
4D3B22DA-7501-4CF2-B9D4-A6B810EF07D6.png

提示了这个异常,并且APP还是可以继续操作,基本改善了APP的状况
但是这个方法不够好用,必须要把方法放进@try里面,当然很难做到,如果对其封装,可能能提高它的实用性

其实NSException这个类里面还有很多东西可以用,有的是Apple Inc已经封装好了,所以可以继续深入研究,一定还有我们没发现的东西,希望需要有发现的宝宝们乐于分享哇!

第二种方法:

  • 下面这个方法,我就展示代码然后说说怎么用,里面封装的比较多,有点C和C++底层的感觉,懂的多的宝宝可以指点下哇,不懂的宝宝就默默拿用吧~~~

  • 先自定义一个捕捉异常的类,具体如下
    .h文件如下:

#import 
@interface RLUncaughtExceptionHandler : NSObject
{
    BOOL dismissed;
}
void HandleException(NSException *exception);
void SignalHandler(int signal);
void InstallUncaughtExceptionHandler(void);
@end

.m文件很长很长:

#import "RLUncaughtExceptionHandler.h"
#include 
#include 

NSString * const UncaughtExceptionHandlerSignalExceptionName = @"UncaughtExceptionHandlerSignalExceptionName";
NSString * const UncaughtExceptionHandlerSignalKey = @"UncaughtExceptionHandlerSignalKey";
NSString * const UncaughtExceptionHandlerAddressesKey = @"UncaughtExceptionHandlerAddressesKey";

volatile int32_t UncaughtExceptionCount = 0;
const int32_t UncaughtExceptionMaximum = 10;

const NSInteger UncaughtExceptionHandlerSkipAddressCount = 4;
const NSInteger UncaughtExceptionHandlerReportAddressCount = 5;




@implementation RLUncaughtExceptionHandler

+ (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
{
    if (anIndex == 0)
    {
        dismissed = YES;
    }else if (anIndex==1) {
        NSLog(@"ssssssss");
    }
}

- (void)validateAndSaveCriticalApplicationData
{
    
}

- (void)handleException:(NSException *)exception
{
    [self validateAndSaveCriticalApplicationData];
    
    UIAlertView *alert =
    [[UIAlertView alloc]
      initWithTitle:NSLocalizedString(@"抱歉,程序出现了异常", nil)
      message:[NSString stringWithFormat:NSLocalizedString(
                                                           @"如果点击继续,程序有可能会出现其他的问题,建议您还是点击退出按钮并重新打开\n\n"
                                                           @"异常原因如下:\n%@\n%@", nil),
               [exception reason],
               [[exception userInfo] objectForKey:UncaughtExceptionHandlerAddressesKey]]
      delegate:self
      cancelButtonTitle:NSLocalizedString(@"退出", nil)
      otherButtonTitles:NSLocalizedString(@"继续", nil), nil];
    
//    UIAlertView *alert =
//    [[UIAlertView alloc]
//     initWithTitle:@"抱歉,程序出现了异常"
//     message:[NSString stringWithFormat:@"如果点击继续,程序有可能会出现其他的问题,建议您还是点击退出按钮并重新打开\n\n"
//                                                          @"异常原因如下:\n%@\n%@",
//              [exception reason],
//              [[exception userInfo] objectForKey:UncaughtExceptionHandlerAddressesKey]]
//     delegate:self
//     cancelButtonTitle:@"退出"
//     otherButtonTitles:@"继续", nil];
    
//    dispatch_async(dispatch_get_main_queue(), ^{
    
        [alert show];
//    });
    

    
    CFRunLoopRef runLoop = CFRunLoopGetCurrent();
    CFArrayRef allModes = CFRunLoopCopyAllModes(runLoop);
    
    while (!dismissed)
    {
        for (NSString *mode in (__bridge NSArray *)allModes)
        {
            CFRunLoopRunInMode((CFStringRef)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:UncaughtExceptionHandlerSignalExceptionName])
    {
        kill(getpid(), [[[exception userInfo] objectForKey:UncaughtExceptionHandlerSignalKey] intValue]);
    }
    else
    {
        [exception raise];
    } 
} 


@end


void HandleException(NSException *exception)
{
    int32_t exceptionCount = OSAtomicIncrement32(&UncaughtExceptionCount);
    if (exceptionCount > UncaughtExceptionMaximum)
    {
        return;
    }
    
    NSArray *callStack = [RLUncaughtExceptionHandler backtrace];
    NSMutableDictionary *userInfo =
    [NSMutableDictionary dictionaryWithDictionary:[exception userInfo]];
    [userInfo
     setObject:callStack
     forKey:UncaughtExceptionHandlerAddressesKey];
    
    [[[RLUncaughtExceptionHandler 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:UncaughtExceptionHandlerSignalKey];
    
    NSArray *callStack = [RLUncaughtExceptionHandler backtrace];
    [userInfo
     setObject:callStack
     forKey:UncaughtExceptionHandlerAddressesKey];
    
    [[[RLUncaughtExceptionHandler alloc] init]
     performSelectorOnMainThread:@selector(handleException:)
     withObject:
     [NSException
      exceptionWithName:UncaughtExceptionHandlerSignalExceptionName
      reason:
      [NSString stringWithFormat:
       NSLocalizedString(@"Signal %d was raised.", nil),
       signal]
      userInfo:
      [NSDictionary
       dictionaryWithObject:[NSNumber numberWithInt:signal]
       forKey:UncaughtExceptionHandlerSignalKey]]
     waitUntilDone:YES];
}

void InstallUncaughtExceptionHandler(void)
{
    NSSetUncaughtExceptionHandler(&HandleException);
    signal(SIGABRT, SignalHandler);
    signal(SIGILL, SignalHandler);
    signal(SIGSEGV, SignalHandler);
    signal(SIGFPE, SignalHandler);
    signal(SIGBUS, SignalHandler);
    signal(SIGPIPE, SignalHandler);
}
  • 然后在Appdelegate里面只需调用捕捉异常类的一个方法,只要整个程序有任何地方崩溃,它都能够捕捉到:(然后你在任何一个文件中写一个会导致程序崩溃的方法,比如给一个Button添加方法,但是并没有实现方法)
 -(BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

    InstallUncaughtExceptionHandler();

    return YES;
}
  • 开始command+R:
宝宝再也不用担心APP闪退了_第5张图片
4F744FAB-1D3E-4285-B357-54989B0B5982.png

如果点退出的话就是退出APP,如果点继续,用户可以继续做其他的操作

当然实际上,你会给用户一个选项,发送崩溃信息给后台,这样后台能够收到用户发送的崩溃记录,开发者能够通过记录进行调试代码。

就这样,APP基本上不会闪退了,这样的APP用起来流畅多了!


update 20161121
Demo演示

宝宝再也不用担心APP闪退了_第6张图片
CrashDeom.gif

你可能感兴趣的:(宝宝再也不用担心APP闪退了)