Umeng 劫持 NSSetUncaughtExceptionHandler()

APP 工程中使用了友盟统计信息,导致自己通过NSSetUncaughtExceptionHandler收集崩溃信息失败。

自定义收集崩溃信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

NSSetUncaughtExceptionHandler (&MLUncaughtExceptionHandler);

return YES;
}

void MLUncaughtExceptionHandler(NSException *exception) {
NSArray *arr = [exception callStackSymbols];
NSString *reason = [exception reason];
NSString *name = [exception name];
//将崩溃原因保存到本地
[[NSUserDefaults standardUserDefaults] setObject:reason forKey:@"reson"];
[[NSUserDefaults standardUserDefaults] synchronize];

}

如上,通过NSSetUncaughtExceptionHandler(),参数传入函数地址。当异常发生时,就会将异常信息保存到本地(当然也可发送给后台或者发邮件)。

问题重现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

NSSetUncaughtExceptionHandler (&MLUncaughtExceptionHandler);
NSSetUncaughtExceptionHandler (&WMUncaughtExceptionHandler);
return YES;
}

void MLUncaughtExceptionHandler(NSException *exception) {
NSArray *arr = [exception callStackSymbols];
NSString *reason = [exception reason];
NSString *name = [exception name];
//将崩溃原因保存到本地
[[NSUserDefaults standardUserDefaults] setObject:reason forKey:@"reson2"];
[[NSUserDefaults standardUserDefaults] synchronize];

}

void WMUncaughtExceptionHandler(NSException *exception) {
NSArray *arr = [exception callStackSymbols];
NSString *reason = [exception reason];
NSString *name = [exception name];
//将崩溃原因保存到本地
[[NSUserDefaults standardUserDefaults] setObject:reason forKey:@"reson"];
[[NSUserDefaults standardUserDefaults] synchronize];

}

上面的代码相当于有两个收集崩溃信息的地方 MLUncaughtExceptionHandler()WMUncaughtExceptionHandler() ,但是当运行程序的时候回发现 MLUncaughtExceptionHandler() 不会收集到任何信息。因为 NSSetUncaughtExceptionHandler() 传入了两次函数地址,第一次函数的地址被替换掉了。

解决方案

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
static NSUncaughtExceptionHandler *_MLHandler;

NSSetUncaughtExceptionHandler (&MLUncaughtExceptionHandler);
// 保存MLUncaughtExceptionHandler异常的handler
_MLHandler = NSGetUncaughtExceptionHandler();
// 设置WMUncaughtExceptionHandler异常的handler
NSSetUncaughtExceptionHandler (&WMUncaughtExceptionHandler);
return YES;
}

void MLUncaughtExceptionHandler(NSException *exception) {
NSArray *arr = [exception callStackSymbols];
NSString *reason = [exception reason];
NSString *name = [exception name];
//将崩溃原因保存到本地
[[NSUserDefaults standardUserDefaults] setObject:reason forKey:@"reson2"];
[[NSUserDefaults standardUserDefaults] synchronize];

}

void WMUncaughtExceptionHandler(NSException *exception) {
NSArray *arr = [exception callStackSymbols];
NSString *reason = [exception reason];
NSString *name = [exception name];
//将崩溃原因保存到本地
[[NSUserDefaults standardUserDefaults] setObject:reason forKey:@"reson"];
[[NSUserDefaults standardUserDefaults] synchronize];
_MLHandler(exception);
}

将之前对exception处理的handle进行保存,下一次处理的时候,重新调用之前的handle,将异常抛出给他们。

Umeng 劫持 Exception 问题

Umeng 应该也是用了上述方式对异常进行的处理,但是并没有将异常进行抛出,所以,当工程中使用了Umeng进行异常统计的时候,我们自己写的异常捕获有可能会失效。如果写在Umeng统计之前就会被Umeng替换掉,异常就无法捕捉。 但是,如果写在Umeng之后,就会造成Umeng统计失效(这个没有去验证)。所以我们就需要使用上面的方式进行处理。

1
2
3
4
5
static NSUncaughtExceptionHandler *_umengHandler;
// 将Umeng异常处理保存
_umengHandler = NSGetUncaughtExceptionHandler();
// 设置自己处理 (UncaughtExceptionHandlerYourself 是你自己处理异常的函数)
NSSetUncaughtExceptionHandler(&UncaughtExceptionHandlerYourself);

如上,首先保存Umeng的handler UncaughtExceptionHandlerYourself 使我们自己处理异常的handler,然后在自己处理完异常之后,调用一下之前保存的Umeng的handler

1
_umengHandler(exception);

· 参考文档 ·
iOS友盟和其他崩溃收集库共存时的冲突问题

-------------本文结束谢谢欣赏-------------
Alice wechat