我们经常会面临这样的情景:应用发布上线后,收到用户反馈说app崩溃了,但自己重现不了。这时候怎么办呢?
很多朋友都会想到用友盟等第三方插件实现,但鉴于安全性要求较高的支付系统,是不允许使用未知来源压缩包/有后门的第三方插件。这时候我们可以考虑自己写一个。
先来看一段代码,了解UncaughtExceptionHandler的最基本用法:
/**
* 程序入口
*/
public class App extends Application {
@Override
public void onCreate() {
super.onCreate();
Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread thread, Throwable ex) {
Toast.makeText(App.this, "程序遇到错误:" + ex.getMessage(), Toast.LENGTH_LONG).show();
ex.printStackTrace();
}
});
}
}
Java中有两种异常:已检测异常(Checked exceptions)和未检测异常(Unchecked exceptions)。
前者必须使用 throws 或 try catch 进行异常处理;后者不需要指定或捕获。
因为run()方法不接受throws语句,所以当一个检测的异常在一个Thread对象的run()方法中抛出,我们需要对其进行捕获并做相应的处理。
java对未检测异常默认处理方式是:将堆栈跟踪信息写到 控制台中(或者记录到错误日志文件中)然后退出程序。常见的APP崩溃现象(应用程序XXX已经停止),正是基于这一原理。
我们可以通过UncaughtExceptionHandler,用来捕获并处理在一个线程对象中抛出的未检测异常,以避免程序终止。。
通俗的理解:setDefaultUncaughtExceptionHandler 相当于app全局的 catch ,用于捕获程序未捕获的异常。
基本逻辑的实现:
public class CrashManager implements UncaughtExceptionHandler {
public static final String TAG = "CrashHandler";
// CrashHandler实例
private static CrashManager instance;
// 程序的Context对象
private Application application;
// 系统默认的UncaughtException处理类
private UncaughtExceptionHandler mDefaultHandler;
/**
* 保证只有一个CrashHandler实例
*/
private CrashManager(Context context) {
application = (Application) context.getApplicationContext();
mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();
Thread.setDefaultUncaughtExceptionHandler(this);
}
/**
* 获取CrashHandler实例 ,单例模式
*/
public static CrashManager getInstance(Context context) {
CrashManager inst = instance;
if (inst == null) {
synchronized (CrashManager.class) {
inst = instance;
if (inst == null) {
inst = new CrashManager(context.getApplicationContext());
instance = inst;
}
}
}
return inst;
}
/**
* 当UncaughtException发生时会转入该函数来处理
*/
@Override
public void uncaughtException(Thread thread, Throwable ex) {
TaskManager.getInstance(application).saveErrorLog(ex);
mDefaultHandler.uncaughtException(thread, ex);
}
}
日志写入sdcard代码:
public class SaveErrorTask<T> extends Task<Object, Object, Void> {
private SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss", Locale.CHINA);
private Context context;
private Throwable ex;
public SaveErrorTask(Context context, Throwable ex) {
setPriority(TaskPriority.UI_LOW);
this.context = context;
this.ex = ex;
}
@Override
protected Void doInBackground(Object... arg0) {
Writer writer = new StringWriter();
PrintWriter printWriter = new PrintWriter(writer);
ex.printStackTrace(printWriter);
Throwable cause = ex.getCause();
while (cause != null) {
cause.printStackTrace(printWriter);
cause = cause.getCause();
}
printWriter.close();
String result = writer.toString();
String time = formatter.format(new Date());
String fileName = time + ".txt";
StringBuilder stringBuffer = new StringBuilder();
DeviceInfo deviceInfo = Utils.getDeviceInfo(context);
stringBuffer.append("\nsdkVersion:" + deviceInfo.sdkVersion);
stringBuffer.append("\nmanufacturer:" + deviceInfo.manufacturer);
stringBuffer.append("\nmodel:" + deviceInfo.model);
stringBuffer.append("\nversion" + ConfigManager.getVersionName(context));
stringBuffer.append("\nerrorStr:" + result);
stringBuffer.append("\ntime:" + time);
String filePath = CacheFileUtils.getLogPath(fileName);
CacheFileUtils.saveErrorStr(filePath, stringBuffer.toString());
return null;
}
}
初始化:
public class MyApplication extends MultiDexApplication {
@Override
public void onCreate() {
super.onCreate();
//初始化 错误日子系统
CrashManager.getInstance(this);
}
}
参考链接:
http://www.importnew.com/14434.html
https://www.cnblogs.com/whoislcj/p/5468190.html