- React-Native内部已经实现了一套异常处理的流程,fetal和soft的error都会将堆栈信息回调回来
- 我们可以通过hook RN的异常处理的方法,在其中加入自己的业务逻辑,比如:上报异常堆栈信息到服务器或者bugly
RN的异常处理
RN的异常处理主要是RCTAssert和RCTExceptionsManager处理的
RCTAssert
其中assert的fetal error处理,暴露了回调函数,我们设置成自己的函数即可
RCTSetFatalHandler(^(NSError *error) {
YourRCTFatal(error);
});
void YourRCTFatal(NSError *error) {
// 搞些事情,比如上报bugly
}
RCTExceptionsManager
1. 异常处理管理类
提供了回调fetal和soft的异常的堆栈信息的方法;同时也提供代理去实现处理这些异常的方法
@protocol RCTExceptionsManagerDelegate <NSObject>
- (void)handleSoftJSExceptionWithMessage:(nullable NSString *)message stack:(nullable NSArray *)stack exceptionId:(NSNumber *)exceptionId;
- (void)handleFatalJSExceptionWithMessage:(nullable NSString *)message stack:(nullable NSArray *)stack exceptionId:(NSNumber *)exceptionId;
@optional
- (void)updateJSExceptionWithMessage:(nullable NSString *)message stack:(nullable NSArray *)stack exceptionId:(NSNumber *)exceptionId;
@end
@interface RCTExceptionsManager : NSObject <RCTBridgeModule>
- (instancetype)initWithDelegate:(id<RCTExceptionsManagerDelegate>)delegate;
- (void)reportSoftException:(nullable NSString *)message stack:(nullable NSArray<NSDictionary *> *)stack exceptionId:(NSNumber *)exceptionId;
- (void)reportFatalException:(nullable NSString *)message stack:(nullable NSArray<NSDictionary *> *)stack exceptionId:(NSNumber *)exceptionId;
@property (nonatomic, weak) id<RCTExceptionsManagerDelegate> delegate;
@property (nonatomic, assign) NSUInteger maxReloadAttempts;
@end
在bridge load的时候会new一个RCTExceptionsManager
的实例,默认是没有设置delegate的,那么就走的是默认的异常处理的流程;
- (instancetype)initWithModuleClass:(Class)moduleClass
bridge:(RCTBridge *)bridge
{
return [self initWithModuleClass:moduleClass
moduleProvider:^id<RCTBridgeModule>{ return [moduleClass new]; }
bridge:bridge];
}
2. 在异常处理中加入自己的业务逻辑,同时又不修改源代码
实现方式有2种
- 实现一个类,遵从
RCTExceptionsManagerDelegate
代理,在代理内部处理自己的业务- hook的方式,hook
reportSoftException
、reportFatalException
3. 代理的方式
实现RCTExceptionsManager
的类别,在类别中等待类的实例实例化之后,设置delegate为我们定义的遵循RCTExceptionsManagerDelegate
协议的实例
@implementation RCTExceptionsManager (Extension)
- (void)batchDidComplete {
// self.delegate = xxx
}
4. hook的方式
hook的灵活度比代理的方式要高,你可以插入代码逻辑也可以直接替换原实现
@implementation RCTExceptionsManager (CassExtension)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[self aspect_hookSelector:@selector(init) withOptions:AspectPositionAfter usingBlock:^(id<AspectInfo>aspectInfo) {
[aspectInfo.instance hookMethods];
} error:nil];
});
}
- (void)hookMethods {
// hook reportSoftException
__weak typeof(self) weakSelf = self;
[self aspect_hookSelector:@selector(reportSoftException:stack:exceptionId:) withOptions:AspectPositionInstead usingBlock:^(id<AspectInfo>aspectInfo) {
[weakSelf mine_reportSoftException:[aspectInfo.arguments safeObjectAtIndex:0]
stack:[aspectInfo.arguments safeObjectAtIndex:1]
exceptionId:[aspectInfo.arguments safeObjectAtIndex:2]];
} error:nil];
}
- (void)mine_reportSoftException:(nullable NSString *)message stack:(nullable NSArray<NSDictionary *> *)stack exceptionId:(NSNumber *)exceptionId {
[self.bridge.redBox showErrorMessage:message withStack:stack];
if (self.delegate) {
[self.delegate handleSoftJSExceptionWithMessage:message stack:stack exceptionId:exceptionId];
}
NSString *description = [@"Unhandled JS SoftException: " stringByAppendingString:message];
NSDictionary *errorInfo = @{ NSLocalizedDescriptionKey: description, RCTJSStackTraceKey: stack };
NSError *error = [NSError errorWithDomain:RCTErrorDomain code:0 userInfo:errorInfo];
// 搞些事情,比如上报bugly
}
@end
RN js异常解析
异常上报之后,就能拿到报错的堆栈信息,我们需要结合sourcemap去解析到具体的js源码的位置
解析可以看这篇文章
通过SourceMap解析RN中的js异常