xCrash是爱奇艺开源的在android平台上面捕获异常的开源库。xCrash能为安卓 APP提供捕获Java崩溃异常,native崩溃异常和ANR异常。
xCrash 能在 App 进程崩溃或 ANR 时,在你指定的目录中生成一个 tombstone 文件(格式与安卓系统的 tombstone 文件类似)。
xCrash项目地址:
github:https://github.com/iqiyi/xCrash
gitee:https://gitee.com/caikelun/xCrash
dependencies {
implementation 'com.iqiyi.xcrash:xcrash-android-lib:2.4.6'
}
2.需要捕获native异常,或者android的api版本大于21。需要添加以下配置,用于捕获异常。
android {
defaultConfig {
ndk {
// 根据需要添加必要的ABI
abiFilters 'armeabi', 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
}
}
}
public class MyCustomApplication extends Application {
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
// 默认初始化
XCrash.init(this);
}
}
Tombstone 文件默认将被写入到 Context#getFilesDir() + “/tombstones” 目录。(通常在: /data/data/PACKAGE_NAME/files/tombstones)。
xCrash提供两个接口用于APP应用初始化xCrash。
初始化接口1:
public static int init(Context ctx) {
return init(ctx, null);
}
初始化接口2:
public static synchronized int init(Context ctx, InitParameters params){
...
}
初始化接口1和接口2都需要传入context用于xCrash获取APP应用进程的信息,不同的是接口1,不传入自定义配置,采用的是xCrash的默认配置,接口2,可以根据APP应用的需要自定义配置。
xCrash提供可选的配置参数,通过可选的配置参数保存APP应用进程发生异常。xCrash的初始化配置参见XCrash.java中的内部类InitParameters。
xCrash配置参数分为四个部分:1.通用输出配置;2.java异常输出配置;3.native异常输出配置;4.ANR异常输出配置。
配置源码参见源文件XCrash.java中内部类InitParameters。
public static class InitParameters{
...
// APP应用版本信息
String appVersion = null;
// 用于存放异常日志的文件夹
String logDir = null;
// 初始化xCrash后延迟xx毫秒,进行日志文件维护任务(为清理多余的过期日志文件)
int logFileMaintainDelayMs = 5000;
// xCrash库的日志输出接口
ILogger logger = null;
// xCrash库需要加载的so文件路径
ILibLoader libLoader = null;
...
}
配置源码参见源文件XCrash.java中内部类InitParameters。
public static class InitParameters{
...
// 是否使能Java异常处理器
boolean enableJavaCrashHandler = true;
// 是否抛出Java异常
boolean javaRethrow = true;
// java异常文件数量
int javaLogCountMax = 10;
// 执行命令 logcat -b system 输出的日志行数
int javaLogcatSystemLines = 50;
// 执行命令 logcat -b event 输出的日志行数
int javaLogcatEventsLines = 50;
// logcat -b main 输出的日志行数
int javaLogcatMainLines = 200;
// 是否输出app应用进程的文件描述符
boolean javaDumpFds = true;
// 是否输出所有线程的日志信息
boolean javaDumpAllThreads = true;
// 输出日志最多的线程数
int javaDumpAllThreadsCountMax = 0;
// 输出线程日志的线程白名单
String[] javaDumpAllThreadsWhiteList = null;
// 发生java异常crash的回调
ICrashCallback javaCallback = null;
...
}
配置源码参见源文件XCrash.java中内部类InitParameters。
public static class InitParameters{
...
// 是否输出native异常标志
boolean enableNativeCrashHandler = true;
// 是否抛出异常
boolean nativeRethrow = true;
// native异常文件的最大数量
int nativeLogCountMax = 10;
// 执行命令 logcat -b system 输出的日志行数
int nativeLogcatSystemLines = 50;
// 执行命令 logcat -b evnet输出的日志行数
int nativeLogcatEventsLines = 50;
// logcat -b main 输出的日志行数
int nativeLogcatMainLines = 200;
// 是否输出产生native异常的so库文件的hash值
boolean nativeDumpElfHash = true;
// 是否输出产生native异常so文件的内存映射
boolean nativeDumpMap = true;
// 是否输出文件描述符
boolean nativeDumpFds = true;
// 是否输出所有线程的日志信息,默认为true,如果为false只输出crahs线程的信息
boolean nativeDumpAllThreads = true;
// 输出日志最多的线程数
int nativeDumpAllThreadsCountMax = 0;
// 输出线程日志的线程白名单
String[] nativeDumpAllThreadsWhiteList = null;
// 发生java异常crash的回调
ICrashCallback nativeCallback = null;
...
}
配置源码参见源文件XCrash.java中内部类InitParameters
public static class InitParameters{
...
//anr
// anr异常处理器,默认为true,如果为false不捕获anr异常
boolean enableAnrHandler = true;
// 是否抛出anr异常。默认为true
boolean anrRethrow = true;
// 是否设置anr的状态标志给进程状态(具体参见源码中的注释)
boolean anrCheckProcessState = true;
// anr日志最大保留文件数量
int anrLogCountMax = 10;
// 执行命令 logcat -b system 输出的日志行数
int anrLogcatSystemLines = 50;
// 执行命令 logcat -b event 输出的日志行数
int anrLogcatEventsLines = 50;
// 执行命令 logcat -b maint输出的日志行数
int anrLogcatMainLines = 200;
// 是否输出app进程的下打开的文件描述符
boolean anrDumpFds = true;
// 发生anr异常的应用回调
ICrashCallback anrCallback = null;
...
}
上一个小节,对可选配置进行了简单的说明。具体xCrash如何配置异常日志输出,可以参见源码中的初始化接口。
public final class XCrash {
// xcrash 初始化标志
private static boolean initialized = false;
// app应用的appId
private static String appId = null;
// app应用版本的版本信息
private static String appVersion = null;
// 日志输出的文件夹路径
private static String logDir = null;
// xcrash默认的日志输出接口
private static ILogger logger = new DefaultLogger();
public static int init(Context ctx) {
// 调用内部的初始化函数
return init(ctx, null);
}
// 初始化配置函数
public static synchronized int init(Context ctx, InitParameters params) {
// 判断是否已初始化过,如果初始化过,不允许初始化两次
if (XCrash.initialized) {
return Errno.OK;
}
...
// 默认配置
if (params == null) {
// 默认配置的可选配置项参见上一小结的内容
params = new InitParameters();
}
// 通用配置,xcrash的日志输出接口。
// 如果通过配置参数进行配置,则xcrash库的日志输出库为参数配置的接口为准。
// 如果不配置,则为DefaultLogger()。DefaultLogger为调用android.util.Log输出日志
if (params.logger != null) {
XCrash.logger = params.logger;
}
// 保存 app 应用的appId
String packageName = ctx.getPackageName();
XCrash.appId = packageName;
// 不存在,保存为unknown
if (TextUtils.isEmpty(XCrash.appId)) {
XCrash.appId = "unknown";
}
if (TextUtils.isEmpty(params.appVersion)) {
// 获取app的版本,使用Android Studio,则为app.build文件中的versionName
params.appVersion = Util.getAppVersion(ctx);
}
// 设置异常日志的输出文件夹路径。
// 默认为 /data/data/PACKAGE_NAME/files/tombstones
if (TextUtils.isEmpty(params.logDir)) {
params.logDir = ctx.getFilesDir() + "/tombstones";
}
XCrash.logDir = params.logDir;
// 获取应用的进程名和进程ID
int pid = android.os.Process.myPid();
String processName = null;
...
// 日志文件的管理器 异常日志文件数量,超过数量,删除最早的。
// 其中异常日志的占位文件,在native异常或者android 5.0以后anr异常会使用到
// 在捕获到native异常的时候,如果存在占位文件,则使用占位文件。不存在才会创建新的文件
FileManager.getInstance().initialize(
params.logDir,// 异常日志文件路径
params.javaLogCountMax,// java异常日志文件数量
params.nativeLogCountMax,
params.anrLogCountMax,
params.placeholderCountMax, // 异常日志的占位文件,可配置
params.placeholderSizeKb,// 异常日志占位文件的大小
params.logFileMaintainDelayMs // xCrash初始化后,延迟xx毫秒,进行日志文件管理。
);
// 是否捕获java异常日志
if (params.enableJavaCrashHandler) {
JavaCrashHandler.getInstance().initialize(
pid,
processName,
appId,
params.appVersion,
params.logDir,
params.javaRethrow,
params.javaLogcatSystemLines,
params.javaLogcatEventsLines,
params.javaLogcatMainLines,
params.javaDumpFds,
params.javaDumpAllThreads,
params.javaDumpAllThreadsCountMax,
params.javaDumpAllThreadsWhiteList,
params.javaCallback);
}
//init ANR handler (API level < 21)
if (params.enableAnrHandler && Build.VERSION.SDK_INT < 21) {
AnrHandler.getInstance().initialize(
ctx,
pid,
processName,
appId,
params.appVersion,
params.logDir,
params.anrCheckProcessState,
params.anrLogcatSystemLines,
params.anrLogcatEventsLines,
params.anrLogcatMainLines,
params.anrDumpFds,
params.anrCallback);
}
//init native crash handler / ANR handler (API level >= 21)
int r = Errno.OK;
if (params.enableNativeCrashHandler || (params.enableAnrHandler && Build.VERSION.SDK_INT >= 21)) {
r = NativeHandler.getInstance().initialize(
ctx,
params.libLoader,
appId,
params.appVersion,
params.logDir,
params.enableNativeCrashHandler,
params.nativeRethrow,
params.nativeLogcatSystemLines,
params.nativeLogcatEventsLines,
params.nativeLogcatMainLines,
params.nativeDumpElfHash,
params.nativeDumpMap,
params.nativeDumpFds,
params.nativeDumpAllThreads,
params.nativeDumpAllThreadsCountMax,
params.nativeDumpAllThreadsWhiteList,
params.nativeCallback,
params.enableAnrHandler && Build.VERSION.SDK_INT >= 21,
params.anrRethrow,
params.anrCheckProcessState,
params.anrLogcatSystemLines,
params.anrLogcatEventsLines,
params.anrLogcatMainLines,
params.anrDumpFds,
params.anrCallback);
}
// 执行日志文件管理
FileManager.getInstance().maintain();
return r;
}
}
xCrash初始化接口,主要对配置信息保存,以及执行日志文件管理。xCrahs配置的可以参见源码提供的xCrash_sample工程。
通过xCrash_sample工程可以对android常见的异常进行测试。下面对日志文件结构进行简单的分析,以便应用发生异常是,通过日志来分析原因。以下日志文件,为采用默认配置输出的异常日志。
异常日志文件。日志文件的组成部分:tombstone_应用启动时间*1000_app版本_app进程名_java异常后缀。
// java 异常文件后缀
static final String javaLogSuffix = ".java.xcrash";
// native异常文件后缀
static final String nativeLogSuffix = ".native.xcrash";
// anr异常文件后缀,api版本小于21
static final String anrLogSuffix = ".anr.xcrash";
// anr异常文件后缀,api版本大于21
static final String traceLogSuffix = ".trace.xcrash";
举例如下:
tombstone_00001571190344276000_1.2.3-beta456-patch789__xcrash.sample.java.xcrash
因此我们可以通过文件名可以获取到部分有用的信息,比如说发生异常的app应用的版本信息,以及发生异常的时间(如果换位年月日时分秒毫秒可能更为明显),异常的类型。
日志主要分为
// 头部信息
*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
Tombstone maker: 'xCrash 2.4.5'
Crash type: 'java'
Start time: '2019-10-16T09:45:44.276+0800'
Crash time: '2019-10-16T09:45:49.395+0800'
App ID: 'xcrash.sample'
App version: '1.2.3-beta456-patch789'
Rooted: 'No'
API level: '25'
OS version: '7.1.2'
ABI list: 'arm64-v8a,armeabi-v7a,armeabi'
Manufacturer: 'LANDI'
Brand: 'LANDI'
Model: 'AECR C10'
Build fingerprint: 'Android/msm8953_64/msm8953_64:7.1.2/N2G47H/builde01180925:user/test-keys'
pid: 22023, tid: 22023, name: main >>> xcrash.sample <<<
// java stacktrace
java stacktrace:
...
// logcat日志输出部分,包括main,system,event
logcat:
// app应用进程打开的文件描述符
open files:
...
// app应用的内存信息
memory info:
System Summary (From: /proc/meminfo)
// 进程信息
Process Status (From: /proc/PID/status)
...
Process Limits (From: /proc/PID/limits)
+++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
// 异常回调填充信息(应用自定的信息,参见ICrashCallback )
日志分为:
// 信息头部
*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
Tombstone maker: 'xCrash 2.4.5'
Crash type: 'native'
Start time: '2019-10-16T16:06:18.882+0800'
Crash time: '2019-10-16T16:06:24.668+0800'
App ID: 'xcrash.sample'
App version: '1.2.3-beta456-patch789'
Rooted: 'No'
API level: '25'
OS version: '7.1.2'
Kernel version: 'Linux version 3.18.31-perf #1 SMP PREEMPT Fri Jan 18 09:50:24 CST 2019 (aarch64)'
ABI list: 'arm64-v8a,armeabi-v7a,armeabi'
Manufacturer: 'LANDI'
Brand: 'LANDI'
Model: 'AECR C10'
Build fingerprint: 'Android/msm8953_64/msm8953_64:7.1.2/N2G47H/builde01180925:user/test-keys'
ABI: 'arm64'
pid: 23897, tid: 23897, name: xcrash.sample >>> xcrash.sample <<<
// 异常信号部分
signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x0
// 异常堆栈信息
backtrace:
// so库的编译信息,build id
build id:
// 堆栈信息
stack:
// 内存信息
memory near xxx:
// 内存映射信息
memory map:
// logcat日志输出部分,包括main,system,event
logcat:
// app应用进程打开的文件描述符
open files:
// 内存信息
memory info:
// 进程信息
Process Limits (From: /proc/PID/limits)
Process Details (From: /proc/PID/smaps)
Process Dalvik Details (From: /proc/PID/smaps)
Process Summary (From: /proc/PID/smaps)
// 应用基本信息
*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
Tombstone maker: 'xCrash 2.4.6'
..
// 异常信号部分
Cmd line: xcrash.sample
Mode: ART DumpForSigQuit
// 主线程信息
DALVIK THREADS (18):
"main" prio=5 tid=1 Sleeping
// logcat日志输出部分,包括main,system,event
logcat:
// app应用进程打开的文件描述符
open files:
// 内存信息
memory info:
// 进程信息
Process Limits (From: /proc/PID/limits)
Process Details (From: /proc/PID/smaps)
Process Dalvik Details (From: /proc/PID/smaps)
Process Summary (From: /proc/PID/smaps)
通过以上的介绍,可以了解xCrash库的基本使用,以及通过xCrash来捕获android应用的异常,通过对简单的异常日志文件可以进行分析,用来定位发生异常的具体位置。后续将对java异常,native异常,anr异常进行源码分析,借此加深对异常系统的理解。
请各位方家不吝赐教,谢谢!