针对于Android开发的小伙伴来说,对于Android Monitor的logcat再熟悉不过了,在这里我们可以查看到项目中的一些log信息,检测开发中出现的一些crash问题。不过由于种种原因,有的时候Android Monitor会闪屏或者crash信息丢失,比较不可控,不过倘若能将crash文件都存到手机上的我们自己创建的文件中岂不是更方便省事呢。
那么我们一起来研究如何创建一个CrashHander来解决
- 1、第一步
由于安全起见,我们需要catch所有类型的问题,那么也就需要实现Thread.UncaughtExceptionHandler接口,首先初始化 UncaughtExceptionHandler, 并设置该 CrashHander为程序的默认处理器
public void init(Context context) {
mContext = context;
// 获取系统默认的UncaughtException处理器
mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();
// 设置该CrashHandler为程序的默认处理器
Thread.setDefaultUncaughtExceptionHandler(this);
}
- 2、 第二步
作为程序员来说,我们在工作学习中的惯性思维是首先catch掉异常,所以接下来,我们重写uncaughtException方法来处理当UncaughtException发生时的异常情况(这里的mDefaultHandler是初始化时的Thread.UncaughtExceptionHandler)。
@Override
public void uncaughtException(Thread thread, Throwable ex) {
if (!handleException(ex) && mDefaultHandler != null) {
// 如果用户没有处理则让系统默认的异常处理器来处理
mDefaultHandler.uncaughtException(thread, ex);
} else {
try {
Thread.sleep(2500);
} catch (InterruptedException e) {
Logger.getLogger("error : " + e);
}
// 退出程序
android.os.Process.killProcess(android.os.Process.myPid());
System.exit(1);
}
}
- 3、第三步
之所以方便就是因为我们可以自定义写入crash的模式、crash的处理方式以及如何收集crash信息,发送crash报告(返回是否处理了该异常信息)。
private boolean handleException(Throwable ex) {
if (ex == null) {
return false;
}
ex.printStackTrace();
new Thread() {
@Override
public void run() {
Looper.prepare();
//反馈给用户发生crash了
Toast.makeText(mContext, R.string.text_error_crash, Toast.LENGTH_SHORT).show();
Looper.loop();
}
}.start();
//配置信息,如果在debug模式下
if (Config.DEBUG) {
// 收集设备参数信息
collectDeviceInfo(mContext);
// 保存日志文件
saveCrashInfo2File(ex);
}
//线上的crash的话我们可以借助友盟,将crash上报友盟
PrintsUMengUtil.reportError(mContext, ex);
return true;
}
- 4、第四步
收集设备参数信息,这里包括收集versionName和versionCode到file里
public void collectDeviceInfo(Context ctx) {
try {
//这里的PackageManager,PackageInfo是Android API 24中提供的
PackageManager pm = ctx.getPackageManager();
PackageInfo pi = pm.getPackageInfo(ctx.getPackageName(), PackageManager.GET_ACTIVITIES);
if (pi != null) {
String versionName = pi.versionName == null ? "null" : pi.versionName;
String versionCode = pi.versionCode + "";
infos.put("versionName", versionName);
infos.put("versionCode", versionCode);
}
} catch (PackageManager.NameNotFoundException e) {
Logger.getLogger(TAG, "an error occured when collect package info");
}
Field[] fields = Build.class.getDeclaredFields();
for (Field field : fields) {
try {
field.setAccessible(true);
infos.put(field.getName(), field.get(null).toString());
} catch (Exception e) {
Logger.getLogger(TAG, "an error occured when collect package info");
}
}
}
- 5、第五步
保存错误信息到文件中,这里返回的是文件名称,便于将文件传送到服务器上。
private String saveCrashInfo2File(Throwable ex) {
StringBuffer sb = new StringBuffer();
for (Map.Entry<String, String> entry : infos.entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
sb.append(key).append("=").append(value).append("\n");
}
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();
sb.append(result);
try {
long timestamp = System.currentTimeMillis();
String time = formatter.format(new Date());
String fileName = "crash-" + time + "-" + timestamp + ".log";
if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
String path = "/sdcard/crash_news/";
File dir = new File(path);
if (!dir.exists()) {
dir.mkdirs();
}
FileOutputStream fos = new FileOutputStream(path + fileName);
sb.append("\r\n报错日期:");
sb.append(new Date(System.currentTimeMillis()).toLocaleString()).append("\r\n");
printStackTrace(sb, ex);
fos.write(sb.toString().getBytes());
fos.close();
}
return fileName;
} catch (Exception e) {
Logger.getLogger(TAG, "an error occured while writing file...");
}
return null;
}
public void printStackTrace(StringBuffer sb, Throwable ex) {
if (sb == null) sb = new StringBuffer();
StackTraceElement[] trace = ex.getStackTrace();
synchronized (sb) {
sb.append(ex.toString() + "\r\n");
for (int i = 0; i < trace.length; i++) {
sb.append(" at " + trace[i] + "\r\n");
}
Throwable ourCause = ex.getCause();
if (ourCause != null)
printStackTraceAsCause(sb, ourCause, trace);
}
}
private void printStackTraceAsCause(StringBuffer sb, Throwable ex, StackTraceElement[]
causedTrace) {
// assert Thread.holdsLock(s);
// Compute number of frames in common between this and caused
if (sb == null) sb = new StringBuffer();
StackTraceElement[] trace = ex.getStackTrace();
int m = trace.length - 1, n = causedTrace.length - 1;
while (m >= 0 && n >= 0 && trace[m].equals(causedTrace[n])) {
m--;
n--;
}
int framesInCommon = trace.length - 1 - m;
sb.append("Caused by: ");
sb.append(ex + "\r\n");
for (int i = 0; i <= m; i++) {
sb.append(" at " + trace[i] + "\r\n");
}
if (framesInCommon != 0) {
sb.append(" ..." + framesInCommon + " more \r\n");
}
// Recurse if we have a cause
Throwable ourCause = ex.getCause();
if (ourCause != null)
printStackTraceAsCause(sb, ourCause, trace);
}
- 6、第6步
网络请求数据时对onResponse函数中的,对于异常code的存储。
public void saveLogInfo2File(String s) {
if (TextUtils.isEmpty(s)) {
return;
}
StringBuffer sb = new StringBuffer();
for (Map.Entry<String, String> entry : infos.entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
sb.append(key + "=" + value + "\n");
}
sb.append(s);
try {
long timestamp = System.currentTimeMillis();
String time = formatter.format(new Date());
String fileName = "log-" + time + "-" + timestamp + ".log";
if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
String path = "/sdcard/crash_news/";
File dir = new File(path);
if (!dir.exists()) {
dir.mkdirs();
}
FileOutputStream fos = new FileOutputStream(path + fileName);
sb.append("\r\nLog日期:");
sb.append(new Date(System.currentTimeMillis()).toLocaleString() + "\r\n");
fos.write(sb.toString().getBytes());
fos.close();
}
} catch (Exception e) {
Logger.getLogger(TAG, "an error occured while writing file..." + e);
}
}
- 7、第七步
对Crash进行初始化和调用。此处是在BaseApplication中通过initCrashHandler方法进行初始化。
private void initCrashHandler() {
CrashHandler crashHandler = CrashHandler.getInstance();
crashHandler.init(getApplicationContext());
}
-
8、第八步
在手机上下载并安装ES文件浏览器,由于这里自定义的crash文件的存储路径为/sdcard/crash_news/,文件名定义为crash_news了,当然了,这些都可以自行定义。
打开crash文件就能看见捕获的crash文件,非常方便,like this:
虽然我们比较不愿意看到crash ,但是面对crash我们还是不能放过任何一个,这样做如此一来便可以更加高效便捷的处理问题。