xCrash

xCrash 能为安卓 app 提供捕获 java 崩溃,native 崩溃和 ANR 的能力。不需要 root 权限或任何系统权限。

https://github.com/iqiyi/xCrash

捕获 Java 崩溃

Java 崩溃的捕获很简单,通过 UncaughtExceptionHandler 接口实现:

class JavaCrashHandler implements UncaughtExceptionHandler {

    void initialize(int pid, String processName, String appId, String appVersion, String logDir, boolean rethrow,
                    int logcatSystemLines, int logcatEventsLines, int logcatMainLines,
                    boolean dumpFds, boolean dumpNetworkInfo, boolean dumpAllThreads, int dumpAllThreadsCountMax, String[] dumpAllThreadsWhiteList,
                    ICrashCallback callback) {
        ...
        // 获取原来的 handler
        this.defaultHandler = Thread.getDefaultUncaughtExceptionHandler();
        // 设置 JavaCrashHandler 对象
        try {
            Thread.setDefaultUncaughtExceptionHandler(this);
        } catch (Exception e) {
            XCrash.getLogger().e(Util.TAG, "JavaCrashHandler setDefaultUncaughtExceptionHandler failed", e);
        }
    }

    @Override
    public void uncaughtException(Thread thread, Throwable throwable) {
        if (defaultHandler != null) {
            Thread.setDefaultUncaughtExceptionHandler(defaultHandler);
        }

        try {
            handleException(thread, throwable);
        } catch (Exception e) {
            XCrash.getLogger().e(Util.TAG, "JavaCrashHandler handleException failed", e);
        }

        if (this.rethrow) {
            if (defaultHandler != null) {
                defaultHandler.uncaughtException(thread, throwable);
            }
        } else {
            ActivityMonitor.getInstance().finishAllActivities();
            Process.killProcess(this.pid);
            System.exit(10);
        }
    }

    ...
}

  1. 调用 handleException(thread, throwable) 方法收集各种信息
  2. 如果 rethrow 为 true,调用原来的 UncaughtExceptionHandleruncaughtException,否则关闭所有的 Activity,杀掉本进程并退出

handleException(thread, throwable) 方法

    private void handleException(Thread thread, Throwable throwable) {
        Date crashTime = new Date();

        //notify the java crash
        NativeHandler.getInstance().notifyJavaCrashed();
        AnrHandler.getInstance().notifyJavaCrashed();

        //create log file
        File logFile = null;
        try {
            String logPath = String.format(Locale.US, "%s/%s_%020d_%s__%s%s", logDir, Util.logPrefix, startTime.getTime() * 1000, appVersion, processName, Util.javaLogSuffix);
            logFile = FileManager.getInstance().createLogFile(logPath);
        } catch (Exception e) {
            XCrash.getLogger().e(Util.TAG, "JavaCrashHandler createLogFile failed", e);
        }

        //get emergency
        String emergency = null;
        try {
            emergency = getEmergency(crashTime, thread, throwable);
        } catch (Exception e) {
            XCrash.getLogger().e(Util.TAG, "JavaCrashHandler getEmergency failed", e);
        }

        //write info to log file
        if (logFile != null) {
            RandomAccessFile raf = null;
            try {
                raf = new RandomAccessFile(logFile, "rws");

                //write emergency info
                if (emergency != null) {
                    raf.write(emergency.getBytes("UTF-8"));
                }

                //If we wrote the emergency info successfully, we don't need to return it from callback again.
                emergency = null;

                //write logcat
                if (logcatMainLines > 0 || logcatSystemLines > 0 || logcatEventsLines > 0) {
                    raf.write(Util.getLogcat(logcatMainLines, logcatSystemLines, logcatEventsLines).getBytes("UTF-8"));
                }

                //write fds
                if (dumpFds) {
                    raf.write(Util.getFds().getBytes("UTF-8"));
                }

                //write network info
                if (dumpNetworkInfo) {
                    raf.write(Util.getNetworkInfo().getBytes("UTF-8"));
                }

                //write memory info
                raf.write(Util.getMemoryInfo().getBytes("UTF-8"));

                //write background / foreground
                raf.write(("foreground:\\\\n" + (ActivityMonitor.getInstance().isApplicationForeground() ? "yes" : "no") + "\\\\n\\\\n").getBytes("UTF-8"));

                //write other threads info
                if (dumpAllThreads) {
                    raf.write(getOtherThreadsInfo(thread).getBytes("UTF-8"));
                }
            } catch (Exception e) {
                XCrash.getLogger().e(Util.TAG, "JavaCrashHandler write log file failed", e);
            } finally {
                if (raf != null) {
                    try {
                        raf.close();
                    } catch (Exception ignored) {
                    }
                }
            }
        }

        //callback
        if (callback != null) {
            try {
                callback.onCrash(logFile == null ? null : logFile.getAbsolutePath(), emergency);
            } catch (Exception ignored) {
            }
        }
    }

  1. 创建日志文件
  2. getEmergency(crashTime, thread, throwable) 获取堆栈信息
  3. 写入 Logcat 日志
  4. 写入句柄信息
  5. 写入网络信息
  6. 写入内存信息
  7. 写入应用状态:是否在前台
  8. 写入其它线程的堆栈信息
  9. 将结果回调出去,可用于上传日志到服务器的操作

getEmergency(crashTime, thread, throwable)