RN JS端出錯(cuò)牧嫉,在Debug環(huán)境顯示為紅屏報(bào)錯(cuò)呕屎,生產(chǎn)環(huán)境APP直接閃退。
RN端有全局異常捕獲函數(shù)
ErrorUtils.setGlobalHandler((error, isFatal) => {})
實(shí)現(xiàn)了該函數(shù)后JS端的異常被該函數(shù)捕獲兢交,錯(cuò)誤不再往原生端拋,js代碼會(huì)執(zhí)行到出錯(cuò)的那一行笼痹,程序不會(huì)導(dǎo)致異常退出配喳。因?yàn)槌鲥e(cuò)點(diǎn)后面的代碼不再執(zhí)行飘诗,APP大多會(huì)出現(xiàn)渲染未完全、白屏界逛、或者點(diǎn)不動(dòng)現(xiàn)象昆稿。
相比程序崩潰,實(shí)現(xiàn)該方法還是有好處的息拜,但問題是js錯(cuò)誤不再拋往原生端溉潭,app根本無(wú)法知道js端發(fā)生了錯(cuò)誤導(dǎo)致問題無(wú)法及時(shí)反饋修改。而且該方法回調(diào)里面的error信息很少少欺,在生產(chǎn)環(huán)境就算知道該error也無(wú)法判斷是哪個(gè)地方出錯(cuò)了喳瓣,所以生產(chǎn)環(huán)境不能使用。
測(cè)試環(huán)境好像也沒啥使用必要赞别。
只要不實(shí)現(xiàn)該方法畏陕,js端錯(cuò)誤就會(huì)拋到原生端,具體拋出方法為react-native/Libraries/Core/ExceptionsManager.js中的reportException仿滔。js端構(gòu)建error拋到原生端惠毁,原生對(duì)應(yīng)的處理方法在:
iOS,RCTExceptionsManager/reportFatalException
android崎页,ExceptionsManagerModule/reportFatalException
其中安卓是收到error是直接構(gòu)建成異常拋給系統(tǒng)鞠绰。
蘋果則是看是否有傳入的自定義處理回調(diào),沒有才構(gòu)建異常拋給系統(tǒng)飒焦。
因?yàn)閖s端的異常會(huì)傳給原生蜈膨,再原生端構(gòu)建異常拋出,所以直接捕獲原生異常即可牺荠。
這里集成bugly捕獲異常翁巍。
生產(chǎn)環(huán)境下,同一個(gè)js異常
let arr = [1, 2, 3, 4, 5];
for (let index = 0; index < arr.length; index++) {
const element = arr[index];
if (element) {
element();
}
}
在bugly后臺(tái)崩潰列表中監(jiān)控到的日志如下
仔細(xì)對(duì)比發(fā)現(xiàn)iOS的崩潰日志中存在如@1032:1098這種特殊符號(hào)休雌,而android的錯(cuò)誤信息由于長(zhǎng)度的原因被截取了無(wú)法顯示完全灶壶,沒有顯示。但是可以在追蹤日志中發(fā)現(xiàn)該特殊符號(hào)挑辆。這個(gè)符號(hào)表示該異常出現(xiàn)在js中的位置例朱,可以通過符號(hào)表讀取出來孝情,參考:https://segmentfault.com/a/1190000011982639
注:bugly后臺(tái)崩潰日志詳情頁(yè)鱼蝉,iOS的追蹤日志總是為空,而android的有內(nèi)容箫荡,不知何故魁亦。
通過崩潰日志得到崩潰對(duì)應(yīng)的RN版本號(hào),定位到該版本羔挡,導(dǎo)出mapping文件洁奈,執(zhí)行下面自定義腳本间唉,定位js崩潰位置。
var sourceMap = require('source-map');
var fs = require('fs');
var args = process.argv.splice(2);
if (args.length < 3) {
console.log('輸入?yún)?shù)缺失');
process.exit(1);
}
var path = args[0]; // mapping 文件路徑
var line = args[1]; // 崩潰日志中 @xxx:xxx 第一個(gè)數(shù)字
var column = args[2]; // 崩潰日志中 @xxx:xxx 第二個(gè)數(shù)字
fs.readFile(path, 'utf8', function (err, data) {
var smc = new sourceMap.SourceMapConsumer(data);
let result = smc.originalPositionFor({
line: parseInt(line), // 注意 這里要求傳入的參數(shù)是number類型
column: parseInt(column),
})
console.log(result);
});
更進(jìn)一步利术,希望js出現(xiàn)異常時(shí)APP不崩潰呈野,但是又能將異常信息上報(bào)以便能修復(fù)生產(chǎn)bug。
iOS端:
js的錯(cuò)誤信息由reportFatalException方法處理印叁,再調(diào)用RCTFatal
void RCTFatal(NSError *error)
{
_RCTLogNativeInternal(RCTLogLevelFatal, NULL, 0, @"%@", error.localizedDescription);
RCTFatalHandler fatalHandler = RCTGetFatalHandler();
if (fatalHandler) {
fatalHandler(error);
} else {
NSString *name = [NSString stringWithFormat:@"%@: %@", RCTFatalExceptionName, error.localizedDescription];
NSString *message = RCTFormatError(error.localizedDescription, error.userInfo[RCTJSStackTraceKey], 175);
NSMutableDictionary *userInfo = [error.userInfo mutableCopy];
[userInfo setObject:RCTFormatError(error.localizedDescription, error.userInfo[RCTJSStackTraceKey], -1)
forKey:RCTUntruncatedMessageKey];
@throw [[NSException alloc] initWithName:name reason:message userInfo:userInfo];
}
}
發(fā)現(xiàn)只要給設(shè)置了RCTGetFatalHandler()回調(diào)函數(shù)就不會(huì)拋出異常被冒。所以只需要自定義該回調(diào),在這里面自己上報(bào)錯(cuò)誤信息給bugly即可實(shí)現(xiàn)所需功能轮蜕。
- (void)setJSFatalHandler{ // application:didFinishLaunchingWithOptions:中調(diào)用
RCTSetFatalHandler(^(NSError *error) {
if (!error.userInfo) {
[Bugly reportError:error];
return;
}
NSArray *jsErrorStacks = error.userInfo[RCTJSStackTraceKey];
if (!jsErrorStacks || jsErrorStacks.count == 0) {
[Bugly reportError:error];
return;
}
// bugly 上傳的錯(cuò)誤內(nèi)容有長(zhǎng)度限制昨悼,下面錯(cuò)誤處理內(nèi)容
NSString *errDesc = error.userInfo[NSLocalizedDescriptionKey] ?: @"";
if (errDesc.length > 150) {
errDesc = [errDesc substringToIndex:150];
}
NSMutableString *errDescStr = errDesc.mutableCopy;
[errDescStr appendString:@"-----jsErrorLocation-----: ["];
NSInteger errCountLimit = 5;
for (NSInteger i = 0; i < errCountLimit; i++) {
NSDictionary *stack = jsErrorStacks[I];
[errDescStr appendString:[NSString stringWithFormat:@"{line:%@, column:%@} ",stack[@"lineNumber"],stack[@"column"]]];
if (i == errCountLimit - 1) {
[errDescStr appendString:@"]"];
}
}
NSDictionary *userInfo = @{ NSLocalizedDescriptionKey:errDescStr};
NSError *newError = [NSError errorWithDomain:error.domain code:error.code userInfo:userInfo];
// 上報(bào)錯(cuò)誤
[Bugly reportError:newError];
});
}
這樣處理過后,js異常時(shí)APP不再崩潰跃洛,bugly控制臺(tái)崩潰分析/崩潰列表中不再有記錄率触,異常信息在錯(cuò)誤分析/錯(cuò)誤列表中呈現(xiàn)。
android端:本人安卓不熟汇竭,暫未處理葱蝗。