JVM为我们提供了线程的未捕获异常处理机制,通过Thread的setUncaughtExceptionHandler方法:
public void setUncaughtExceptionHandler(UncaughtExceptionHandler eh)
通过该方法给某个thread设置一个UncaughtExceptionHandler,可以确保在该线程出现异常时能通过回调UncaughtExceptionHandler接口的public void uncaughtException(Thread t, Throwable e) 方法来处理异常,这样的好处或者说目的是可以在线程代码边界之外(Thread的run()方法之外),有一个地方能处理未捕获异常。但是要特别明确的是:虽然是在回调方法中处理异常,但这个回调方法在执行时依然还在抛出异常的这个线程中!另外还要特别说明一点:如果线程是通过线程池创建,线程异常发生时UncaughtExceptionHandler接口不一定会立即回调。
当遇到未捕获异常的情况下,记录android程序无缘无故crash,自定义CrashExceptionHandle implements UncaughtExceptionHandler,处理未捕获异常,并将日志持久化到文件中,有助于排查bug。
package xyz.o88o.crash;
import android.content.Context;
import org.json.JSONObject;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.lang.Thread.UncaughtExceptionHandler;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import xyz.o88o.AppContext;
import xyz.o88o.account.AccountUtils;
import xyz.o88o.utils.DeviceUtil;
import xyz.o88o.utils.FileUtil;
import xyz.o88o.utils.LogUtil;
public class CrashExceptionHandle implements UncaughtExceptionHandler {
private final static String TAG = CrashExceptionHandle.class.getSimpleName();
public final static String KEY_JSON_ERROR_STACK = "error_stack";
Context mContext;
// 用于格式化日期,作为日志文件名的一部分
private DateFormat formatter = new SimpleDateFormat("yyyy_MM_dd-HH_mm_ss");
// CrashHandler实例
private static CrashExceptionHandle instance;
// 系统默认的UncaughtException处理类
private UncaughtExceptionHandler mDefaultHandler;
private CrashExceptionHandle() {
// 获取系统默认的UncaughtException处理器
mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();
// 设置该CrashHandler为程序的默认处理器
Thread.setDefaultUncaughtExceptionHandler(this);
}
public synchronized static CrashExceptionHandle getIntance() {
if (instance == null) {
instance = new CrashExceptionHandle();
}
return instance;
}
public void init(Context ctx) {
mContext = ctx;
}
@Override
public void uncaughtException(Thread thread, Throwable ex) {
handleException(thread, ex);
mDefaultHandler.uncaughtException(thread, ex);// 系统默认异常处理器
}
private boolean handleException(final Thread thread, final Throwable ex) {
if (ex == null) {
return false;
}
LogUtil.e(TAG, "crash", ex);
new Thread() {
@Override
public void run() {
saveCrashLog2File(ex);
}
}.start();
return true;
}
private void saveCrashLog2File(Throwable ex) {
String time = formatter.format(System.currentTimeMillis());
String filename = "crash_" + time + ".log";
JSONObject messageRootObject = new JSONObject();
try {
messageRootObject.put("device_id", DeviceUtil.mDeviceId);
messageRootObject.put("@timestamp", time);
messageRootObject.put("uid", AccountUtils.getAccountUID(AppContext.getContext()));
messageRootObject.put("platform", DeviceUtil.mOs);
messageRootObject.put("error", ex.toString());
messageRootObject.put(KEY_JSON_ERROR_STACK, buildStackTraceFromException(ex));
} catch (Exception e) {
e.printStackTrace();
}
StringBuilder builder = new StringBuilder();
builder.append(messageRootObject.toString()).append("\n");
write(filename, builder.toString(), false);
}
private String buildStackTraceFromException(Throwable ex) {
String context = null;
if (ex != null) {
context = ex.toString() + "\n";
StackTraceElement[] ste = ex.getStackTrace();
for (int i = 0; i < ste.length; i++) {
context += " at " + ste[i].toString() + "\n";
}
Throwable cex = ex.getCause();
if (cex != null) {
ste = cex.getStackTrace();
context += "Cased by: " + cex.toString() + "\n";
for (int i = 0; i < ste.length; i++) {
context += " at " + ste[i].toString() + "\n";
}
}
}
return context;
}
/**
* 写入文件,并将文件路径写入tray
*
* @param fileName
* @param content
* @param append
*/
private void write(String fileName, String content, boolean append) {
File file = new File(FileUtil.SDCARD_STORAGE_PATH_LOG, fileName);
if (!file.getParentFile().exists()) {
file.getParentFile().mkdir();
}
if (!file.exists()) {
try {
file.createNewFile();
} catch (IOException e) {
}
}
BufferedWriter out = null;
try {
out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file, append)));
out.write(content);
} catch (Exception e) {
} finally {
try {
if (out != null) {
out.flush();
out.close();
out = null;
}
} catch (IOException e) {
}
}
}
}