公司在做海外产品,由于免费版firebase对native崩溃的捕获上报能力有限(无堆栈,无具体上下文信息),因此对我们定位和降低native极为不利,当native崩溃总量进入top行列的时候,就不能对其视而不见了,因此我开始调研解决方案,最后决定接入爱奇艺推出的xCrash来捕获native崩溃,当然了,xCrash本身是不具备上报功能的,它只是将崩溃信息写入tombstone文件。因此需要在其基础之上添加上报功能。
要实现上报功能,就需要选择一个上报时机,在崩溃发生时或App再次启动时,我选择了在崩溃发生时即时上传tombstone文件,并与服务端约定上报协议,最终实现可在firebase后台通过设备ID来查看上传到服务器的tombstone文件。
具体实现如下:
在崩溃发生的回调中找到最新的tombstone文件,打包上传至服务器
public void init(Context context) {
XCrash.init(context, new XCrash.InitParameters()
.setAppVersion(BuildConfig.VERSION_NAME)
.setLogDir(getCrashDir().getAbsolutePath())
.setJavaRethrow(true)
.setJavaLogCountMax(3)
.setJavaDumpAllThreadsWhiteList(new String[]{"^main$", "^Binder:.*", ".*Finalizer.*"})
.setJavaDumpAllThreadsCountMax(0)
.setNativeRethrow(true)
.setNativeLogCountMax(3)
.setNativeDumpAllThreadsWhiteList(new String[]{"^Signal Catcher$", "^Jit thread pool$", ".*(R|r)ender.*", ".*Chrome.*"})
.setNativeDumpAllThreadsCountMax(0)
.setAnrRethrow(true)
.setAnrLogCountMax(3)
.setPlaceholderCountMax(0)
.setLogFileMaintainDelayMs(1000)
.setLogger(mLogger)
.setLibLoader(new ILibLoader() {
@Override public void loadLibrary(String libName) {
try {
ReLinker.loadLibrary(context, libName);
} catch (Exception e) {
printLog(e + " | " + libName);
System.loadLibrary(libName);
}
}
}).setAnrCallback(new ICrashCallback() {
// ANR发生时的回调
@Override public void onCrash(String logPath, String emergency) throws Exception {
catchANRLogDelay();
}
}).setJavaCallback(new ICrashCallback() {
// Java崩溃发生时的回调
@Override public void onCrash(String logPath, String emergency) throws Exception {
catchCrashLog();
}
}).setNativeCallback(new ICrashCallback() {
//Native崩溃发生时的回调
@Override public void onCrash(String logPath, String emergency) throws Exception {
catchCrashLog();
}
})
);
}
// 打包上传tombstone文件
private void catchCrashLog() {
crashZipId = generateCrashId();
final File out = new File(AppInstances.getPathManager().getTmpFilePath() + crashZipId + ".gzip");
try {
List<File> files = Arrays.asList(getCrashDir().listFiles());
if (ListUtils.isEmpty(files)) {
return;
}
File crashFile = null;
long lastModified = 0;
for (File f : files) {
if (f.lastModified() > lastModified) {
lastModified = f.lastModified();
crashFile = f;
}
}
if (crashFile != null) {
GZIPUtils.gzipFile(crashFile.getAbsolutePath(), out.getAbsolutePath());
}
} catch (Throwable e) {
e.printStackTrace();
}
upload();
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
上面的代码有一点需要注意: 为了尽可能将崩溃日志成功上传至服务器,主线程sleep五秒,对于anr的情况则是在非UI线程中上传日志。
日志上传成功后,就可以在firebase的崩溃列表中查看某一个崩溃,在数据一栏找到设备ID来查询。因为我们有运营后台可以将设备ID和用户ID关联,因此就可以轻松查到每个用户的崩溃日志。
此即时上报的方案上线后虽然助力定位到了一些问题,但也有如下缺陷和问题
-
引入了新的问题,如OOM,ANR等问题。
-
由于上报依赖于全局的OkHttp Client,因此如果App在没有初始化OkHttp Client的情况下崩溃,则不会上报,就属漏报了。
-
崩溃和anr不能即时聚合到一处供查看
-
ANR目前不能查询,但是已尝试使用firebase Event上报,但是貌似免费版无法查看Event的字段,上报的意义不大,后期考虑向服务端上报。
问题2和3其实可以很容易改善,但是问题1还有待进一步观察,同期因为我们代码的原因引入了一个严重的OOM问题,所以不太确定是不是xCrash受到了影响和牵连。这个OOM问题修复后准备再打开xCrash进一步验证。
目前观察到一个现象,Android 7以下的机器基本都没有成功上报,比较奇怪,但是本地测试Android 4的机器是可以上报的,此问题后续有待验证。