Android的两种崩溃
Android 崩溃分为 Java 崩溃和 Native崩溃两种。
Java崩溃的知识点
Java崩溃的原因
简单来说,Java崩溃就是在Java代码中,出现了未被捕获的异常,导致应用程序异常退出。
Java异常的归类
Java的异常可分为分为可查的异常(checkedexceptions)和不可查的异常(unchecked exceptions)
常见的异常可归类为如下图:
其中Error和RuntimeException是unchecked exceptions,编译器默认无法通过对其处理。其余checkedexceptions,需要我们在代码中try-catch。
崩溃的捕捉
UncaughtExceptionHandler
先来看一下这个接口的作用
/**
* Interface for handlers invoked when a Thread abruptly
* terminates due to an uncaught exception.
* When a thread is about to terminate due to an uncaught exception
* the Java Virtual Machine will query the thread for its
* UncaughtExceptionHandler using
* {@link #getUncaughtExceptionHandler} and will invoke the handler's
* uncaughtException method, passing the thread and the
* exception as arguments.
* If a thread has not had its UncaughtExceptionHandler
* explicitly set, then its ThreadGroup object acts as its
* UncaughtExceptionHandler. If the ThreadGroup object
* has no
* special requirements for dealing with the exception, it can forward
* the invocation to the {@linkplain #getDefaultUncaughtExceptionHandler
* default uncaught exception handler}.
*
* @see #setDefaultUncaughtExceptionHandler
* @see #setUncaughtExceptionHandler
* @see ThreadGroup#uncaughtException
* @since 1.5
*/
@FunctionalInterface
public interface UncaughtExceptionHandler {
/**
* Method invoked when the given thread terminates due to the
* given uncaught exception.
*
Any exception thrown by this method will be ignored by the
* Java Virtual Machine.
* @param t the thread
* @param e the exception
*/
void uncaughtException(Thread t, Throwable e);
}
大致意思就是如果线程发生未处理的异常,会调用UncaughtExceptionHandler的uncaugthException方法去处理异常,如果该线程没有设置UncaughtExceptionHandler,则会去调用ThreadGroup的UncaughtExceptionHandler,若还是没有,则最终getDefaultUncaughtExceptionHandler来处理异常。
系统默认的UncaughtExceptionHandler
日常当我们应用崩溃时,会有一个默认的系统弹窗,告知我们应用崩溃,那系统的崩溃是如何定义的呢?源码如下,注释已经比较完整。
/**
* Handle application death from an uncaught exception. The framework
* catches these for the main threads, so this should only matter for
* threads created by applications. Before this method runs, the given
* instance of {@link LoggingHandler} should already have logged details
* (and if not it is run first).
*/
private static class KillApplicationHandler implements Thread.UncaughtExceptionHandler {
private final LoggingHandler mLoggingHandler;
/**
* Create a new KillApplicationHandler that follows the given LoggingHandler.
* If {@link #uncaughtException(Thread, Throwable) uncaughtException} is called
* on the created instance without {@code loggingHandler} having been triggered,
* {@link LoggingHandler#uncaughtException(Thread, Throwable)
* loggingHandler.uncaughtException} will be called first.
*
* @param loggingHandler the {@link LoggingHandler} expected to have run before
* this instance's {@link #uncaughtException(Thread, Throwable) uncaughtException}
* is being called.
*/
public KillApplicationHandler(LoggingHandler loggingHandler) {
this.mLoggingHandler = Objects.requireNonNull(loggingHandler);
}
@Override
public void uncaughtException(Thread t, Throwable e) {
try {
ensureLogging(t, e);
// Don't re-enter -- avoid infinite loops if crash-reporting crashes.
if (mCrashing) return;
mCrashing = true;
// Try to end profiling. If a profiler is running at this point, and we kill the
// process (below), the in-memory buffer will be lost. So try to stop, which will
// flush the buffer. (This makes method trace profiling useful to debug crashes.)
if (ActivityThread.currentActivityThread() != null) {
ActivityThread.currentActivityThread().stopProfiling();
}
// Bring up crash dialog, wait for it to be dismissed
ActivityManager.getService().handleApplicationCrash(
mApplicationObject, new ApplicationErrorReport.ParcelableCrashInfo(e));
} catch (Throwable t2) {
if (t2 instanceof DeadObjectException) {
// System process is dead; ignore
} else {
try {
Clog_e(TAG, "Error reporting crash", t2);
} catch (Throwable t3) {
// Even Clog_e() fails! Oh well.
}
}
} finally {
// Try everything to make sure this process goes away.
Process.killProcess(Process.myPid());
System.exit(10);
}
}
该接口实现在RuntimeInit类中,并在Runtime初始化时写入设置成我们默认的异常处理类
RuntimeInit.class
protected static final void commonInit() {
if (DEBUG) Slog.d(TAG, "Entered RuntimeInit!");
/*
* set handlers; these apply to all threads in the VM. Apps can replace
* the default handler, but not the pre handler.
*/
LoggingHandler loggingHandler = new LoggingHandler();
Thread.setUncaughtExceptionPreHandler(loggingHandler);
Thread.setDefaultUncaughtExceptionHandler(new KillApplicationHandler(loggingHandler));
......
}
自定义崩溃捕捉
到这里思路已经很清晰了,我们要做的就是自己实现一个对崩溃处理的UncaughtExceptionHandler,那么我们应该设置在哪,初始化的时机在何时。我们先来看看系统初始化用到的方法,即Thread.setDefaultUncaughtExceptionHandler()。
/**
* Set the default handler invoked when a thread abruptly terminates
* due to an uncaught exception, and no other handler has been defined
* for that thread.
*
* Uncaught exception handling is controlled first by the thread, then
* by the thread's {@link ThreadGroup} object and finally by the default
* uncaught exception handler. If the thread does not have an explicit
* uncaught exception handler set, and the thread's thread group
* (including parent thread groups) does not specialize its
* uncaughtException method, then the default handler's
* uncaughtException method will be invoked.
*
By setting the default uncaught exception handler, an application
* can change the way in which uncaught exceptions are handled (such as
* logging to a specific device, or file) for those threads that would
* already accept whatever "default" behavior the system
* provided.
*
*
Note that the default uncaught exception handler should not usually
* defer to the thread's ThreadGroup object, as that could cause
* infinite recursion.
*
* @param eh the object to use as the default uncaught exception handler.
* If null then there is no default handler.
*
* @throws SecurityException if a security manager is present and it
* denies {@link RuntimePermission}
* ("setDefaultUncaughtExceptionHandler")
*
* @see #setUncaughtExceptionHandler
* @see #getUncaughtExceptionHandler
* @see ThreadGroup#uncaughtException
* @since 1.5
*/
public static void setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler eh) {
defaultUncaughtExceptionHandler = eh;
}
这个defaultUncaughtHandler是Thread类中一个静态的成员,所以,按道理,我们为任意一个线程设置异常处理,所有的线程都应该能共用这个异常处理器。为了在ui线程中添加异常处理Handler,我推荐大家在Application中添加而不是在Activity中添加。Application标识着整个应用,在Android声明周期中是第一个启动的,早于任何的Activity、Service等。
有了以上的知识,我们就可以自己来实现一个崩溃捕捉和处理的lib啦
其实实现方法网上都大同小异,主要是对异常捕获后的处理机制不一致。一般会通过储存崩溃日志并上报这种方案去解决。这里先基础实现崩溃日志文件的存储。
一个崩溃日志应该包括的基本信息有:
- 崩溃原因和栈记录
- 日期和APP版本信息
- 机型信息
所以我们定义基础的异常处理器如下:
@Override
public void uncaughtException(Thread thread, Throwable throwable) {
try {
//将崩溃信息记录到文件
dumpToFile(thread, throwable);
} catch (IOException e) {
e.printStackTrace();
}
throwable.printStackTrace();
//如果系统仍有设置默认的处理器,则调用系统默认的
if (mDefaultUncaughtExceptionHandler != null) {
mDefaultUncaughtExceptionHandler.uncaughtException(thread, throwable);
} else {
//结束进程并退出
android.os.Process.killProcess(android.os.Process.myPid());
System.exit(10);
}
}
记录文件具体如下:
private void dumpToFile(Thread thread, Throwable ex) throws IOException {
File file = null;
PrintWriter printWriter = null;
String crashTime = dataFormat.format(new Date(System.currentTimeMillis()));
String dirPath = Utils.getCrashLogPath(mContext);
File dir = new File(dirPath);
if (!dir.exists()) {
boolean ok = dir.mkdirs();
if (!ok) {
return;
}
}
//Log文件的名字
String fileName = "Crash" + "_" + crashTime + FILE_NAME_SUFFIX;
file = new File(dir, fileName);
if (!file.exists()) {
boolean createNewFileOk = file.createNewFile();
if (!createNewFileOk) {
return;
}
}
try {
//开始写日志
printWriter = new PrintWriter(new BufferedWriter(new FileWriter(file)));
//崩溃时间
printWriter.println(crashTime);
//导出APP信息
dumpAppInfo(printWriter);
//导出手机信息
dumpPhoneInfo(printWriter);
//导出异常的调用栈信息
ex.printStackTrace(printWriter);
Log.e(TAG, "崩溃日志输入完成");
} catch (Exception e) {
Log.e(TAG, "导出信息失败");
} finally {
if (printWriter != null) {
printWriter.close();
}
}
}
好了,Java崩溃捕获大致就这样。
Demo地址:
https://github.com/timbobo/JavaCrashCatcher