背景
这是最早参加工作时候学习的一个技术,程序在运行时遇到崩溃时,作为开发人员如何感知并解决?
应用异常在所难免,所以处理异常情况下应用出现的崩溃就非常有必要。
崩溃顾名思义,不像普通的异常,它们是无法预知且不可控的。
因此捕获应用崩溃日志数据的意义很大。开发者能通过捕获到的崩溃日志分析问题,继而解决问题。
最早做Android开发时我很奇怪,应用已经崩溃了,那么谁来负责捕获这个异常?应用本身的代码怎么获取到异常信息并且保存呢?难道需要开发一个伴侣应用进行监测?当时想捕获崩溃日志是不可能实现的。
方案
通过了解异常捕获和抛出的过程和原理,发现崩溃异常出现之后的一系列操作都是可控的。
应用中异常会不断出现,有些异常没有被捕获处理,这样就会导致应用崩溃。因为这时候异常信息就交给系统虚拟机进行处理,虚拟机会Crash该应用,导致应用出现崩溃的情况。
我们捕获崩溃日志就是在异常交给虚拟机处理之前,应用本身获取日志信息,并进行保存或者上传的操作。从而实现获取异常日志,分析日志的功能。
实现
具体实现就非常简单了,这里以把崩溃日志保存成日志文件的方式为例,说明整个处理过程。
- 首先创建捕获全局异常的类,捕获应用中未处理的异常,即崩溃情况。
package com.hbd.mobilepstn.utils;
import android.content.Context;
import android.os.Environment;
import android.os.Looper;
import android.util.Log;
import android.widget.Toast;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.Thread.UncaughtExceptionHandler;
import java.util.Date;
/**
* User:Gurecn [email protected]
* Date:2016/9/20
* I'm glad to share my knowledge with you all.
* 全局异常处理.
*/
public class CrashHandler implements UncaughtExceptionHandler {
private static CrashHandler instance;
private Context ctx;
public static CrashHandler getInstance() {
if (instance == null) {
instance = new CrashHandler();
}
return instance;
}
public void init(Context ctx) {
this.ctx = ctx;
Thread.setDefaultUncaughtExceptionHandler(this);
}
@Override
public void uncaughtException(Thread arg0, Throwable arg1) {
String logdir;
if (Environment.getExternalStorageDirectory() != null) {
logdir = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "hbdLog";
File file = new File(logdir);
boolean mkSuccess;
if (!file.isDirectory()) {
mkSuccess = file.mkdirs();
if (!mkSuccess) {
mkSuccess = file.mkdirs();
}
}
try {
FileWriter fw = new FileWriter(logdir + File.separator + "error.log", true);
if (fw != null) {
fw.write(new Date() + "\n");
StackTraceElement[] stackTrace = arg1.getStackTrace();
fw.write(arg1.getMessage() + "\n");
for (int i = 0; i < stackTrace.length; i++) {
fw.write("file:" + stackTrace[i].getFileName() + " class:" + stackTrace[i].getClassName()
+ " method:" + stackTrace[i].getMethodName() + " line:" + stackTrace[i].getLineNumber() + "\n");
}
fw.write("\n");
fw.close();
}
} catch (IOException e) {
Log.e("crash handler", "load file failed...", e.getCause());
}
}
arg1.printStackTrace();
new Thread() {
@Override
public void run() {
Looper.prepare();
Toast.makeText(ctx, "遇到异常了.你能把hbdLog/error.log给我们,我们一起完善", Toast.LENGTH_LONG).show();
Looper.loop();
}
}.start();
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.exit(0);
}
}
- 然后将捕获异常类应用到APP中,仅需要在Application中的OnCreate方法中添加两句话,使其生效:
CrashHandler crashHandler = CrashHandler.getInstance();
crashHandler.init(this);
总结
其中最关键的设置生效的方式是调用系统提供的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 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.
翻译:
设置当线程由于未捕获的异常(uncaught exception)而突然终止时调用的默认处理程序,并且没有为该线程定义其他处理程序(gurecn:重复设置时以最后一次设置为准)。
未捕获的异常处理首先由线程控制,然后由线程的 ThreadGroup 对象控制,最后由默认的未捕获异常处理程序控制。如果线程没有设置显式的未捕获异常处理程序,并且线程的线程组(包括父线程组)没有专门化其 uncaughtException 方法,则将调用默认处理程序的 uncaughtException 方法。
通过设置默认的未捕获异常处理程序,应用程序可以为那些已经接受系统提供的任何“默认”行为的线程更改处理未捕获异常的方式(例如记录到特定设备或文件)。
请注意,默认的未捕获异常处理程序通常不应遵循线程的 ThreadGroup 对象,因为这可能会导致无限递归。(gurecn:启动-崩溃-启动-崩溃。。。。此时手机一般会提示:该应用多次崩溃,建议强制关闭。)