針對(duì)于Android開發(fā)的小伙伴來說莽红,對(duì)于Android Monitor的logcat再熟悉不過了,在這里我們可以查看到項(xiàng)目中的一些log信息磷蜀,檢測開發(fā)中出現(xiàn)的一些crash問題包雀。不過由于種種原因谢谦,有的時(shí)候Android Monitor會(huì)閃屏或者crash信息丟失进陡,比較不可控愿阐,不過倘若能將crash文件都存到手機(jī)上的我們自己創(chuàng)建的文件中豈不是更方便省事呢。
那么我們一起來研究如何創(chuàng)建一個(gè)CrashHander來解決
- 1趾疚、第一步
由于安全起見缨历,我們需要catch所有類型的問題,那么也就需要實(shí)現(xiàn)Thread.UncaughtExceptionHandler接口盗蟆,首先初始化 UncaughtExceptionHandler戈二, 并設(shè)置該 CrashHander為程序的默認(rèn)處理器
public void init(Context context) {
mContext = context;
// 獲取系統(tǒng)默認(rèn)的UncaughtException處理器
mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();
// 設(shè)置該CrashHandler為程序的默認(rèn)處理器
Thread.setDefaultUncaughtExceptionHandler(this);
}
- 2、 第二步
作為程序員來說喳资,我們?cè)诠ぷ鲗W(xué)習(xí)中的慣性思維是首先catch掉異常,所以接下來腾供,我們重寫uncaughtException方法來處理當(dāng)UncaughtException發(fā)生時(shí)的異常情況(這里的mDefaultHandler是初始化時(shí)的Thread.UncaughtExceptionHandler)仆邓。
@Override
public void uncaughtException(Thread thread, Throwable ex) {
if (!handleException(ex) && mDefaultHandler != null) {
// 如果用戶沒有處理則讓系統(tǒng)默認(rèn)的異常處理器來處理
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鲜滩、第三步
之所以方便就是因?yàn)槲覀兛梢宰远x寫入crash的模式、crash的處理方式以及如何收集crash信息节值,發(fā)送crash報(bào)告(返回是否處理了該異常信息)徙硅。
private boolean handleException(Throwable ex) {
if (ex == null) {
return false;
}
ex.printStackTrace();
new Thread() {
@Override
public void run() {
Looper.prepare();
//反饋給用戶發(fā)生crash了
Toast.makeText(mContext, R.string.text_error_crash, Toast.LENGTH_SHORT).show();
Looper.loop();
}
}.start();
//配置信息,如果在debug模式下
if (Config.DEBUG) {
// 收集設(shè)備參數(shù)信息
collectDeviceInfo(mContext);
// 保存日志文件
saveCrashInfo2File(ex);
}
//線上的crash的話我們可以借助友盟搞疗,將crash上報(bào)友盟
PrintsUMengUtil.reportError(mContext, ex);
return true;
}
- 4嗓蘑、第四步
收集設(shè)備參數(shù)信息,這里包括收集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桩皿、第五步
保存錯(cuò)誤信息到文件中,這里返回的是文件名稱幢炸,便于將文件傳送到服務(wù)器上泄隔。
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報(bào)錯(cuò)日期:");
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步
網(wǎng)絡(luò)請(qǐng)求數(shù)據(jù)時(shí)對(duì)onResponse函數(shù)中的宛徊,對(duì)于異常code的存儲(chǔ)佛嬉。
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、第七步
對(duì)Crash進(jìn)行初始化和調(diào)用闸天。此處是在BaseApplication中通過initCrashHandler方法進(jìn)行初始化暖呕。
private void initCrashHandler() {
CrashHandler crashHandler = CrashHandler.getInstance();
crashHandler.init(getApplicationContext());
}
-
8、第八步
在手機(jī)上下載并安裝ES文件瀏覽器苞氮,由于這里自定義的crash文件的存儲(chǔ)路徑為/sdcard/crash_news/缰揪,文件名定義為crash_news了,當(dāng)然了葱淳,這些都可以自行定義钝腺。
打開crash文件就能看見捕獲的crash文件,非常方便,like this:
雖然我們比較不愿意看到crash ,但是面對(duì)crash我們還是不能放過任何一個(gè),這樣做如此一來便可以更加高效便捷的處理問題赞厕。