失眠网,内容丰富有趣,生活中的好帮手!
失眠网 > android 崩溃日志捕获 安卓Java崩溃的捕获和日志记录

android 崩溃日志捕获 安卓Java崩溃的捕获和日志记录

时间:2020-11-18 23:53:41

相关推荐

android 崩溃日志捕获 安卓Java崩溃的捕获和日志记录

Android的两种崩溃

Android 崩溃分为 Java 崩溃和 Native崩溃两种。

Java崩溃的知识点

Java崩溃.png

Java崩溃的原因

简单来说,Java崩溃就是在Java代码中,出现了未被捕获的异常,导致应用程序异常退出。

Java异常的归类

Java的异常可分为分为可查的异常(checkedexceptions)和不可查的异常(unchecked exceptions)

常见的异常可归类为如下图:

Throwable.png

其中Error和RuntimeException是unchecked exceptions,编译器默认无法通过对其处理。其余checkedexceptions,需要我们在代码中try-catch。

崩溃的捕捉

UncaughtExceptionHandler

先来看一下这个接口的作用

/**

* Interface for handlers invoked when aThreadabruptly

* 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

*UncaughtExceptionHandlerusing

* {@link #getUncaughtExceptionHandler} and will invoke the handler's

*uncaughtExceptionmethod, passing the thread and the

* exception as arguments.

* If a thread has not had itsUncaughtExceptionHandler

* explicitly set, then itsThreadGroupobject acts as its

*UncaughtExceptionHandler. If theThreadGroupobject

* 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

*uncaughtExceptionmethod, then the default handler's

*uncaughtExceptionmethod 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'sThreadGroupobject, as that could cause

* infinite recursion.

*

* @param eh the object to use as the default uncaught exception handler.

* Ifnullthen 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地址:

如果觉得《android 崩溃日志捕获 安卓Java崩溃的捕获和日志记录》对你有帮助,请点赞、收藏,并留下你的观点哦!

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。