CrashHandler处理异常
情景
Android应用无法避免不会发生crash,可能属于系统底层的bug、或者机型适配、亦或者糟糕的网络环境。
当crash发生时,系统会kill我们的应用程序,会闪退或者提升用户程序已停止运行,而且更恐怖的是,对于开发者而言,是无法知道当时用户所操作或者面临的网络环境的,也无能为力的去解决改bug。
但是Android系统提供了这类问题的方法:UncaughtExceptionHandler
CrashHandler
public interface UncaughtExceptionHandler {
/**
* Method invoked when the given thread terminates due to the
* given uncaught exception.
* <p>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);
}
源码给出的定义如下:当一个线程由于未捕获异常而即将终止时,会回调这个接口。
当然有设置该接口的:
public static void setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler eh) {
// Android-removed: SecurityManager stubbed out on Android
/*
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(
new RuntimePermission("setDefaultUncaughtExceptionHandler")
);
}
*/
defaultUncaughtExceptionHandler = eh;
}
思路
根据上面的理解,我们可以设置一个UncaughtExceptionHandler
接口。当系统发送crash时,就会回调uncaughtException
接口,这是可以普获到异常信息了。
我们可以记录当前的崩溃信息,存在本地,记得记录当时硬件环境。再下次合理的时机将错误信息上传,对于缓存问题,如果存在脏数据导致的经常性崩溃,就可以强行清除本地缓存,也是一种避险手段。在上传服务器有几种选择:一是立即上传;二是等待应用空闲上传。
public class CrashHandler implements UncaughtExceptionHandler {
private static final String TAG = "CrashHandler";
private static final String INT_CRASH_TIME = "int_crash_time";
private static final int MAX_CRASH_TIME = 3;
private static CrashHandler instance;
private Context mContext;
private UncaughtExceptionHandler mOriginalHandler;
public static CrashHandler getInstance() {
if (instance == null) {
instance = new CrashHandler();
}
return instance;
}
/**
* 初始化
*/
public void init(Context context) {
mContext = context;
mOriginalHandler = Thread.getDefaultUncaughtExceptionHandler();
/**
* 若打开L开关则小翼不打盹,反之则打盹
* */
// if (!L.isDebug) {
//设置该CrashHandler为程序的默认处理器
Thread.setDefaultUncaughtExceptionHandler(this);
// }
}
/**
* 把崩溃信息放在sd卡缓存目录下,若没有sd卡,就放在data/data中的缓存目录下
*
* <p>SDCard/Android/data/你的应用包名/cache/<p/>
*/
private static File getDiskCrashDir(Context context, String dirName) {
String cachePath;
if (context.getExternalCacheDir() != null) {
cachePath = context.getExternalCacheDir().getPath();
} else {
cachePath = context.getFilesDir().getPath();
}
return new File(cachePath + File.separator + dirName);
}
@Override
public void uncaughtException(Thread thread, Throwable ex) {
try {
Log.e("未捕获的异常", ex.toString());
handleCrash();
if (BuildConfig.DEBUG) {
saveCrash2File(ex);
}
uploadExceptionToService(ex);
} catch (Exception e) {
Log.e("uncaughtException又抛出的异常", ex.toString());
}
mOriginalHandler.uncaughtException(thread, ex);
}
private void uploadExceptionToService(Throwable ex) {
//上传操作
// 一定要获取一些硬件信息(在不侵犯隐私的前提下)
}
private void handleCrash() {
if (MyApplication.getInstance() != null) {
//elapsedTime 这个是 系统启动后到发送崩溃的时间
final long elapsedTime = SystemClock.elapsedRealtime() - MyApplication.getInstance().getStartTime();
Log.e(TAG, "elapsedTime:" + elapsedTime);
//如果在启动后10s内就崩溃了,那就需要清除缓存了,很大概率就是缓存出现了脏数据,导致启动读取缓存数据就崩溃了。
if (elapsedTime < 10 * 1000) {
int time = (Integer) SPUtil.get(MyApplication.getInstance(),"app",INT_CRASH_TIME, 0);
time += 1;
Log.e(TAG, "崩溃次数" + time);
if (time >= MAX_CRASH_TIME) {
Log.e(TAG, "崩溃次数达到上限,清除缓存");
//reset
SPUtil.put(MyApplication.getInstance(),"app",INT_CRASH_TIME, 0);
//到达上限 清除缓存相关数据
CacheUtil.clean();
} else {
SPUtil.put(MyApplication.getInstance(),"app",INT_CRASH_TIME, time);
}
}
} else {
Log.e(TAG, "获取不到context");
}
}
/**
* 保存错误信息到文件中
*
* @param ex 崩溃信息
* @throws IOException
*/
public void saveCrash2File(Throwable ex) throws IOException {
StringBuilder sb = new StringBuilder();
Writer writer = new StringWriter();
PrintWriter printWriter = new PrintWriter(writer);
ex.printStackTrace(printWriter);
Throwable cause = ex.getCause(); // 返回此异常的原因(尝试加载类时发生错误引发的异常;否则返回 null)
while (cause != null) {
cause.printStackTrace(printWriter);
cause = cause.getCause();
}
printWriter.close();
String result = writer.toString();
sb.append(result);
try {
// 用于格式化日期,作为日志文件名的一部分
DateFormat formatter = new SimpleDateFormat("MM.dd@HH.mm", Locale.CHINA);
String time = formatter.format(new Date());
String fileName = time + ".log";
File dir = getDiskCrashDir(mContext, "crash");
if (!dir.exists()) {
if (!dir.mkdirs()) {
return;
}
}
String filePath = dir.getAbsolutePath() + File.separator + fileName;
FileOutputStream fos = new FileOutputStream(filePath);
fos.write(sb.toString().getBytes());
fos.close();
} catch (IOException e) {
Log.e(TAG,e.toString());
}
}
}
在应用启动的时候初始化: CrashHandler.getInstance().init(getApplication());